[Spice-devel] [PATCH spice-gtk 2/3] Move gtk/ -> src/

Marc-André Lureau marcandre.lureau at redhat.com
Fri Jun 5 09:29:43 PDT 2015


For historical reasons, the code was placed under gtk/ subdirectory.
If it was always bugging you, bug no more!
---
 Makefile.am                                  |    2 +-
 configure.ac                                 |   10 +-
 doc/reference/Makefile.am                    |   10 +-
 gtk/Makefile.am                              |  703 ------
 gtk/bio-gio.c                                |  114 -
 gtk/bio-gio.h                                |   30 -
 gtk/channel-base.c                           |  284 ---
 gtk/channel-cursor.c                         |  529 -----
 gtk/channel-cursor.h                         |   77 -
 gtk/channel-display-mjpeg.c                  |  156 --
 gtk/channel-display-priv.h                   |  113 -
 gtk/channel-display.c                        | 1789 ---------------
 gtk/channel-display.h                        |  102 -
 gtk/channel-inputs.c                         |  603 ------
 gtk/channel-inputs.h                         |   89 -
 gtk/channel-main.c                           | 2993 --------------------------
 gtk/channel-main.h                           |  109 -
 gtk/channel-playback-priv.h                  |   24 -
 gtk/channel-playback.c                       |  496 -----
 gtk/channel-playback.h                       |   76 -
 gtk/channel-port.c                           |  361 ----
 gtk/channel-port.h                           |   76 -
 gtk/channel-record.c                         |  482 -----
 gtk/channel-record.h                         |   77 -
 gtk/channel-smartcard.c                      |  587 -----
 gtk/channel-smartcard.h                      |   68 -
 gtk/channel-usbredir-priv.h                  |   61 -
 gtk/channel-usbredir.c                       |  686 ------
 gtk/channel-usbredir.h                       |   71 -
 gtk/channel-webdav.c                         |  613 ------
 gtk/channel-webdav.h                         |   68 -
 gtk/client_sw_canvas.c                       |   20 -
 gtk/client_sw_canvas.h                       |   25 -
 gtk/continuation.c                           |  102 -
 gtk/continuation.h                           |   61 -
 gtk/controller/Makefile.am                   |  100 -
 gtk/controller/controller.vala               |  286 ---
 gtk/controller/custom.h                      |   22 -
 gtk/controller/custom.vapi                   |   28 -
 gtk/controller/dump.c                        |  118 -
 gtk/controller/foreign-menu.vala             |  197 --
 gtk/controller/gio-windows-2.0.vapi          |   30 -
 gtk/controller/menu.vala                     |  108 -
 gtk/controller/namedpipe.c                   |  270 ---
 gtk/controller/namedpipe.h                   |   59 -
 gtk/controller/namedpipeconnection.c         |  245 ---
 gtk/controller/namedpipeconnection.h         |   56 -
 gtk/controller/namedpipelistener.c           |  329 ---
 gtk/controller/namedpipelistener.h           |   70 -
 gtk/controller/spice-controller-listener.c   |  159 --
 gtk/controller/spice-controller-listener.h   |   47 -
 gtk/controller/spice-foreign-menu-listener.c |  161 --
 gtk/controller/spice-foreign-menu-listener.h |   47 -
 gtk/controller/test.c                        |  292 ---
 gtk/controller/util.vala                     |   42 -
 gtk/controller/win32-util.c                  |  161 --
 gtk/controller/win32-util.h                  |   30 -
 gtk/coroutine.h                              |   83 -
 gtk/coroutine_gthread.c                      |  170 --
 gtk/coroutine_ucontext.c                     |  150 --
 gtk/coroutine_winfibers.c                    |  126 --
 gtk/decode-glz-tmpl.c                        |  336 ---
 gtk/decode-glz.c                             |  475 ----
 gtk/decode-jpeg.c                            |  191 --
 gtk/decode-zlib.c                            |   89 -
 gtk/decode.h                                 |   44 -
 gtk/desktop-integration.c                    |  223 --
 gtk/desktop-integration.h                    |   64 -
 gtk/gio-coroutine.c                          |  275 ---
 gtk/gio-coroutine.h                          |   66 -
 gtk/giopipe.c                                |  484 -----
 gtk/giopipe.h                                |   29 -
 gtk/glib-compat.c                            |   79 -
 gtk/glib-compat.h                            |   68 -
 gtk/gtk-compat.h                             |   56 -
 gtk/keymap-gen.pl                            |  214 --
 gtk/keymaps.csv                              |  490 -----
 gtk/map-file                                 |  139 --
 gtk/smartcard-manager-priv.h                 |   37 -
 gtk/smartcard-manager.c                      |  737 -------
 gtk/smartcard-manager.h                      |   80 -
 gtk/spice-audio-priv.h                       |   42 -
 gtk/spice-audio.c                            |  274 ---
 gtk/spice-audio.h                            |  109 -
 gtk/spice-channel-cache.h                    |  106 -
 gtk/spice-channel-enums.h                    |    7 -
 gtk/spice-channel-priv.h                     |  203 --
 gtk/spice-channel.c                          | 2960 -------------------------
 gtk/spice-channel.h                          |  131 --
 gtk/spice-client-glib-usb-acl-helper.c       |  372 ----
 gtk/spice-client-gtk-manual.defs             |  117 -
 gtk/spice-client-gtk-module.c                |   45 -
 gtk/spice-client-gtk.override                |  171 --
 gtk/spice-client.c                           |   27 -
 gtk/spice-client.h                           |   79 -
 gtk/spice-cmdline.c                          |   98 -
 gtk/spice-cmdline.h                          |   29 -
 gtk/spice-common.h                           |   36 -
 gtk/spice-glib-sym-file                      |  111 -
 gtk/spice-grabsequence.c                     |  163 --
 gtk/spice-grabsequence.h                     |   61 -
 gtk/spice-gstaudio.c                         |  739 -------
 gtk/spice-gstaudio.h                         |   56 -
 gtk/spice-gtk-session-priv.h                 |   34 -
 gtk/spice-gtk-session.c                      | 1229 -----------
 gtk/spice-gtk-session.h                      |   65 -
 gtk/spice-gtk-sym-file                       |   23 -
 gtk/spice-marshal.txt                        |   14 -
 gtk/spice-option.c                           |  284 ---
 gtk/spice-option.h                           |   31 -
 gtk/spice-pulse.c                            | 1354 ------------
 gtk/spice-pulse.h                            |   57 -
 gtk/spice-session-priv.h                     |  104 -
 gtk/spice-session.c                          | 2728 -----------------------
 gtk/spice-session.h                          |  103 -
 gtk/spice-types.h                            |   35 -
 gtk/spice-uri-priv.h                         |   30 -
 gtk/spice-uri.c                              |  462 ----
 gtk/spice-uri.h                              |   52 -
 gtk/spice-util-priv.h                        |   52 -
 gtk/spice-util.c                             |  497 -----
 gtk/spice-util.h                             |   63 -
 gtk/spice-version.h.in                       |   70 -
 gtk/spice-widget-cairo.c                     |  160 --
 gtk/spice-widget-priv.h                      |  141 --
 gtk/spice-widget-x11.c                       |  280 ---
 gtk/spice-widget.c                           | 2642 -----------------------
 gtk/spice-widget.h                           |   92 -
 gtk/spicy-screenshot.c                       |  196 --
 gtk/spicy-stats.c                            |  144 --
 gtk/spicy.c                                  | 1855 ----------------
 gtk/usb-acl-helper.c                         |  299 ---
 gtk/usb-acl-helper.h                         |   72 -
 gtk/usb-device-manager-priv.h                |   48 -
 gtk/usb-device-manager.c                     | 1932 -----------------
 gtk/usb-device-manager.h                     |  122 --
 gtk/usb-device-widget.c                      |  554 -----
 gtk/usb-device-widget.h                      |   81 -
 gtk/usbutil.c                                |  323 ---
 gtk/usbutil.h                                |   39 -
 gtk/vmcstream.c                              |  535 -----
 gtk/vmcstream.h                              |   81 -
 gtk/vncdisplaykeymap.c                       |  323 ---
 gtk/vncdisplaykeymap.h                       |   36 -
 gtk/win-usb-clerk.h                          |   36 -
 gtk/win-usb-dev.c                            |  542 -----
 gtk/win-usb-dev.h                            |  110 -
 gtk/win-usb-driver-install.c                 |  398 ----
 gtk/win-usb-driver-install.h                 |  104 -
 gtk/wocky-http-proxy.c                       |  537 -----
 gtk/wocky-http-proxy.h                       |   56 -
 po/POTFILES.in                               |   22 +-
 po/POTFILES.skip                             |    3 +-
 src/Makefile.am                              |  703 ++++++
 src/bio-gio.c                                |  114 +
 src/bio-gio.h                                |   30 +
 src/channel-base.c                           |  284 +++
 src/channel-cursor.c                         |  529 +++++
 src/channel-cursor.h                         |   77 +
 src/channel-display-mjpeg.c                  |  156 ++
 src/channel-display-priv.h                   |  113 +
 src/channel-display.c                        | 1789 +++++++++++++++
 src/channel-display.h                        |  102 +
 src/channel-inputs.c                         |  603 ++++++
 src/channel-inputs.h                         |   89 +
 src/channel-main.c                           | 2993 ++++++++++++++++++++++++++
 src/channel-main.h                           |  109 +
 src/channel-playback-priv.h                  |   24 +
 src/channel-playback.c                       |  496 +++++
 src/channel-playback.h                       |   76 +
 src/channel-port.c                           |  361 ++++
 src/channel-port.h                           |   76 +
 src/channel-record.c                         |  482 +++++
 src/channel-record.h                         |   77 +
 src/channel-smartcard.c                      |  587 +++++
 src/channel-smartcard.h                      |   68 +
 src/channel-usbredir-priv.h                  |   61 +
 src/channel-usbredir.c                       |  686 ++++++
 src/channel-usbredir.h                       |   71 +
 src/channel-webdav.c                         |  613 ++++++
 src/channel-webdav.h                         |   68 +
 src/client_sw_canvas.c                       |   20 +
 src/client_sw_canvas.h                       |   25 +
 src/continuation.c                           |  102 +
 src/continuation.h                           |   61 +
 src/controller/Makefile.am                   |  100 +
 src/controller/controller.vala               |  286 +++
 src/controller/custom.h                      |   22 +
 src/controller/custom.vapi                   |   28 +
 src/controller/dump.c                        |  118 +
 src/controller/foreign-menu.vala             |  197 ++
 src/controller/gio-windows-2.0.vapi          |   30 +
 src/controller/menu.vala                     |  108 +
 src/controller/namedpipe.c                   |  270 +++
 src/controller/namedpipe.h                   |   59 +
 src/controller/namedpipeconnection.c         |  245 +++
 src/controller/namedpipeconnection.h         |   56 +
 src/controller/namedpipelistener.c           |  329 +++
 src/controller/namedpipelistener.h           |   70 +
 src/controller/spice-controller-listener.c   |  159 ++
 src/controller/spice-controller-listener.h   |   47 +
 src/controller/spice-foreign-menu-listener.c |  161 ++
 src/controller/spice-foreign-menu-listener.h |   47 +
 src/controller/test.c                        |  292 +++
 src/controller/util.vala                     |   42 +
 src/controller/win32-util.c                  |  161 ++
 src/controller/win32-util.h                  |   30 +
 src/coroutine.h                              |   83 +
 src/coroutine_gthread.c                      |  170 ++
 src/coroutine_ucontext.c                     |  150 ++
 src/coroutine_winfibers.c                    |  126 ++
 src/decode-glz-tmpl.c                        |  336 +++
 src/decode-glz.c                             |  475 ++++
 src/decode-jpeg.c                            |  191 ++
 src/decode-zlib.c                            |   89 +
 src/decode.h                                 |   44 +
 src/desktop-integration.c                    |  223 ++
 src/desktop-integration.h                    |   64 +
 src/gio-coroutine.c                          |  275 +++
 src/gio-coroutine.h                          |   66 +
 src/giopipe.c                                |  484 +++++
 src/giopipe.h                                |   29 +
 src/glib-compat.c                            |   79 +
 src/glib-compat.h                            |   68 +
 src/gtk-compat.h                             |   56 +
 src/keymap-gen.pl                            |  214 ++
 src/keymaps.csv                              |  490 +++++
 src/map-file                                 |  139 ++
 src/smartcard-manager-priv.h                 |   37 +
 src/smartcard-manager.c                      |  737 +++++++
 src/smartcard-manager.h                      |   80 +
 src/spice-audio-priv.h                       |   42 +
 src/spice-audio.c                            |  274 +++
 src/spice-audio.h                            |  109 +
 src/spice-channel-cache.h                    |  106 +
 src/spice-channel-enums.h                    |    7 +
 src/spice-channel-priv.h                     |  203 ++
 src/spice-channel.c                          | 2960 +++++++++++++++++++++++++
 src/spice-channel.h                          |  131 ++
 src/spice-client-glib-usb-acl-helper.c       |  372 ++++
 src/spice-client-gtk-manual.defs             |  117 +
 src/spice-client-gtk-module.c                |   45 +
 src/spice-client-gtk.override                |  171 ++
 src/spice-client.c                           |   27 +
 src/spice-client.h                           |   79 +
 src/spice-cmdline.c                          |   98 +
 src/spice-cmdline.h                          |   29 +
 src/spice-common.h                           |   36 +
 src/spice-glib-sym-file                      |  111 +
 src/spice-grabsequence.c                     |  163 ++
 src/spice-grabsequence.h                     |   61 +
 src/spice-gstaudio.c                         |  739 +++++++
 src/spice-gstaudio.h                         |   56 +
 src/spice-gtk-session-priv.h                 |   34 +
 src/spice-gtk-session.c                      | 1229 +++++++++++
 src/spice-gtk-session.h                      |   65 +
 src/spice-gtk-sym-file                       |   23 +
 src/spice-marshal.txt                        |   14 +
 src/spice-option.c                           |  284 +++
 src/spice-option.h                           |   31 +
 src/spice-pulse.c                            | 1354 ++++++++++++
 src/spice-pulse.h                            |   57 +
 src/spice-session-priv.h                     |  104 +
 src/spice-session.c                          | 2728 +++++++++++++++++++++++
 src/spice-session.h                          |  103 +
 src/spice-types.h                            |   35 +
 src/spice-uri-priv.h                         |   30 +
 src/spice-uri.c                              |  462 ++++
 src/spice-uri.h                              |   52 +
 src/spice-util-priv.h                        |   52 +
 src/spice-util.c                             |  497 +++++
 src/spice-util.h                             |   63 +
 src/spice-version.h.in                       |   70 +
 src/spice-widget-cairo.c                     |  160 ++
 src/spice-widget-priv.h                      |  141 ++
 src/spice-widget-x11.c                       |  280 +++
 src/spice-widget.c                           | 2642 +++++++++++++++++++++++
 src/spice-widget.h                           |   92 +
 src/spicy-screenshot.c                       |  196 ++
 src/spicy-stats.c                            |  144 ++
 src/spicy.c                                  | 1855 ++++++++++++++++
 src/usb-acl-helper.c                         |  299 +++
 src/usb-acl-helper.h                         |   72 +
 src/usb-device-manager-priv.h                |   48 +
 src/usb-device-manager.c                     | 1932 +++++++++++++++++
 src/usb-device-manager.h                     |  122 ++
 src/usb-device-widget.c                      |  554 +++++
 src/usb-device-widget.h                      |   81 +
 src/usbutil.c                                |  323 +++
 src/usbutil.h                                |   39 +
 src/vmcstream.c                              |  535 +++++
 src/vmcstream.h                              |   81 +
 src/vncdisplaykeymap.c                       |  323 +++
 src/vncdisplaykeymap.h                       |   36 +
 src/win-usb-clerk.h                          |   36 +
 src/win-usb-dev.c                            |  542 +++++
 src/win-usb-dev.h                            |  110 +
 src/win-usb-driver-install.c                 |  398 ++++
 src/win-usb-driver-install.h                 |  104 +
 src/wocky-http-proxy.c                       |  537 +++++
 src/wocky-http-proxy.h                       |   56 +
 tests/Makefile.am                            |    6 +-
 vapi/Makefile.am                             |    6 +-
 303 files changed, 44198 insertions(+), 44197 deletions(-)
 delete mode 100644 gtk/Makefile.am
 delete mode 100644 gtk/bio-gio.c
 delete mode 100644 gtk/bio-gio.h
 delete mode 100644 gtk/channel-base.c
 delete mode 100644 gtk/channel-cursor.c
 delete mode 100644 gtk/channel-cursor.h
 delete mode 100644 gtk/channel-display-mjpeg.c
 delete mode 100644 gtk/channel-display-priv.h
 delete mode 100644 gtk/channel-display.c
 delete mode 100644 gtk/channel-display.h
 delete mode 100644 gtk/channel-inputs.c
 delete mode 100644 gtk/channel-inputs.h
 delete mode 100644 gtk/channel-main.c
 delete mode 100644 gtk/channel-main.h
 delete mode 100644 gtk/channel-playback-priv.h
 delete mode 100644 gtk/channel-playback.c
 delete mode 100644 gtk/channel-playback.h
 delete mode 100644 gtk/channel-port.c
 delete mode 100644 gtk/channel-port.h
 delete mode 100644 gtk/channel-record.c
 delete mode 100644 gtk/channel-record.h
 delete mode 100644 gtk/channel-smartcard.c
 delete mode 100644 gtk/channel-smartcard.h
 delete mode 100644 gtk/channel-usbredir-priv.h
 delete mode 100644 gtk/channel-usbredir.c
 delete mode 100644 gtk/channel-usbredir.h
 delete mode 100644 gtk/channel-webdav.c
 delete mode 100644 gtk/channel-webdav.h
 delete mode 100644 gtk/client_sw_canvas.c
 delete mode 100644 gtk/client_sw_canvas.h
 delete mode 100644 gtk/continuation.c
 delete mode 100644 gtk/continuation.h
 delete mode 100644 gtk/controller/Makefile.am
 delete mode 100644 gtk/controller/controller.vala
 delete mode 100644 gtk/controller/custom.h
 delete mode 100644 gtk/controller/custom.vapi
 delete mode 100644 gtk/controller/dump.c
 delete mode 100644 gtk/controller/foreign-menu.vala
 delete mode 100644 gtk/controller/gio-windows-2.0.vapi
 delete mode 100644 gtk/controller/menu.vala
 delete mode 100644 gtk/controller/namedpipe.c
 delete mode 100644 gtk/controller/namedpipe.h
 delete mode 100644 gtk/controller/namedpipeconnection.c
 delete mode 100644 gtk/controller/namedpipeconnection.h
 delete mode 100644 gtk/controller/namedpipelistener.c
 delete mode 100644 gtk/controller/namedpipelistener.h
 delete mode 100644 gtk/controller/spice-controller-listener.c
 delete mode 100644 gtk/controller/spice-controller-listener.h
 delete mode 100644 gtk/controller/spice-foreign-menu-listener.c
 delete mode 100644 gtk/controller/spice-foreign-menu-listener.h
 delete mode 100644 gtk/controller/test.c
 delete mode 100644 gtk/controller/util.vala
 delete mode 100644 gtk/controller/win32-util.c
 delete mode 100644 gtk/controller/win32-util.h
 delete mode 100644 gtk/coroutine.h
 delete mode 100644 gtk/coroutine_gthread.c
 delete mode 100644 gtk/coroutine_ucontext.c
 delete mode 100644 gtk/coroutine_winfibers.c
 delete mode 100644 gtk/decode-glz-tmpl.c
 delete mode 100644 gtk/decode-glz.c
 delete mode 100644 gtk/decode-jpeg.c
 delete mode 100644 gtk/decode-zlib.c
 delete mode 100644 gtk/decode.h
 delete mode 100644 gtk/desktop-integration.c
 delete mode 100644 gtk/desktop-integration.h
 delete mode 100644 gtk/gio-coroutine.c
 delete mode 100644 gtk/gio-coroutine.h
 delete mode 100644 gtk/giopipe.c
 delete mode 100644 gtk/giopipe.h
 delete mode 100644 gtk/glib-compat.c
 delete mode 100644 gtk/glib-compat.h
 delete mode 100644 gtk/gtk-compat.h
 delete mode 100755 gtk/keymap-gen.pl
 delete mode 100644 gtk/keymaps.csv
 delete mode 100644 gtk/map-file
 delete mode 100644 gtk/smartcard-manager-priv.h
 delete mode 100644 gtk/smartcard-manager.c
 delete mode 100644 gtk/smartcard-manager.h
 delete mode 100644 gtk/spice-audio-priv.h
 delete mode 100644 gtk/spice-audio.c
 delete mode 100644 gtk/spice-audio.h
 delete mode 100644 gtk/spice-channel-cache.h
 delete mode 100644 gtk/spice-channel-enums.h
 delete mode 100644 gtk/spice-channel-priv.h
 delete mode 100644 gtk/spice-channel.c
 delete mode 100644 gtk/spice-channel.h
 delete mode 100644 gtk/spice-client-glib-usb-acl-helper.c
 delete mode 100644 gtk/spice-client-gtk-manual.defs
 delete mode 100644 gtk/spice-client-gtk-module.c
 delete mode 100644 gtk/spice-client-gtk.override
 delete mode 100644 gtk/spice-client.c
 delete mode 100644 gtk/spice-client.h
 delete mode 100644 gtk/spice-cmdline.c
 delete mode 100644 gtk/spice-cmdline.h
 delete mode 100644 gtk/spice-common.h
 delete mode 100644 gtk/spice-glib-sym-file
 delete mode 100644 gtk/spice-grabsequence.c
 delete mode 100644 gtk/spice-grabsequence.h
 delete mode 100644 gtk/spice-gstaudio.c
 delete mode 100644 gtk/spice-gstaudio.h
 delete mode 100644 gtk/spice-gtk-session-priv.h
 delete mode 100644 gtk/spice-gtk-session.c
 delete mode 100644 gtk/spice-gtk-session.h
 delete mode 100644 gtk/spice-gtk-sym-file
 delete mode 100644 gtk/spice-marshal.txt
 delete mode 100644 gtk/spice-option.c
 delete mode 100644 gtk/spice-option.h
 delete mode 100644 gtk/spice-pulse.c
 delete mode 100644 gtk/spice-pulse.h
 delete mode 100644 gtk/spice-session-priv.h
 delete mode 100644 gtk/spice-session.c
 delete mode 100644 gtk/spice-session.h
 delete mode 100644 gtk/spice-types.h
 delete mode 100644 gtk/spice-uri-priv.h
 delete mode 100644 gtk/spice-uri.c
 delete mode 100644 gtk/spice-uri.h
 delete mode 100644 gtk/spice-util-priv.h
 delete mode 100644 gtk/spice-util.c
 delete mode 100644 gtk/spice-util.h
 delete mode 100644 gtk/spice-version.h.in
 delete mode 100644 gtk/spice-widget-cairo.c
 delete mode 100644 gtk/spice-widget-priv.h
 delete mode 100644 gtk/spice-widget-x11.c
 delete mode 100644 gtk/spice-widget.c
 delete mode 100644 gtk/spice-widget.h
 delete mode 100644 gtk/spicy-screenshot.c
 delete mode 100644 gtk/spicy-stats.c
 delete mode 100644 gtk/spicy.c
 delete mode 100644 gtk/usb-acl-helper.c
 delete mode 100644 gtk/usb-acl-helper.h
 delete mode 100644 gtk/usb-device-manager-priv.h
 delete mode 100644 gtk/usb-device-manager.c
 delete mode 100644 gtk/usb-device-manager.h
 delete mode 100644 gtk/usb-device-widget.c
 delete mode 100644 gtk/usb-device-widget.h
 delete mode 100644 gtk/usbutil.c
 delete mode 100644 gtk/usbutil.h
 delete mode 100644 gtk/vmcstream.c
 delete mode 100644 gtk/vmcstream.h
 delete mode 100644 gtk/vncdisplaykeymap.c
 delete mode 100644 gtk/vncdisplaykeymap.h
 delete mode 100644 gtk/win-usb-clerk.h
 delete mode 100644 gtk/win-usb-dev.c
 delete mode 100644 gtk/win-usb-dev.h
 delete mode 100644 gtk/win-usb-driver-install.c
 delete mode 100644 gtk/win-usb-driver-install.h
 delete mode 100644 gtk/wocky-http-proxy.c
 delete mode 100644 gtk/wocky-http-proxy.h
 create mode 100644 src/Makefile.am
 create mode 100644 src/bio-gio.c
 create mode 100644 src/bio-gio.h
 create mode 100644 src/channel-base.c
 create mode 100644 src/channel-cursor.c
 create mode 100644 src/channel-cursor.h
 create mode 100644 src/channel-display-mjpeg.c
 create mode 100644 src/channel-display-priv.h
 create mode 100644 src/channel-display.c
 create mode 100644 src/channel-display.h
 create mode 100644 src/channel-inputs.c
 create mode 100644 src/channel-inputs.h
 create mode 100644 src/channel-main.c
 create mode 100644 src/channel-main.h
 create mode 100644 src/channel-playback-priv.h
 create mode 100644 src/channel-playback.c
 create mode 100644 src/channel-playback.h
 create mode 100644 src/channel-port.c
 create mode 100644 src/channel-port.h
 create mode 100644 src/channel-record.c
 create mode 100644 src/channel-record.h
 create mode 100644 src/channel-smartcard.c
 create mode 100644 src/channel-smartcard.h
 create mode 100644 src/channel-usbredir-priv.h
 create mode 100644 src/channel-usbredir.c
 create mode 100644 src/channel-usbredir.h
 create mode 100644 src/channel-webdav.c
 create mode 100644 src/channel-webdav.h
 create mode 100644 src/client_sw_canvas.c
 create mode 100644 src/client_sw_canvas.h
 create mode 100644 src/continuation.c
 create mode 100644 src/continuation.h
 create mode 100644 src/controller/Makefile.am
 create mode 100644 src/controller/controller.vala
 create mode 100644 src/controller/custom.h
 create mode 100644 src/controller/custom.vapi
 create mode 100644 src/controller/dump.c
 create mode 100644 src/controller/foreign-menu.vala
 create mode 100644 src/controller/gio-windows-2.0.vapi
 create mode 100644 src/controller/menu.vala
 create mode 100644 src/controller/namedpipe.c
 create mode 100644 src/controller/namedpipe.h
 create mode 100644 src/controller/namedpipeconnection.c
 create mode 100644 src/controller/namedpipeconnection.h
 create mode 100644 src/controller/namedpipelistener.c
 create mode 100644 src/controller/namedpipelistener.h
 create mode 100644 src/controller/spice-controller-listener.c
 create mode 100644 src/controller/spice-controller-listener.h
 create mode 100644 src/controller/spice-foreign-menu-listener.c
 create mode 100644 src/controller/spice-foreign-menu-listener.h
 create mode 100644 src/controller/test.c
 create mode 100644 src/controller/util.vala
 create mode 100644 src/controller/win32-util.c
 create mode 100644 src/controller/win32-util.h
 create mode 100644 src/coroutine.h
 create mode 100644 src/coroutine_gthread.c
 create mode 100644 src/coroutine_ucontext.c
 create mode 100644 src/coroutine_winfibers.c
 create mode 100644 src/decode-glz-tmpl.c
 create mode 100644 src/decode-glz.c
 create mode 100644 src/decode-jpeg.c
 create mode 100644 src/decode-zlib.c
 create mode 100644 src/decode.h
 create mode 100644 src/desktop-integration.c
 create mode 100644 src/desktop-integration.h
 create mode 100644 src/gio-coroutine.c
 create mode 100644 src/gio-coroutine.h
 create mode 100644 src/giopipe.c
 create mode 100644 src/giopipe.h
 create mode 100644 src/glib-compat.c
 create mode 100644 src/glib-compat.h
 create mode 100644 src/gtk-compat.h
 create mode 100755 src/keymap-gen.pl
 create mode 100644 src/keymaps.csv
 create mode 100644 src/map-file
 create mode 100644 src/smartcard-manager-priv.h
 create mode 100644 src/smartcard-manager.c
 create mode 100644 src/smartcard-manager.h
 create mode 100644 src/spice-audio-priv.h
 create mode 100644 src/spice-audio.c
 create mode 100644 src/spice-audio.h
 create mode 100644 src/spice-channel-cache.h
 create mode 100644 src/spice-channel-enums.h
 create mode 100644 src/spice-channel-priv.h
 create mode 100644 src/spice-channel.c
 create mode 100644 src/spice-channel.h
 create mode 100644 src/spice-client-glib-usb-acl-helper.c
 create mode 100644 src/spice-client-gtk-manual.defs
 create mode 100644 src/spice-client-gtk-module.c
 create mode 100644 src/spice-client-gtk.override
 create mode 100644 src/spice-client.c
 create mode 100644 src/spice-client.h
 create mode 100644 src/spice-cmdline.c
 create mode 100644 src/spice-cmdline.h
 create mode 100644 src/spice-common.h
 create mode 100644 src/spice-glib-sym-file
 create mode 100644 src/spice-grabsequence.c
 create mode 100644 src/spice-grabsequence.h
 create mode 100644 src/spice-gstaudio.c
 create mode 100644 src/spice-gstaudio.h
 create mode 100644 src/spice-gtk-session-priv.h
 create mode 100644 src/spice-gtk-session.c
 create mode 100644 src/spice-gtk-session.h
 create mode 100644 src/spice-gtk-sym-file
 create mode 100644 src/spice-marshal.txt
 create mode 100644 src/spice-option.c
 create mode 100644 src/spice-option.h
 create mode 100644 src/spice-pulse.c
 create mode 100644 src/spice-pulse.h
 create mode 100644 src/spice-session-priv.h
 create mode 100644 src/spice-session.c
 create mode 100644 src/spice-session.h
 create mode 100644 src/spice-types.h
 create mode 100644 src/spice-uri-priv.h
 create mode 100644 src/spice-uri.c
 create mode 100644 src/spice-uri.h
 create mode 100644 src/spice-util-priv.h
 create mode 100644 src/spice-util.c
 create mode 100644 src/spice-util.h
 create mode 100644 src/spice-version.h.in
 create mode 100644 src/spice-widget-cairo.c
 create mode 100644 src/spice-widget-priv.h
 create mode 100644 src/spice-widget-x11.c
 create mode 100644 src/spice-widget.c
 create mode 100644 src/spice-widget.h
 create mode 100644 src/spicy-screenshot.c
 create mode 100644 src/spicy-stats.c
 create mode 100644 src/spicy.c
 create mode 100644 src/usb-acl-helper.c
 create mode 100644 src/usb-acl-helper.h
 create mode 100644 src/usb-device-manager-priv.h
 create mode 100644 src/usb-device-manager.c
 create mode 100644 src/usb-device-manager.h
 create mode 100644 src/usb-device-widget.c
 create mode 100644 src/usb-device-widget.h
 create mode 100644 src/usbutil.c
 create mode 100644 src/usbutil.h
 create mode 100644 src/vmcstream.c
 create mode 100644 src/vmcstream.h
 create mode 100644 src/vncdisplaykeymap.c
 create mode 100644 src/vncdisplaykeymap.h
 create mode 100644 src/win-usb-clerk.h
 create mode 100644 src/win-usb-dev.c
 create mode 100644 src/win-usb-dev.h
 create mode 100644 src/win-usb-driver-install.c
 create mode 100644 src/win-usb-driver-install.h
 create mode 100644 src/wocky-http-proxy.c
 create mode 100644 src/wocky-http-proxy.h

diff --git a/Makefile.am b/Makefile.am
index e559c4d..3d7a174 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,7 +1,7 @@
 ACLOCAL_AMFLAGS = -I m4
 NULL =
 
-SUBDIRS = spice-common gtk man po doc data
+SUBDIRS = spice-common src man po doc data
 
 if BUILD_TESTS
 SUBDIRS += tests
diff --git a/configure.ac b/configure.ac
index cf5a039..1d8f4d0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -88,7 +88,7 @@ dnl =========================================================================
 dnl Chek optional features
 
 srcdir="$(dirname $0)"
-if test ! -e "$srcdir/gtk/vncdisplaykeymap_osx2xtkbd.c"; then
+if test ! -e "$srcdir/src/vncdisplaykeymap_osx2xtkbd.c"; then
   AC_MSG_CHECKING([for Text::CSV Perl module])
   perl -MText::CSV -e "" >/dev/null 2>&1
   if test $? -ne 0 ; then
@@ -708,7 +708,7 @@ dnl ===========================================================================
 dnl check compiler flags
 
 # We want to enable these, but need to sort out the
-# decl mess with  gtk/generated_*.c
+# decl mess with  src/generated_*.c
 dontwarn="-Wmissing-prototypes -Wmissing-declarations"
 
 # We want to enable these, but Gtk+2.0 has a bad decl
@@ -746,9 +746,9 @@ spice-controller.pc
 data/Makefile
 data/spicy.nsis
 po/Makefile.in
-gtk/Makefile
-gtk/spice-version.h
-gtk/controller/Makefile
+src/Makefile
+src/spice-version.h
+src/controller/Makefile
 doc/Makefile
 doc/reference/Makefile
 man/Makefile
diff --git a/doc/reference/Makefile.am b/doc/reference/Makefile.am
index 76c7d34..0e4d5b3 100644
--- a/doc/reference/Makefile.am
+++ b/doc/reference/Makefile.am
@@ -7,7 +7,7 @@ DOC_MODULE = spice-gtk
 DOC_MAIN_SGML_FILE = $(DOC_MODULE)-docs.xml
 
 # Source code location
-DOC_SOURCE_DIR = $(top_srcdir)/gtk
+DOC_SOURCE_DIR = $(top_srcdir)/src
 
 # Extra options to supply to gtkdoc-scan.
 SCAN_OPTIONS = \
@@ -18,8 +18,8 @@ SCAN_OPTIONS = \
 MKDB_OPTIONS = --xml-mode --output-format=xml
 
 # Used for dependencies. The docs will be rebuilt if any of these change.
-HFILE_GLOB = $(top_srcdir)/gtk/*.h
-CFILE_GLOB = $(top_srcdir)/gtk/*.c
+HFILE_GLOB = $(top_srcdir)/src/*.h
+CFILE_GLOB = $(top_srcdir)/src/*.c
 
 # Header files to ignore when scanning. Use base file name, no paths
 IGNORE_HFILES=					\
@@ -54,8 +54,8 @@ IGNORE_HFILES=					\
 	$(NULL)
 
 # CFLAGS and LDFLAGS for compiling gtkdoc-scangobj with your library.
-GTKDOC_CFLAGS = -I$(top_srcdir) -I$(top_builddir) -I$(top_srcdir)/gtk -I$(top_builddir)/gtk $(SPICE_GLIB_CFLAGS) $(SPICE_GTK_CFLAGS) $(COMMON_CFLAGS)
-GTKDOC_LIBS = $(top_builddir)/gtk/libspice-client-glib-2.0.la $(top_builddir)/gtk/libspice-client-gtk-$(SPICE_GTK_API_VERSION).la
+GTKDOC_CFLAGS = -I$(top_srcdir) -I$(top_builddir) -I$(top_srcdir)/src -I$(top_builddir)/src $(SPICE_GLIB_CFLAGS) $(SPICE_GTK_CFLAGS) $(COMMON_CFLAGS)
+GTKDOC_LIBS = $(top_builddir)/src/libspice-client-glib-2.0.la $(top_builddir)/src/libspice-client-gtk-$(SPICE_GTK_API_VERSION).la
 
 include $(top_srcdir)/gtk-doc.make
 
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
deleted file mode 100644
index 25e2255..0000000
--- a/gtk/Makefile.am
+++ /dev/null
@@ -1,703 +0,0 @@
-NULL =
-SUBDIRS =
-
-if WITH_CONTROLLER
-SUBDIRS += controller
-endif
-
-# Avoid need for perl(Text::CSV) by end users
-KEYMAPS =					\
-	vncdisplaykeymap_xorgevdev2xtkbd.c	\
-	vncdisplaykeymap_xorgkbd2xtkbd.c	\
-	vncdisplaykeymap_xorgxquartz2xtkbd.c	\
-	vncdisplaykeymap_xorgxwin2xtkbd.c	\
-	vncdisplaykeymap_osx2xtkbd.c		\
-	vncdisplaykeymap_win322xtkbd.c		\
-	vncdisplaykeymap_x112xtkbd.c		\
-	$(NULL)
-
-# End users build dependencies can be cleaned
-GLIBGENS =					\
-	spice-glib-enums.c			\
-	spice-glib-enums.h			\
-	spice-marshal.c				\
-	spice-marshal.h				\
-	spice-widget-enums.c			\
-	spice-widget-enums.h			\
-	$(NULL)
-
-CLEANFILES = $(GLIBGENS)
-BUILT_SOURCES = $(GLIBGENS) $(KEYMAPS)
-
-EXTRA_DIST =					\
-	$(KEYMAPS)				\
-	decode-glz-tmpl.c			\
-	keymap-gen.pl				\
-	keymaps.csv				\
-	map-file				\
-	spice-glib-sym-file			\
-	spice-gtk-sym-file			\
-	spice-client-gtk-manual.defs		\
-	spice-client-gtk.override		\
-	spice-marshal.txt			\
-	spice-version.h.in			\
-	$(NULL)
-
-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
-endif
-
-lib_LTLIBRARIES = libspice-client-glib-2.0.la
-
-if WITH_GTK
-if HAVE_GTK_2
-lib_LTLIBRARIES += libspice-client-gtk-2.0.la
-else
-lib_LTLIBRARIES += libspice-client-gtk-3.0.la
-endif
-endif
-
-if HAVE_LD_VERSION_SCRIPT
-GLIB_SYMBOLS_LDFLAGS = -Wl,--version-script=${srcdir}/map-file
-GLIB_SYMBOLS_FILE = map-file
-GTK_SYMBOLS_LDFLAGS = $(GLIB_SYMBOLS_LDFLAGS)
-GTK_SYMBOLS_FILE = $(GLIB_SYMBOLS_FILE)
-else
-GLIB_SYMBOLS_LDFLAGS = -export-symbols ${srcdir}/spice-glib-sym-file
-GLIB_SYMBOLS_FILE = spice-glib-sym-file
-GTK_SYMBOLS_LDFLAGS = -export-symbols ${srcdir}/spice-gtk-sym-file
-GTK_SYMBOLS_FILE = spice-gtk-sym-file
-endif
-
-KEYMAP_GEN = $(srcdir)/keymap-gen.pl
-
-SPICE_COMMON_CPPFLAGS =						\
-	-DG_LOG_DOMAIN=\"GSpice\"				\
-	-DSPICE_NO_DEPRECATED					\
-	-DSPICE_GTK_LOCALEDIR=\"${SPICE_GTK_LOCALEDIR}\"	\
-	-DPNP_IDS=\""$(PNP_IDS)"\"				\
-	-DUSB_IDS=\""$(USB_IDS)"\"				\
-	-DSPICE_DISABLE_ABORT					\
-	-I$(top_srcdir)						\
-	$(COMMON_CFLAGS)					\
-	$(PIXMAN_CFLAGS)					\
-	$(PULSE_CFLAGS)						\
-	$(GTK_CFLAGS)						\
-	$(CAIRO_CFLAGS)						\
-	$(GLIB2_CFLAGS)						\
-	$(GIO_CFLAGS)						\
-	$(GOBJECT2_CFLAGS)					\
-	$(SSL_CFLAGS)						\
-	$(SASL_CFLAGS)						\
-	$(GST_CFLAGS)						\
-	$(SMARTCARD_CFLAGS)					\
-	$(USBREDIR_CFLAGS)					\
-	$(GUDEV_CFLAGS)						\
-	$(SOUP_CFLAGS)						\
-	$(PHODAV_CFLAGS)					\
-	$(LZ4_CFLAGS)					\
-	$(NULL)
-
-AM_CPPFLAGS =					\
-	$(SPICE_COMMON_CPPFLAGS)		\
-	$(SPICE_CFLAGS)				\
-	$(NULL)
-
-# http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html
-SPICE_GTK_LDFLAGS_COMMON =		\
-	-version-info 4:0:0		\
-	-no-undefined			\
-	$(GTK_SYMBOLS_LDFLAGS)		\
-	$(NULL)
-
-SPICE_GTK_LIBADD_COMMON =		\
-	libspice-client-glib-2.0.la	\
-	$(GTK_LIBS)			\
-	$(CAIRO_LIBS)			\
-	$(XRANDR_LIBS)			\
-	$(LIBM)				\
-	$(NULL)
-
-SPICE_GTK_SOURCES_COMMON =		\
-	glib-compat.h			\
-	gtk-compat.h			\
-	spice-util.c			\
-	spice-util-priv.h		\
-	spice-gtk-session.c		\
-	spice-gtk-session-priv.h	\
-	spice-widget.c			\
-	spice-widget-priv.h		\
-	vncdisplaykeymap.c		\
-	vncdisplaykeymap.h		\
-	spice-grabsequence.c		\
-	spice-grabsequence.h		\
-	desktop-integration.c		\
-	desktop-integration.h		\
-	usb-device-widget.c		\
-	$(NULL)
-
-nodist_SPICE_GTK_SOURCES_COMMON =	\
-	spice-widget-enums.c		\
-	spice-marshal.c			\
-	$(NULL)
-
-if WITH_X11
-SPICE_GTK_SOURCES_COMMON +=		\
-	spice-widget-x11.c		\
-	$(NULL)
-else
-SPICE_GTK_SOURCES_COMMON +=		\
-	spice-widget-cairo.c		\
-	$(NULL)
-endif
-
-if WITH_GTK
-if HAVE_GTK_2
-libspice_client_gtk_2_0_la_DEPEDENCIES = $(GTK_SYMBOLS_FILE)
-libspice_client_gtk_2_0_la_LDFLAGS = $(SPICE_GTK_LDFLAGS_COMMON)
-libspice_client_gtk_2_0_la_LIBADD = $(SPICE_GTK_LIBADD_COMMON)
-libspice_client_gtk_2_0_la_SOURCES = $(SPICE_GTK_SOURCES_COMMON)
-nodist_libspice_client_gtk_2_0_la_SOURCES = $(nodist_SPICE_GTK_SOURCES_COMMON)
-else
-libspice_client_gtk_3_0_la_DEPEDENCIES = $(GTK_SYMBOLS_FILE)
-libspice_client_gtk_3_0_la_LDFLAGS = $(SPICE_GTK_LDFLAGS_COMMON)
-libspice_client_gtk_3_0_la_LIBADD = $(SPICE_GTK_LIBADD_COMMON)
-libspice_client_gtk_3_0_la_SOURCES = $(SPICE_GTK_SOURCES_COMMON)
-nodist_libspice_client_gtk_3_0_la_SOURCES = $(nodist_SPICE_GTK_SOURCES_COMMON)
-endif
-
-libspice_client_gtkincludedir = $(includedir)/spice-client-gtk-$(SPICE_GTK_API_VERSION)
-libspice_client_gtkinclude_HEADERS =	\
-	spice-gtk-session.h		\
-	spice-widget.h			\
-	spice-grabsequence.h		\
-	usb-device-widget.h		\
-	$(NULL)
-
-nodist_libspice_client_gtkinclude_HEADERS =	\
-	spice-widget-enums.h			\
-	$(NULL)
-endif
-
-libspice_client_glib_2_0_la_DEPENDENCIES = $(GLIB_SYMBOLS_FILE)
-
-libspice_client_glib_2_0_la_LDFLAGS =	\
-	-version-info 13:0:5		\
-	-no-undefined			\
-	$(GLIB_SYMBOLS_LDFLAGS)		\
-	$(NULL)
-
-libspice_client_glib_2_0_la_LIBADD =					\
-	$(top_builddir)/spice-common/common/libspice-common.la		\
-	$(top_builddir)/spice-common/common/libspice-common-client.la	\
-	$(GLIB2_LIBS)							\
-	$(SOUP_LIBS)							\
-	$(GIO_LIBS)							\
-	$(GOBJECT2_LIBS)						\
-	$(JPEG_LIBS)							\
-	$(Z_LIBS)							\
-	$(LZ4_LIBS)							\
-	$(PIXMAN_LIBS)							\
-	$(SSL_LIBS)							\
-	$(PULSE_LIBS)							\
-	$(GST_LIBS)							\
-	$(SASL_LIBS)							\
-	$(SMARTCARD_LIBS)						\
-	$(USBREDIR_LIBS)						\
-	$(GUDEV_LIBS)							\
-	$(PHODAV_LIBS)							\
-	$(NULL)
-
-if WITH_POLKIT
-USB_ACL_HELPER_SRCS =				\
-	usb-acl-helper.c			\
-	usb-acl-helper.h			\
-	$(NULL)
-AM_CPPFLAGS += -DACL_HELPER_PATH="\"$(ACL_HELPER_DIR)\""
-else
-USB_ACL_HELPER_SRCS =
-endif
-
-libspice_client_glib_2_0_la_SOURCES =			\
-	bio-gio.c					\
-	bio-gio.h					\
-	glib-compat.c					\
-	glib-compat.h					\
-	spice-audio.c					\
-	spice-audio-priv.h				\
-	spice-common.h					\
-	spice-util.c					\
-	spice-util-priv.h				\
-	spice-option.h					\
-	spice-option.c					\
-							\
-	spice-client.c					\
-	spice-session.c					\
-	spice-session-priv.h				\
-	spice-channel.c					\
-	spice-channel-cache.h				\
-	spice-channel-priv.h				\
-	coroutine.h					\
-	gio-coroutine.c					\
-	gio-coroutine.h					\
-							\
-	channel-base.c					\
-	channel-webdav.c				\
-	channel-cursor.c				\
-	channel-display.c				\
-	channel-display-priv.h				\
-	channel-display-mjpeg.c				\
-	channel-inputs.c				\
-	channel-main.c					\
-	channel-playback.c				\
-	channel-playback-priv.h				\
-	channel-port.c					\
-	channel-record.c				\
-	channel-smartcard.c				\
-	channel-usbredir.c				\
-	channel-usbredir-priv.h				\
-	smartcard-manager.c				\
-	smartcard-manager-priv.h			\
-	spice-uri.c					\
-	spice-uri-priv.h				\
-	usb-device-manager.c				\
-	usb-device-manager-priv.h			\
-	usbutil.c					\
-	usbutil.h					\
-	$(USB_ACL_HELPER_SRCS)				\
-	vmcstream.c					\
-	vmcstream.h					\
-	wocky-http-proxy.c				\
-	wocky-http-proxy.h				\
-							\
-	decode.h					\
-	decode-glz.c					\
-	decode-jpeg.c					\
-	decode-zlib.c					\
-							\
-	client_sw_canvas.c	\
-	client_sw_canvas.h	\
-	$(NULL)
-
-nodist_libspice_client_glib_2_0_la_SOURCES =	\
-	spice-glib-enums.c			\
-	spice-marshal.c				\
-	spice-marshal.h				\
-	$(NULL)
-
-libspice_client_glibincludedir = $(includedir)/spice-client-glib-2.0
-libspice_client_glibinclude_HEADERS =	\
-	spice-audio.h			\
-	spice-client.h			\
-	spice-uri.h			\
-	spice-types.h			\
-	spice-session.h			\
-	spice-channel.h			\
-	spice-util.h			\
-	spice-option.h			\
-	spice-version.h			\
-	channel-cursor.h		\
-	channel-display.h		\
-	channel-inputs.h		\
-	channel-main.h			\
-	channel-playback.h		\
-	channel-port.h			\
-	channel-record.h		\
-	channel-smartcard.h		\
-	channel-usbredir.h		\
-	channel-webdav.h		\
-	usb-device-manager.h		\
-	smartcard-manager.h		\
-	$(NULL)
-
-nodist_libspice_client_glibinclude_HEADERS =	\
-	spice-glib-enums.h			\
-	$(NULL)
-
-# file for API compatibility, but we don't want warning during our compilation
-dist_libspice_client_glibinclude_DATA =	\
-	spice-channel-enums.h		\
-	$(NULL)
-
-if WITH_PULSE
-libspice_client_glib_2_0_la_SOURCES +=	\
-	spice-pulse.c			\
-	spice-pulse.h			\
-	$(NULL)
-endif
-
-if WITH_GSTAUDIO
-libspice_client_glib_2_0_la_SOURCES +=	\
-	spice-gstaudio.c		\
-	spice-gstaudio.h		\
-	$(NULL)
-endif
-
-if WITH_PHODAV
-libspice_client_glib_2_0_la_SOURCES +=	\
-	giopipe.c			\
-	giopipe.h			\
-	$(NULL)
-endif
-
-if WITH_UCONTEXT
-libspice_client_glib_2_0_la_SOURCES += continuation.h continuation.c coroutine_ucontext.c
-endif
-
-if WITH_WINFIBER
-libspice_client_glib_2_0_la_SOURCES += coroutine_winfibers.c
-endif
-
-if WITH_GTHREAD
-libspice_client_glib_2_0_la_SOURCES += coroutine_gthread.c
-libspice_client_glib_2_0_la_LIBADD += $(GTHREAD_LIBS)
-endif
-
-
-WIN_USB_FILES= \
-	win-usb-dev.h			\
-	win-usb-dev.c			\
-	win-usb-clerk.h			\
-	win-usb-driver-install.h	\
-	win-usb-driver-install.c	\
-	$(NULL)
-
-if OS_WIN32
-if WITH_USBREDIR
-libspice_client_glib_2_0_la_SOURCES += \
-	$(WIN_USB_FILES)
-endif
-libspice_client_glib_2_0_la_LIBADD += -lws2_32 -lgdi32
-endif
-
-spicy_SOURCES =					\
-	spicy.c					\
-	spice-cmdline.h				\
-	spice-cmdline.c				\
-	$(NULL)
-
-spicy_LDADD =						\
-	libspice-client-gtk-$(SPICE_GTK_API_VERSION).la	\
-	libspice-client-glib-2.0.la			\
-	$(XRANDR_LIBS)					\
-	$(GTHREAD_LIBS)					\
-	$(GTK_LIBS)					\
-	$(LIBM)						\
-	$(NULL)
-
-spicy_CPPFLAGS =			\
-	$(AM_CPPFLAGS)			\
-	$(XRANDR_CFLAGS)		\
-	$(GTHREAD_CFLAGS)		\
-	-DSPICE_DISABLE_DEPRECATED	\
-	$(NULL)
-
-
-if WITH_POLKIT
-spice_client_glib_usb_acl_helper_SOURCES =	\
-	glib-compat.c				\
-	glib-compat.h				\
-	spice-client-glib-usb-acl-helper.c	\
-	$(NULL)
-
-spice_client_glib_usb_acl_helper_LDADD =	\
-	$(GLIB2_LIBS)				\
-	$(GIO_LIBS)				\
-	$(POLKIT_LIBS)				\
-	$(ACL_LIBS)				\
-	$(PIE_LDFLAGS)				\
-	$(NULL)
-
-spice_client_glib_usb_acl_helper_CPPFLAGS =	\
-	$(SPICE_CFLAGS)				\
-	$(GLIB2_CFLAGS)				\
-	$(GIO_CFLAGS)				\
-	$(POLKIT_CFLAGS)			\
-	$(PIE_CFLAGS)				\
-	$(NULL)
-
-install-data-hook:
-	-chown root $(DESTDIR)$(acldir)/spice-client-glib-usb-acl-helper
-	-chmod u+s  $(DESTDIR)$(acldir)/spice-client-glib-usb-acl-helper
-
-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
-if HAVE_GTK_2
-$(libspice_client_gtk_2_0_la_SOURCES): spice-glib-enums.h spice-widget-enums.h
-else
-$(libspice_client_gtk_3_0_la_SOURCES): spice-glib-enums.h spice-widget-enums.h
-endif
-endif
-
-spice-marshal.c: spice-marshal.h
-spice-glib-enums.c: spice-glib-enums.h
-spice-widget-enums.c: spice-widget-enums.h
-
-spice-marshal.c: spice-marshal.txt
-	$(AM_V_GEN)echo "#include \"config.h\"" > $@ && \
-		echo "#include \"spice-marshal.h\"" > $@ && \
-		glib-genmarshal --body $< >> $@ || (rm -f $@ && exit 1)
-
-spice-marshal.h: spice-marshal.txt
-	$(AM_V_GEN)glib-genmarshal --header $< > $@ || (rm -f $@ && exit 1)
-
-spice-glib-enums.c: spice-channel.h channel-inputs.h spice-session.h
-	$(AM_V_GEN)glib-mkenums --fhead "#include \"config.h\"\n\n" \
-			--fhead "#include <glib-object.h>\n" \
-			--fhead "#include \"spice-glib-enums.h\"\n\n" \
-			--fprod "\n#include \"spice-session.h\"\n" \
-			--fprod "\n#include \"spice-channel.h\"\n" \
-			--fprod "\n#include \"channel-inputs.h\"\n" \
-			--vhead "static const G at Type@Value _ at enum_name@_values[] = {" \
-			--vprod "  { @VALUENAME@, \"@VALUENAME@\", \"@valuenick@\" }," \
-			--vtail "  { 0, NULL, NULL }\n};\n\n" \
-			--vtail "GType\n at enum_name@_get_type (void)\n{\n" \
-			--vtail "  static GType type = 0;\n" \
-			--vtail "  static volatile gsize type_volatile = 0;\n\n" \
-			--vtail "  if (g_once_init_enter(&type_volatile)) {\n" \
-			--vtail "    type = g_ at type@_register_static (\"@EnumName@\", _ at enum_name@_values);\n" \
-			--vtail "    g_once_init_leave(&type_volatile, type);\n" \
-			--vtail "  }\n\n" \
-			--vtail "  return type;\n}\n\n" \
-		$^ > $@
-
-spice-glib-enums.h: spice-channel.h channel-inputs.h spice-session.h
-	$(AM_V_GEN)glib-mkenums --fhead "#ifndef SPICE_GLIB_ENUMS_H\n" \
-			--fhead "#define SPICE_GLIB_ENUMS_H\n\n" \
-			--fhead "G_BEGIN_DECLS\n\n" \
-			--ftail "G_END_DECLS\n\n" \
-			--ftail "#endif /* SPICE_CHANNEL_ENUMS_H */\n" \
-			--eprod "#define SPICE_TYPE_ at ENUMSHORT@ @enum_name at _get_type()\n" \
-			--eprod "GType @enum_name at _get_type (void);\n" \
-		$^ >  $@
-
-spice-widget-enums.c: spice-widget.h
-	$(AM_V_GEN)glib-mkenums --fhead "#include \"config.h\"\n\n" \
-			--fhead "#include <glib-object.h>\n" \
-			--fhead "#include \"spice-widget-enums.h\"\n\n" \
-			--fprod "\n#include \"spice-widget.h\"\n" \
-			--vhead "static const G at Type@Value _ at enum_name@_values[] = {" \
-			--vprod "  { @VALUENAME@, \"@VALUENAME@\", \"@valuenick@\" }," \
-			--vtail "  { 0, NULL, NULL }\n};\n\n" \
-			--vtail "GType\n at enum_name@_get_type (void)\n{\n" \
-			--vtail "  static GType type = 0;\n" \
-			--vtail "  static volatile gsize type_volatile = 0;\n\n" \
-			--vtail "  if (g_once_init_enter(&type_volatile)) {\n" \
-			--vtail "    type = g_ at type@_register_static (\"@EnumName@\", _ at enum_name@_values);\n" \
-			--vtail "    g_once_init_leave(&type_volatile, type);\n" \
-			--vtail "  }\n\n" \
-			--vtail "  return type;\n}\n\n" \
-		$< > $@
-
-spice-widget-enums.h: spice-widget.h
-	$(AM_V_GEN)glib-mkenums --fhead "#ifndef SPICE_WIDGET_ENUMS_H\n" \
-			--fhead "#define SPICE_WIDGET_ENUMS_H\n\n" \
-			--fhead "G_BEGIN_DECLS\n\n" \
-			--ftail "G_END_DECLS\n\n" \
-			--ftail "#endif /* SPICE_WIDGET_ENUMS_H */\n" \
-			--eprod "#define SPICE_TYPE_ at ENUMSHORT@ @enum_name at _get_type()\n" \
-			--eprod "GType @enum_name at _get_type (void);\n" \
-		$< >  $@
-
-
-vncdisplaykeymap.c: $(KEYMAPS)
-
-$(KEYMAPS): $(KEYMAP_GEN) keymaps.csv
-
-# Note despite being autogenerated these are not part of CLEANFILES, they
-# are actually a part of EXTRA_DIST to avoid the need for perl(Text::CSV) by
-# end users
-vncdisplaykeymap_xorgevdev2xtkbd.c:
-	$(AM_V_GEN)$(KEYMAP_GEN) $(srcdir)/keymaps.csv xorgevdev xtkbd > $@ || rm $@
-
-vncdisplaykeymap_xorgkbd2xtkbd.c:
-	$(AM_V_GEN)$(KEYMAP_GEN) $(srcdir)/keymaps.csv xorgkbd xtkbd > $@ || rm $@
-
-vncdisplaykeymap_xorgxquartz2xtkbd.c:
-	$(AM_V_GEN)$(KEYMAP_GEN) $(srcdir)/keymaps.csv xorgxquartz xtkbd > $@ || rm $@
-
-vncdisplaykeymap_xorgxwin2xtkbd.c:
-	$(AM_V_GEN)$(KEYMAP_GEN) $(srcdir)/keymaps.csv xorgxwin xtkbd > $@ || rm $@
-
-vncdisplaykeymap_osx2xtkbd.c:
-	$(AM_V_GEN)$(KEYMAP_GEN) $(srcdir)/keymaps.csv osx xtkbd > $@ || rm $@
-
-vncdisplaykeymap_win322xtkbd.c:
-	$(AM_V_GEN)$(KEYMAP_GEN) $(srcdir)/keymaps.csv win32 xtkbd > $@ || rm $@
-
-vncdisplaykeymap_x112xtkbd.c:
-	$(AM_V_GEN)$(KEYMAP_GEN) $(srcdir)/keymaps.csv x11 xtkbd > $@ || rm $@
-
-if WITH_PYTHON
-pyexec_LTLIBRARIES = SpiceClientGtk.la
-
-# workaround for broken parallel install support in automake with LTLIBRARIES
-# http://debbugs.gnu.org/cgi/bugreport.cgi?bug=7328
-install_pyexecLTLIBRARIES = install-pyexecLTLIBRARIES
-$(install_pyexecLTLIBRARIES): install-libLTLIBRARIES
-
-SpiceClientGtk_la_LIBADD = libspice-client-gtk-2.0.la libspice-client-glib-2.0.la $(PYGTK_LIBS)
-SpiceClientGtk_la_CFLAGS = $(GTK_CFLAGS) $(PYTHON_INCLUDES) $(PYGTK_CFLAGS) $(WARN_PYFLAGS)
-SpiceClientGtk_la_LDFLAGS = -module -avoid-version -fPIC
-SpiceClientGtk_la_SOURCES = spice-client-gtk-module.c
-nodist_SpiceClientGtk_la_SOURCES = spice-client-gtk-module.defs.c
-
-CODEGENDIR = `pkg-config --variable=codegendir pygtk-2.0`
-DEFSDIR = `pkg-config --variable=defsdir pygtk-2.0`
-
-spice-client-gtk.defs: $(libspice_client_gtkinclude_HEADERS) $(nodist_libspice_client_gtkinclude_HEADERS) $(libspice_client_glibinclude_HEADERS) $(nodist_libspice_client_glibinclude_HEADERS)
-	$(AM_V_GEN)$(PYTHON) $(CODEGENDIR)/h2def.py \
-		-f $(srcdir)/spice-client-gtk-manual.defs \
-		$^ > $@
-
-spice-client-gtk-module.defs.c: spice-client-gtk.override spice-client-gtk.defs spice-client-gtk-manual.defs
-	@cat spice-client-gtk.defs $(srcdir)/spice-client-gtk-manual.defs > tmp.defs
-	$(AM_V_GEN)pygobject-codegen-2.0 --prefix spice \
-			  --register $(DEFSDIR)/gdk-types.defs \
-			  --register $(DEFSDIR)/gtk-types.defs \
-			  --override $(srcdir)/spice-client-gtk.override \
-			  tmp.defs > $@
-	@rm tmp.defs
-
-CLEANFILES += spice-client-gtk-module.defs.c spice-client-gtk.defs
-endif
-
--include $(INTROSPECTION_MAKEFILE)
-
-if G_IR_SCANNER_SYMBOL_PREFIX
-PREFIX_ARGS = --symbol-prefix=spice --identifier-prefix=Spice
-else
-PREFIX_ARGS = --strip-prefix=Spice
-endif
-
-INTROSPECTION_GIRS =
-INTROSPECTION_SCANNER_ARGS = --warn-all --accept-unprefixed --add-include-path=$(builddir) $(PREFIX_ARGS)
-INTROSPECTION_COMPILER_ARGS = --includedir=$(builddir)
-
-if HAVE_INTROSPECTION
-glib_introspection_files =				\
-	$(libspice_client_glibinclude_HEADERS)		\
-	$(nodist_libspice_client_glibinclude_HEADERS)	\
-	spice-audio.c					\
-	spice-client.c					\
-	spice-session.c					\
-	spice-channel.c					\
-	spice-glib-enums.c				\
-	spice-option.c					\
-	spice-util.c					\
-	channel-webdav.c				\
-	channel-cursor.c				\
-	channel-display.c				\
-	channel-inputs.c				\
-	channel-main.c					\
-	channel-playback.c				\
-	channel-port.c					\
-	channel-record.c				\
-	channel-smartcard.c				\
-	channel-usbredir.c				\
-	smartcard-manager.c				\
-	usb-device-manager.c				\
-	$(NULL)
-
-gtk_introspection_files =				\
-	$(libspice_client_gtkinclude_HEADERS)		\
-	$(nodist_libspice_client_gtkinclude_HEADERS)	\
-	spice-gtk-session.c				\
-	spice-widget.c					\
-	spice-grabsequence.c				\
-	usb-device-widget.c				\
-	$(NULL)
-
-SpiceClientGLib-2.0.gir: libspice-client-glib-2.0.la
-SpiceClientGLib_2_0_gir_INCLUDES = GObject-2.0 Gio-2.0
-SpiceClientGLib_2_0_gir_CFLAGS = $(SPICE_COMMON_CPPFLAGS)
-SpiceClientGLib_2_0_gir_LIBS = libspice-client-glib-2.0.la
-SpiceClientGLib_2_0_gir_FILES = $(glib_introspection_files)
-SpiceClientGLib_2_0_gir_EXPORT_PACKAGES = spice-client-glib-2.0
-SpiceClientGLib_2_0_gir_SCANNERFLAGS = --c-include="spice-client.h"
-INTROSPECTION_GIRS += SpiceClientGLib-2.0.gir
-
-if WITH_GTK
-if HAVE_GTK_2
-SpiceClientGtk-2.0.gir: libspice-client-gtk-2.0.la SpiceClientGLib-2.0.gir
-SpiceClientGtk_2_0_gir_INCLUDES = GObject-2.0 Gtk-2.0 SpiceClientGLib-2.0
-SpiceClientGtk_2_0_gir_CFLAGS = $(SPICE_COMMON_CPPFLAGS)
-SpiceClientGtk_2_0_gir_LIBS = libspice-client-gtk-2.0.la libspice-client-glib-2.0.la
-SpiceClientGtk_2_0_gir_FILES = $(gtk_introspection_files)
-SpiceClientGtk_2_0_gir_EXPORT_PACKAGES = spice-client-gtk-2.0
-SpiceClientGtk_2_0_gir_SCANNERFLAGS = --c-include="spice-widget.h"
-else
-SpiceClientGtk-3.0.gir: libspice-client-gtk-3.0.la SpiceClientGLib-2.0.gir
-SpiceClientGtk_3_0_gir_INCLUDES = GObject-2.0 Gtk-3.0 SpiceClientGLib-2.0
-SpiceClientGtk_3_0_gir_CFLAGS = $(SPICE_COMMON_CPPFLAGS)
-SpiceClientGtk_3_0_gir_LIBS = libspice-client-gtk-3.0.la libspice-client-glib-2.0.la
-SpiceClientGtk_3_0_gir_FILES = $(gtk_introspection_files)
-SpiceClientGtk_3_0_gir_EXPORT_PACKAGES = spice-client-gtk-3.0
-SpiceClientGtk_3_0_gir_SCANNERFLAGS = --c-include="spice-widget.h"
-endif
-INTROSPECTION_GIRS += SpiceClientGtk-$(SPICE_GTK_API_VERSION).gir
-endif
-
-girdir = $(datadir)/gir-1.0
-gir_DATA = $(INTROSPECTION_GIRS)
-
-typelibsdir = $(libdir)/girepository-1.0
-typelibs_DATA = $(INTROSPECTION_GIRS:.gir=.typelib)
-
-CLEANFILES += $(gir_DATA) $(typelibs_DATA)
-endif
-
-update-map-file: $(libspice_client_gtkinclude_HEADERS) $(nodist_libspice_client_gtkinclude_HEADERS) $(libspice_client_glibinclude_HEADERS) $(nodist_libspice_client_glibinclude_HEADERS)
-	( echo "SPICEGTK_1 {" ; \
-	  echo "global:" ; \
-	  ctags -f - -I G_GNUC_CONST --c-kinds=p $^ | awk '/^spice_/ { print $$1 ";" }' | sort ; \
-	  echo "local:" ;  \
-	  echo "*;" ; \
-	  echo "};" ) > $(srcdir)/map-file
-
-update-glib-sym-file: $(libspice_client_glibinclude_HEADERS) $(nodist_libspice_client_glibinclude_HEADERS)
-	( ctags -f - -I G_GNUC_CONST --c-kinds=p $^ | awk '/^spice_/ { print $$1 }' | sort ; \
-	) > $(srcdir)/spice-glib-sym-file
-
-update-gtk-sym-file: $(libspice_client_gtkinclude_HEADERS) $(nodist_libspice_client_gtkinclude_HEADERS)
-	( ctags -f - -I G_GNUC_CONST --c-kinds=p $^ | awk '/^spice_/ { print $$1 }' | sort ; \
-	) > $(srcdir)/spice-gtk-sym-file
-
-update-symbol-files: update-map-file update-glib-sym-file update-gtk-sym-file
-
--include $(top_srcdir)/git.mk
diff --git a/gtk/bio-gio.c b/gtk/bio-gio.c
deleted file mode 100644
index 108ac1a..0000000
--- a/gtk/bio-gio.c
+++ /dev/null
@@ -1,114 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2012 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 <string.h>
-#include <glib.h>
-
-#include "spice-util.h"
-#include "bio-gio.h"
-
-typedef struct bio_gsocket_method {
-    BIO_METHOD method;
-    GIOStream *stream;
-} bio_gsocket_method;
-
-#define BIO_GET_GSOCKET(bio)  (((bio_gsocket_method*)bio->method)->gsocket)
-#define BIO_GET_ISTREAM(bio)  (g_io_stream_get_input_stream(((bio_gsocket_method*)bio->method)->stream))
-#define BIO_GET_OSTREAM(bio)  (g_io_stream_get_output_stream(((bio_gsocket_method*)bio->method)->stream))
-
-static int bio_gio_write(BIO *bio, const char *in, int inl)
-{
-    gssize ret;
-    GError *error = NULL;
-
-    ret = g_pollable_output_stream_write_nonblocking(G_POLLABLE_OUTPUT_STREAM(BIO_GET_OSTREAM(bio)),
-                                                     in, inl, NULL, &error);
-    BIO_clear_retry_flags(bio);
-
-    if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
-        BIO_set_retry_write(bio);
-    if (error != NULL) {
-        g_warning("%s", error->message);
-        g_clear_error(&error);
-    }
-
-    return ret;
-}
-
-static int bio_gio_read(BIO *bio, char *out, int outl)
-{
-    gssize ret;
-    GError *error = NULL;
-
-    ret = g_pollable_input_stream_read_nonblocking(G_POLLABLE_INPUT_STREAM(BIO_GET_ISTREAM(bio)),
-                                                   out, outl, NULL, &error);
-    BIO_clear_retry_flags(bio);
-
-    if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
-        BIO_set_retry_read(bio);
-    else if (error != NULL)
-        g_warning("%s", error->message);
-
-    g_clear_error(&error);
-
-    return ret;
-}
-
-static int bio_gio_destroy(BIO *bio)
-{
-    if (bio == NULL || bio->method == NULL)
-        return 0;
-
-    SPICE_DEBUG("bio gsocket destroy");
-    g_free(bio->method);
-    bio->method = NULL;;
-
-    return 1;
-}
-
-static int bio_gio_puts(BIO *bio, const char *str)
-{
-    int n, ret;
-
-    n = strlen(str);
-    ret = bio_gio_write(bio, str, n);
-
-    return ret;
-}
-
-G_GNUC_INTERNAL
-BIO* bio_new_giostream(GIOStream *stream)
-{
-    // TODO: make an actual new BIO type, or just switch to GTls already...
-    BIO *bio = BIO_new_socket(-1, BIO_NOCLOSE);
-
-    bio_gsocket_method *bio_method = g_new(bio_gsocket_method, 1);
-    bio_method->method = *bio->method;
-    bio_method->stream = stream;
-
-    bio->method->destroy(bio);
-    bio->method = (BIO_METHOD*)bio_method;
-
-    bio->method->bwrite = bio_gio_write;
-    bio->method->bread = bio_gio_read;
-    bio->method->bputs = bio_gio_puts;
-    bio->method->destroy = bio_gio_destroy;
-
-    return bio;
-}
diff --git a/gtk/bio-gio.h b/gtk/bio-gio.h
deleted file mode 100644
index 31fd369..0000000
--- a/gtk/bio-gio.h
+++ /dev/null
@@ -1,30 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2012 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 BIO_GIO_H_
-# define BIO_GIO_H_
-
-#include <openssl/bio.h>
-#include <gio/gio.h>
-
-G_BEGIN_DECLS
-
-BIO* bio_new_giostream(GIOStream *stream);
-
-G_END_DECLS
-
-#endif /* !BIO_GIO_H_ */
diff --git a/gtk/channel-base.c b/gtk/channel-base.c
deleted file mode 100644
index 77d339c..0000000
--- a/gtk/channel-base.c
+++ /dev/null
@@ -1,284 +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-session-priv.h"
-#include "spice-channel-priv.h"
-
-/* coroutine context */
-G_GNUC_INTERNAL
-void spice_channel_handle_set_ack(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceChannelPrivate *c = channel->priv;
-    SpiceMsgSetAck* ack = spice_msg_in_parsed(in);
-    SpiceMsgOut *out = spice_msg_out_new(channel, SPICE_MSGC_ACK_SYNC);
-    SpiceMsgcAckSync sync = {
-        .generation = ack->generation,
-    };
-
-    c->message_ack_window = c->message_ack_count = ack->window;
-    c->marshallers->msgc_ack_sync(out->marshaller, &sync);
-    spice_msg_out_send_internal(out);
-}
-
-/* coroutine context */
-G_GNUC_INTERNAL
-void spice_channel_handle_ping(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceChannelPrivate *c = channel->priv;
-    SpiceMsgPing *ping = spice_msg_in_parsed(in);
-    SpiceMsgOut *pong = spice_msg_out_new(channel, SPICE_MSGC_PONG);
-
-    c->marshallers->msgc_pong(pong->marshaller, ping);
-    spice_msg_out_send_internal(pong);
-}
-
-/* coroutine context */
-G_GNUC_INTERNAL
-void spice_channel_handle_notify(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    static const char* severity_strings[] = {"info", "warn", "error"};
-    static const char* visibility_strings[] = {"!", "!!", "!!!"};
-
-    SpiceMsgNotify *notify = spice_msg_in_parsed(in);
-    const char *severity   = "?";
-    const char *visibility = "?";
-    const char *message_str = NULL;
-
-    if (notify->severity <= SPICE_NOTIFY_SEVERITY_ERROR) {
-        severity = severity_strings[notify->severity];
-    }
-    if (notify->visibilty <= SPICE_NOTIFY_VISIBILITY_HIGH) {
-        visibility = visibility_strings[notify->visibilty];
-    }
-
-    if (notify->message_len &&
-        notify->message_len <= in->dpos - sizeof(*notify)) {
-        message_str = (char*)notify->message;
-    }
-
-    CHANNEL_DEBUG(channel, "%s -- %s%s #%u%s%.*s", __FUNCTION__,
-            severity, visibility, notify->what,
-            message_str ? ": " : "", notify->message_len,
-            message_str ? message_str : "");
-}
-
-/* coroutine context */
-G_GNUC_INTERNAL
-void spice_channel_handle_disconnect(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceMsgDisconnect *disconnect = spice_msg_in_parsed(in);
-
-    CHANNEL_DEBUG(channel, "%s: ts: %" PRIu64", reason: %u", __FUNCTION__,
-                  disconnect->time_stamp, disconnect->reason);
-}
-
-typedef struct WaitForChannelData
-{
-    SpiceWaitForChannel *wait;
-    SpiceChannel *channel;
-} WaitForChannelData;
-
-/* coroutine and main context */
-static gboolean wait_for_channel(gpointer data)
-{
-    WaitForChannelData *wfc = data;
-    SpiceChannelPrivate *c = wfc->channel->priv;
-    SpiceChannel *wait_channel;
-
-    wait_channel = spice_session_lookup_channel(c->session, wfc->wait->channel_id, wfc->wait->channel_type);
-    g_return_val_if_fail(wait_channel != NULL, TRUE);
-
-    if (wait_channel->priv->last_message_serial >= wfc->wait->message_serial)
-        return TRUE;
-
-    return FALSE;
-}
-
-/* coroutine context */
-G_GNUC_INTERNAL
-void spice_channel_handle_wait_for_channels(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceChannelPrivate *c = channel->priv;
-    SpiceMsgWaitForChannels *wfc = spice_msg_in_parsed(in);
-    int i;
-
-    for (i = 0; i < wfc->wait_count; ++i) {
-        WaitForChannelData data = {
-            .wait = wfc->wait_list + i,
-            .channel = channel
-        };
-
-        CHANNEL_DEBUG(channel, "waiting for serial %" PRIu64 " (%d/%d)", data.wait->message_serial, i + 1, wfc->wait_count);
-        if (g_coroutine_condition_wait(&c->coroutine, wait_for_channel, &data))
-            CHANNEL_DEBUG(channel, "waiting for serial %"  PRIu64 ", done", data.wait->message_serial);
-        else
-            CHANNEL_DEBUG(channel, "waiting for serial %" PRIu64 ", cancelled", data.wait->message_serial);
-    }
-}
-
-static void
-get_msg_handler(SpiceChannel *channel, SpiceMsgIn *in, gpointer data)
-{
-    SpiceMsgIn **msg = data;
-
-    g_return_if_fail(msg != NULL);
-    g_return_if_fail(*msg == NULL);
-
-    spice_msg_in_ref(in);
-    *msg = in;
-}
-
-/* coroutine context */
-G_GNUC_INTERNAL
-void spice_channel_handle_migrate(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceMsgOut *out;
-    SpiceMsgIn *data = NULL;
-    SpiceMsgMigrate *mig = spice_msg_in_parsed(in);
-    SpiceChannelPrivate *c = channel->priv;
-
-    CHANNEL_DEBUG(channel, "%s: flags %u", __FUNCTION__, mig->flags);
-    if (mig->flags & SPICE_MIGRATE_NEED_FLUSH) {
-        /* if peer version > 1: pushing the mark msg before all other messgages and sending it,
-         * and only it */
-        if (c->peer_hdr.major_version == 1) {
-            /* iterate_write is blocking and flushing all pending write */
-            SPICE_CHANNEL_GET_CLASS(channel)->iterate_write(channel);
-        }
-        out = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_MIGRATE_FLUSH_MARK);
-        spice_msg_out_send_internal(out);
-    }
-    if (mig->flags & SPICE_MIGRATE_NEED_DATA_TRANSFER) {
-        spice_channel_recv_msg(channel, get_msg_handler, &data);
-        if (!data) {
-            g_critical("expected SPICE_MSG_MIGRATE_DATA, got empty message");
-            goto end;
-        } else if (spice_header_get_msg_type(data->header, c->use_mini_header) !=
-                   SPICE_MSG_MIGRATE_DATA) {
-            g_critical("expected SPICE_MSG_MIGRATE_DATA, got %d",
-                      spice_header_get_msg_type(data->header, c->use_mini_header));
-            goto end;
-        }
-    }
-
-    /* swapping channels sockets */
-    spice_session_channel_migrate(c->session, channel);
-
-    /* pushing the MIGRATE_DATA before all other pending messages */
-    if ((mig->flags & SPICE_MIGRATE_NEED_DATA_TRANSFER) && (data != NULL)) {
-        out = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_MIGRATE_DATA);
-        spice_marshaller_add(out->marshaller, data->data,
-                             spice_header_get_msg_size(data->header, c->use_mini_header));
-        spice_msg_out_send_internal(out);
-    }
-
-end:
-    if (data)
-        spice_msg_in_unref(data);
-}
-
-
-static void set_handlers(SpiceChannelClass *klass,
-                         const spice_msg_handler* handlers, const int n)
-{
-    int i;
-
-    g_array_set_size(klass->handlers, MAX(klass->handlers->len, n));
-    for (i = 0; i < n; i++) {
-        if (handlers[i])
-            g_array_index(klass->handlers, spice_msg_handler, i) = handlers[i];
-    }
-}
-
-static void spice_channel_add_base_handlers(SpiceChannelClass *klass)
-{
-    static const spice_msg_handler handlers[] = {
-        [ SPICE_MSG_SET_ACK ]                  = spice_channel_handle_set_ack,
-        [ SPICE_MSG_PING ]                     = spice_channel_handle_ping,
-        [ SPICE_MSG_NOTIFY ]                   = spice_channel_handle_notify,
-        [ SPICE_MSG_DISCONNECTING ]            = spice_channel_handle_disconnect,
-        [ SPICE_MSG_WAIT_FOR_CHANNELS ]        = spice_channel_handle_wait_for_channels,
-        [ SPICE_MSG_MIGRATE ]                  = spice_channel_handle_migrate,
-    };
-
-    set_handlers(klass, handlers, G_N_ELEMENTS(handlers));
-}
-
-G_GNUC_INTERNAL
-void spice_channel_set_handlers(SpiceChannelClass *klass,
-                                const spice_msg_handler* handlers, const int n)
-{
-    /* FIXME: use class private (requires glib 2.24) */
-    g_return_if_fail(klass->handlers == NULL);
-    klass->handlers = g_array_sized_new(FALSE, TRUE, sizeof(spice_msg_handler), n);
-
-    spice_channel_add_base_handlers(klass);
-    set_handlers(klass, handlers, n);
-}
-
-static void
-vmc_write_free_cb(uint8_t *data, void *user_data)
-{
-    GSimpleAsyncResult *result = user_data;
-
-    g_simple_async_result_complete_in_idle(result);
-    g_object_unref(result);
-}
-
-G_GNUC_INTERNAL
-void spice_vmc_write_async(SpiceChannel *self,
-                           const void *buffer, gsize count,
-                           GCancellable *cancellable,
-                           GAsyncReadyCallback callback,
-                           gpointer user_data)
-{
-    SpiceMsgOut *msg;
-    GSimpleAsyncResult *simple;
-
-    simple = g_simple_async_result_new(G_OBJECT(self), callback, user_data,
-                                       spice_port_write_async);
-    g_simple_async_result_set_op_res_gssize(simple, count);
-
-    msg = spice_msg_out_new(SPICE_CHANNEL(self), SPICE_MSGC_SPICEVMC_DATA);
-    spice_marshaller_add_ref_full(msg->marshaller, (uint8_t*)buffer, count,
-                                  vmc_write_free_cb, simple);
-    spice_msg_out_send(msg);
-}
-
-G_GNUC_INTERNAL
-gssize spice_vmc_write_finish(SpiceChannel *self,
-                              GAsyncResult *result, GError **error)
-{
-    GSimpleAsyncResult *simple;
-
-    g_return_val_if_fail(result != NULL, -1);
-
-    simple = (GSimpleAsyncResult *)result;
-
-    if (g_simple_async_result_propagate_error(simple, error))
-        return -1;
-
-    g_return_val_if_fail(g_simple_async_result_is_valid(result, G_OBJECT(self),
-                                                        spice_port_write_async), -1);
-
-    return g_simple_async_result_get_op_res_gssize(simple);
-}
diff --git a/gtk/channel-cursor.c b/gtk/channel-cursor.c
deleted file mode 100644
index e6514a2..0000000
--- a/gtk/channel-cursor.c
+++ /dev/null
@@ -1,529 +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-compat.h"
-#include "spice-client.h"
-#include "spice-common.h"
-
-#include "spice-channel-priv.h"
-#include "spice-channel-cache.h"
-#include "spice-marshal.h"
-
-/**
- * SECTION:channel-cursor
- * @short_description: update cursor shape and position
- * @title: Cursor Channel
- * @section_id:
- * @see_also: #SpiceChannel, and the GTK widget #SpiceDisplay
- * @stability: Stable
- * @include: channel-cursor.h
- *
- * The Spice protocol defines a set of messages for controlling cursor
- * shape and position on the remote display area. The cursor changes
- * that should be reflected on the display are notified by
- * signals. See for example #SpiceCursorChannel::cursor-set
- * #SpiceCursorChannel::cursor-move signals.
- */
-
-#define SPICE_CURSOR_CHANNEL_GET_PRIVATE(obj)                                  \
-    (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_CURSOR_CHANNEL, SpiceCursorChannelPrivate))
-
-typedef struct display_cursor display_cursor;
-
-struct display_cursor {
-    SpiceCursorHeader           hdr;
-    gboolean                    default_cursor;
-    int                         refcount;
-    guint32                     data[];
-};
-
-struct _SpiceCursorChannelPrivate {
-    display_cache               *cursors;
-    gboolean                    init_done;
-};
-
-enum {
-    SPICE_CURSOR_SET,
-    SPICE_CURSOR_MOVE,
-    SPICE_CURSOR_HIDE,
-    SPICE_CURSOR_RESET,
-
-    SPICE_CURSOR_LAST_SIGNAL,
-};
-
-static guint signals[SPICE_CURSOR_LAST_SIGNAL];
-
-static display_cursor * display_cursor_ref(display_cursor *cursor);
-static void display_cursor_unref(display_cursor *cursor);
-static void channel_set_handlers(SpiceChannelClass *klass);
-
-G_DEFINE_TYPE(SpiceCursorChannel, spice_cursor_channel, SPICE_TYPE_CHANNEL)
-
-/* ------------------------------------------------------------------ */
-
-static void spice_cursor_channel_init(SpiceCursorChannel *channel)
-{
-    SpiceCursorChannelPrivate *c;
-
-    c = channel->priv = SPICE_CURSOR_CHANNEL_GET_PRIVATE(channel);
-
-    c->cursors = cache_new((GDestroyNotify)display_cursor_unref);
-}
-
-static void spice_cursor_channel_finalize(GObject *obj)
-{
-    SpiceCursorChannel *channel = SPICE_CURSOR_CHANNEL(obj);
-    SpiceCursorChannelPrivate *c = channel->priv;
-
-    g_clear_pointer(&c->cursors, cache_unref);
-
-    if (G_OBJECT_CLASS(spice_cursor_channel_parent_class)->finalize)
-        G_OBJECT_CLASS(spice_cursor_channel_parent_class)->finalize(obj);
-}
-
-/* coroutine context */
-static void spice_cursor_channel_reset(SpiceChannel *channel, gboolean migrating)
-{
-    SpiceCursorChannelPrivate *c = SPICE_CURSOR_CHANNEL(channel)->priv;
-
-    cache_clear(c->cursors);
-    c->init_done = FALSE;
-
-    SPICE_CHANNEL_CLASS(spice_cursor_channel_parent_class)->channel_reset(channel, migrating);
-}
-
-static void spice_cursor_channel_class_init(SpiceCursorChannelClass *klass)
-{
-    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
-    SpiceChannelClass *channel_class = SPICE_CHANNEL_CLASS(klass);
-
-    gobject_class->finalize     = spice_cursor_channel_finalize;
-    channel_class->channel_reset = spice_cursor_channel_reset;
-
-    /**
-     * SpiceCursorChannel::cursor-set:
-     * @cursor: the #SpiceCursorChannel that emitted the signal
-     * @width: width of the shape
-     * @height: height of the shape
-     * @hot_x: horizontal offset of the 'hotspot' of the cursor
-     * @hot_y: vertical offset of the 'hotspot' of the cursor
-     * @rgba: 32bits shape data, or %NULL if default cursor. It might
-     * be freed after the signal is emitted, so make sure to copy it
-     * if you need it later!
-     *
-     * The #SpiceCursorChannel::cursor-set signal is emitted to modify
-     * cursor aspect and position on the display area.
-     **/
-    signals[SPICE_CURSOR_SET] =
-        g_signal_new("cursor-set",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_FIRST,
-                     G_STRUCT_OFFSET(SpiceCursorChannelClass, cursor_set),
-                     NULL, NULL,
-                     g_cclosure_user_marshal_VOID__INT_INT_INT_INT_POINTER,
-                     G_TYPE_NONE,
-                     5,
-                     G_TYPE_INT, G_TYPE_INT,
-                     G_TYPE_INT, G_TYPE_INT,
-                     G_TYPE_POINTER);
-
-    /**
-     * SpiceCursorChannel::cursor-move:
-     * @cursor: the #SpiceCursorChannel that emitted the signal
-     * @x: x position
-     * @y: y position
-     *
-     * The #SpiceCursorChannel::cursor-move signal is emitted to update
-     * the cursor position on the display area.
-     **/
-    signals[SPICE_CURSOR_MOVE] =
-        g_signal_new("cursor-move",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_FIRST,
-                     G_STRUCT_OFFSET(SpiceCursorChannelClass, cursor_move),
-                     NULL, NULL,
-                     g_cclosure_user_marshal_VOID__INT_INT,
-                     G_TYPE_NONE,
-                     2,
-                     G_TYPE_INT, G_TYPE_INT);
-
-    /**
-     * SpiceCursorChannel::cursor-hide:
-     * @cursor: the #SpiceCursorChannel that emitted the signal
-     *
-     * The #SpiceCursorChannel::cursor-hide signal is emitted to hide
-     * the cursor/pointer on the display area.
-     **/
-    signals[SPICE_CURSOR_HIDE] =
-        g_signal_new("cursor-hide",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_FIRST,
-                     G_STRUCT_OFFSET(SpiceCursorChannelClass, cursor_hide),
-                     NULL, NULL,
-                     g_cclosure_marshal_VOID__VOID,
-                     G_TYPE_NONE,
-                     0);
-
-    /**
-     * SpiceCursorChannel::cursor-reset:
-     * @cursor: the #SpiceCursorChannel that emitted the signal
-     *
-     * The #SpiceCursorChannel::cursor-reset signal is emitted to
-     * reset the cursor to its default context.
-     **/
-    signals[SPICE_CURSOR_RESET] =
-        g_signal_new("cursor-reset",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_FIRST,
-                     G_STRUCT_OFFSET(SpiceCursorChannelClass, cursor_reset),
-                     NULL, NULL,
-                     g_cclosure_marshal_VOID__VOID,
-                     G_TYPE_NONE,
-                     0);
-
-    g_type_class_add_private(klass, sizeof(SpiceCursorChannelPrivate));
-    channel_set_handlers(SPICE_CHANNEL_CLASS(klass));
-}
-
-/* ------------------------------------------------------------------ */
-
-#ifdef DEBUG_CURSOR
-static void print_cursor(display_cursor *cursor, const guint8 *data)
-{
-    int x, y, bpl;
-    const guint8 *xor, *and;
-
-    bpl = (cursor->hdr.width + 7) / 8;
-    and = data;
-    xor = and + bpl * cursor->hdr.height;
-
-    printf("data (%d x %d):\n", cursor->hdr.width, cursor->hdr.height);
-    for (y = 0 ; y < cursor->hdr.height; ++y) {
-        for (x = 0 ; x < cursor->hdr.width / 8; x++) {
-            printf("%02X", and[x]);
-        }
-        and += bpl;
-        printf("\n");
-    }
-    printf("xor:\n");
-    for (y = 0 ; y < cursor->hdr.height; ++y) {
-        for (x = 0 ; x < cursor->hdr.width / 8; ++x) {
-            printf("%02X", xor[x]);
-        }
-        xor += bpl;
-        printf("\n");
-    }
-}
-#endif
-
-static void mono_cursor(display_cursor *cursor, const guint8 *data)
-{
-    int bpl = (cursor->hdr.width + 7) / 8;
-    const guint8 *xor, *and;
-    guint8 *dest;
-    dest = (uint8_t *)cursor->data;
-
-#ifdef DEBUG_CURSOR
-    print_cursor(cursor, data);
-#endif
-    and = data;
-    xor = and + bpl * cursor->hdr.height;
-    spice_mono_edge_highlight(cursor->hdr.width, cursor->hdr.height,
-                              and, xor, dest);
-}
-
-static guint8 get_pix_mask(const guint8 *data, gint offset, gint pix_index)
-{
-    return data[offset + (pix_index >> 3)] & (0x80 >> (pix_index % 8));
-}
-
-static guint32 get_pix_hack(gint pix_index, gint width)
-{
-    return (((pix_index % width) ^ (pix_index / width)) & 1) ? 0xc0303030 : 0x30505050;
-}
-
-static display_cursor * display_cursor_ref(display_cursor *cursor)
-{
-    g_return_val_if_fail(cursor != NULL, NULL);
-    g_return_val_if_fail(cursor->refcount > 0, NULL);
-
-    cursor->refcount++;
-    return cursor;
-}
-
-static void display_cursor_unref(display_cursor *cursor)
-{
-    g_return_if_fail(cursor != NULL);
-    g_return_if_fail(cursor->refcount > 0);
-
-    cursor->refcount--;
-    if (cursor->refcount == 0)
-        g_free(cursor);
-}
-
-static const char *cursor_type_to_string(int type)
-{
-    switch (type) {
-    case SPICE_CURSOR_TYPE_MONO:
-        return "mono";
-    case SPICE_CURSOR_TYPE_ALPHA:
-        return "alpha";
-    case SPICE_CURSOR_TYPE_COLOR32:
-        return "color32";
-    case SPICE_CURSOR_TYPE_COLOR16:
-        return "color16";
-    case SPICE_CURSOR_TYPE_COLOR4:
-        return "color4";
-    }
-    return "unknown";
-}
-
-static display_cursor *set_cursor(SpiceChannel *channel, SpiceCursor *scursor)
-{
-    SpiceCursorChannelPrivate *c = SPICE_CURSOR_CHANNEL(channel)->priv;
-    SpiceCursorHeader *hdr = &scursor->header;
-    display_cursor *cursor;
-    size_t size;
-    gint i, pix_mask, pix;
-    const guint8* data;
-    guint8 *rgba;
-    guint8 val;
-
-    CHANNEL_DEBUG(channel, "%s: flags %d, size %d", __FUNCTION__,
-                  scursor->flags, scursor->data_size);
-
-    if (scursor->flags & SPICE_CURSOR_FLAGS_NONE)
-        return NULL;
-
-    CHANNEL_DEBUG(channel, "%s: type %s(%d), %" PRIx64 ", %dx%d", __FUNCTION__,
-                  cursor_type_to_string(hdr->type), hdr->type, hdr->unique,
-                  hdr->width, hdr->height);
-
-    if (scursor->flags & SPICE_CURSOR_FLAGS_FROM_CACHE) {
-        cursor = cache_find(c->cursors, hdr->unique);
-        g_return_val_if_fail(cursor != NULL, NULL);
-        return display_cursor_ref(cursor);
-    }
-
-    g_return_val_if_fail(scursor->data_size != 0, NULL);
-
-    size = 4u * hdr->width * hdr->height;
-    cursor = g_malloc0(sizeof(*cursor) + size);
-    cursor->hdr = *hdr;
-    cursor->default_cursor = FALSE;
-    cursor->refcount = 1;
-    data = scursor->data;
-
-    switch (hdr->type) {
-    case SPICE_CURSOR_TYPE_MONO:
-        mono_cursor(cursor, data);
-        break;
-    case SPICE_CURSOR_TYPE_ALPHA:
-        memcpy(cursor->data, data, size);
-        break;
-    case SPICE_CURSOR_TYPE_COLOR32:
-        memcpy(cursor->data, data, size);
-        for (i = 0; i < hdr->width * hdr->height; i++) {
-            pix_mask = get_pix_mask(data, size, i);
-            if (pix_mask && *((guint32*)data + i) == 0xffffff) {
-                cursor->data[i] = get_pix_hack(i, hdr->width);
-            } else {
-                cursor->data[i] |= (pix_mask ? 0 : 0xff000000);
-            }
-        }
-        break;
-    case SPICE_CURSOR_TYPE_COLOR16:
-        for (i = 0; i < hdr->width * hdr->height; i++) {
-            pix_mask = get_pix_mask(data, size, i);
-            pix = *((guint16*)data + i);
-            if (pix_mask && pix == 0x7fff) {
-                cursor->data[i] = get_pix_hack(i, hdr->width);
-            } else {
-                cursor->data[i] |= ((pix & 0x1f) << 3) | ((pix & 0x3e0) << 6) |
-                    ((pix & 0x7c00) << 9) | (pix_mask ? 0 : 0xff000000);
-            }
-        }
-        break;
-    case SPICE_CURSOR_TYPE_COLOR4:
-        size = ((unsigned int)(SPICE_ALIGN(hdr->width, 2) / 2)) * hdr->height;
-        for (i = 0; i < hdr->width * hdr->height; i++) {
-            pix_mask = get_pix_mask(data, size + (sizeof(uint32_t) << 4), i);
-            int idx = (i & 1) ? (data[i >> 1] & 0x0f) : ((data[i >> 1] & 0xf0) >> 4);
-            pix = *((uint32_t*)(data + size) + idx);
-            if (pix_mask && pix == 0xffffff) {
-                cursor->data[i] = get_pix_hack(i, hdr->width);
-            } else {
-                cursor->data[i] = pix | (pix_mask ? 0 : 0xff000000);
-            }
-        }
-
-        break;
-    default:
-        g_warning("%s: unimplemented cursor type %d", __FUNCTION__,
-                  hdr->type);
-        cursor->default_cursor = TRUE;
-        goto cache_add;
-    }
-
-    rgba = (guint8*)cursor->data;
-    for (i = 0; i < hdr->width * hdr->height; i++) {
-        val = rgba[0];
-        rgba[0] = rgba[2];
-        rgba[2] = val;
-        rgba += 4;
-    }
-
-cache_add:
-    if (scursor->flags & SPICE_CURSOR_FLAGS_CACHE_ME) {
-        cache_add(c->cursors, hdr->unique, display_cursor_ref(cursor));
-    }
-
-    return cursor;
-}
-
-/* coroutine context */
-static void emit_cursor_set(SpiceChannel *channel, display_cursor *cursor)
-{
-    g_return_if_fail(cursor != NULL);
-    g_coroutine_signal_emit(channel, signals[SPICE_CURSOR_SET], 0,
-                            cursor->hdr.width, cursor->hdr.height,
-                            cursor->hdr.hot_spot_x, cursor->hdr.hot_spot_y,
-                            cursor->default_cursor ? NULL : cursor->data);
-}
-
-/* coroutine context */
-static void cursor_handle_init(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceMsgCursorInit *init = spice_msg_in_parsed(in);
-    SpiceCursorChannelPrivate *c = SPICE_CURSOR_CHANNEL(channel)->priv;
-    display_cursor *cursor;
-
-    g_return_if_fail(c->init_done == FALSE);
-
-    cache_clear(c->cursors);
-    cursor = set_cursor(channel, &init->cursor);
-    c->init_done = TRUE;
-    if (cursor)
-        emit_cursor_set(channel, cursor);
-    if (!init->visible || !cursor)
-        g_coroutine_signal_emit(channel, signals[SPICE_CURSOR_HIDE], 0);
-    if (cursor)
-        display_cursor_unref(cursor);
-}
-
-/* coroutine context */
-static void cursor_handle_reset(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceCursorChannelPrivate *c = SPICE_CURSOR_CHANNEL(channel)->priv;
-
-    CHANNEL_DEBUG(channel, "%s, init_done: %d", __FUNCTION__, c->init_done);
-
-    cache_clear(c->cursors);
-    g_coroutine_signal_emit(channel, signals[SPICE_CURSOR_RESET], 0);
-    c->init_done = FALSE;
-}
-
-/* coroutine context */
-static void cursor_handle_set(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceMsgCursorSet *set = spice_msg_in_parsed(in);
-    SpiceCursorChannelPrivate *c = SPICE_CURSOR_CHANNEL(channel)->priv;
-    display_cursor *cursor;
-
-    g_return_if_fail(c->init_done == TRUE);
-
-    cursor = set_cursor(channel, &set->cursor);
-    if (cursor)
-        emit_cursor_set(channel, cursor);
-    else
-        g_coroutine_signal_emit(channel, signals[SPICE_CURSOR_HIDE], 0);
-
-
-    if (cursor)
-        display_cursor_unref(cursor);
-}
-
-/* coroutine context */
-static void cursor_handle_move(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceMsgCursorMove *move = spice_msg_in_parsed(in);
-    SpiceCursorChannelPrivate *c = SPICE_CURSOR_CHANNEL(channel)->priv;
-
-    g_return_if_fail(c->init_done == TRUE);
-
-    g_coroutine_signal_emit(channel, signals[SPICE_CURSOR_MOVE], 0,
-                            move->position.x, move->position.y);
-}
-
-/* coroutine context */
-static void cursor_handle_hide(SpiceChannel *channel, SpiceMsgIn *in)
-{
-#ifdef EXTRA_CHECKS
-    SpiceCursorChannelPrivate *c = SPICE_CURSOR_CHANNEL(channel)->priv;
-
-    g_return_if_fail(c->init_done == TRUE);
-#endif
-
-    g_coroutine_signal_emit(channel, signals[SPICE_CURSOR_HIDE], 0);
-}
-
-/* coroutine context */
-static void cursor_handle_trail(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceCursorChannelPrivate *c = SPICE_CURSOR_CHANNEL(channel)->priv;
-
-    g_return_if_fail(c->init_done == TRUE);
-
-    g_warning("%s: TODO", __FUNCTION__);
-}
-
-/* coroutine context */
-static void cursor_handle_inval_one(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceCursorChannelPrivate *c = SPICE_CURSOR_CHANNEL(channel)->priv;
-    SpiceMsgDisplayInvalOne *zap = spice_msg_in_parsed(in);
-
-    g_return_if_fail(c->init_done == TRUE);
-
-    cache_remove(c->cursors, zap->id);
-}
-
-/* coroutine context */
-static void cursor_handle_inval_all(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceCursorChannelPrivate *c = SPICE_CURSOR_CHANNEL(channel)->priv;
-
-    cache_clear(c->cursors);
-}
-
-static void channel_set_handlers(SpiceChannelClass *klass)
-{
-    static const spice_msg_handler handlers[] = {
-        [ SPICE_MSG_CURSOR_INIT ]              = cursor_handle_init,
-        [ SPICE_MSG_CURSOR_RESET ]             = cursor_handle_reset,
-        [ SPICE_MSG_CURSOR_SET ]               = cursor_handle_set,
-        [ SPICE_MSG_CURSOR_MOVE ]              = cursor_handle_move,
-        [ SPICE_MSG_CURSOR_HIDE ]              = cursor_handle_hide,
-        [ SPICE_MSG_CURSOR_TRAIL ]             = cursor_handle_trail,
-        [ SPICE_MSG_CURSOR_INVAL_ONE ]         = cursor_handle_inval_one,
-        [ SPICE_MSG_CURSOR_INVAL_ALL ]         = cursor_handle_inval_all,
-    };
-
-    spice_channel_set_handlers(klass, handlers, G_N_ELEMENTS(handlers));
-}
diff --git a/gtk/channel-cursor.h b/gtk/channel-cursor.h
deleted file mode 100644
index 5b5ed47..0000000
--- a/gtk/channel-cursor.h
+++ /dev/null
@@ -1,77 +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_CLIENT_CURSOR_CHANNEL_H__
-#define __SPICE_CLIENT_CURSOR_CHANNEL_H__
-
-#include "spice-client.h"
-
-G_BEGIN_DECLS
-
-#define SPICE_TYPE_CURSOR_CHANNEL            (spice_cursor_channel_get_type())
-#define SPICE_CURSOR_CHANNEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), SPICE_TYPE_CURSOR_CHANNEL, SpiceCursorChannel))
-#define SPICE_CURSOR_CHANNEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), SPICE_TYPE_CURSOR_CHANNEL, SpiceCursorChannelClass))
-#define SPICE_IS_CURSOR_CHANNEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), SPICE_TYPE_CURSOR_CHANNEL))
-#define SPICE_IS_CURSOR_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SPICE_TYPE_CURSOR_CHANNEL))
-#define SPICE_CURSOR_CHANNEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), SPICE_TYPE_CURSOR_CHANNEL, SpiceCursorChannelClass))
-
-typedef struct _SpiceCursorChannel SpiceCursorChannel;
-typedef struct _SpiceCursorChannelClass SpiceCursorChannelClass;
-typedef struct _SpiceCursorChannelPrivate SpiceCursorChannelPrivate;
-
-/**
- * SpiceCursorChannel:
- *
- * The #SpiceCursorChannel struct is opaque and should not be accessed directly.
- */
-struct _SpiceCursorChannel {
-    SpiceChannel parent;
-
-    /*< private >*/
-    SpiceCursorChannelPrivate *priv;
-    /* Do not add fields to this struct */
-};
-
-/**
- * SpiceCursorChannelClass:
- * @parent_class: Parent class.
- * @cursor_set: Signal class handler for the #SpiceCursorChannel::cursor-set signal.
- * @cursor_move: Signal class handler for the #SpiceCursorChannel::cursor-move signal.
- * @cursor_hide: Signal class handler for the #SpiceCursorChannel::cursor-hide signal.
- * @cursor_reset: Signal class handler for the #SpiceCursorChannel::cursor-reset signal.
- *
- * Class structure for #SpiceCursorChannel.
- */
-struct _SpiceCursorChannelClass {
-    SpiceChannelClass parent_class;
-
-    /* signals */
-    void (*cursor_set)(SpiceCursorChannel *channel, gint width, gint height,
-                       gint hot_x, gint hot_y, gpointer rgba);
-    void (*cursor_move)(SpiceCursorChannel *channel, gint x, gint y);
-    void (*cursor_hide)(SpiceCursorChannel *channel);
-    void (*cursor_reset)(SpiceCursorChannel *channel);
-
-    /*< private >*/
-    /* Do not add fields to this struct */
-};
-
-GType spice_cursor_channel_get_type(void);
-
-G_END_DECLS
-
-#endif /* __SPICE_CLIENT_CURSOR_CHANNEL_H__ */
diff --git a/gtk/channel-display-mjpeg.c b/gtk/channel-display-mjpeg.c
deleted file mode 100644
index 95d5b33..0000000
--- a/gtk/channel-display-mjpeg.c
+++ /dev/null
@@ -1,156 +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-channel-priv.h"
-
-#include "channel-display-priv.h"
-
-static void mjpeg_src_init(struct jpeg_decompress_struct *cinfo)
-{
-    display_stream *st = SPICE_CONTAINEROF(cinfo->src, display_stream, mjpeg_src);
-    uint8_t *data;
-
-    cinfo->src->bytes_in_buffer = stream_get_current_frame(st, &data);
-    cinfo->src->next_input_byte = data;
-}
-
-static boolean mjpeg_src_fill(struct jpeg_decompress_struct *cinfo)
-{
-    g_critical("need more input data");
-    return 0;
-}
-
-static void mjpeg_src_skip(struct jpeg_decompress_struct *cinfo,
-                           long num_bytes)
-{
-    cinfo->src->next_input_byte += num_bytes;
-}
-
-static void mjpeg_src_term(struct jpeg_decompress_struct *cinfo)
-{
-    /* nothing */
-}
-
-G_GNUC_INTERNAL
-void stream_mjpeg_init(display_stream *st)
-{
-    st->mjpeg_cinfo.err = jpeg_std_error(&st->mjpeg_jerr);
-    jpeg_create_decompress(&st->mjpeg_cinfo);
-
-    st->mjpeg_src.init_source         = mjpeg_src_init;
-    st->mjpeg_src.fill_input_buffer   = mjpeg_src_fill;
-    st->mjpeg_src.skip_input_data     = mjpeg_src_skip;
-    st->mjpeg_src.resync_to_restart   = jpeg_resync_to_restart;
-    st->mjpeg_src.term_source         = mjpeg_src_term;
-    st->mjpeg_cinfo.src               = &st->mjpeg_src;
-}
-
-G_GNUC_INTERNAL
-void stream_mjpeg_data(display_stream *st)
-{
-    gboolean back_compat = st->channel->priv->peer_hdr.major_version == 1;
-    int width;
-    int height;
-    uint8_t *dest;
-    uint8_t *lines[4];
-
-    stream_get_dimensions(st, &width, &height);
-    dest = g_malloc0(width * height * 4);
-
-    g_free(st->out_frame);
-    st->out_frame = dest;
-
-    jpeg_read_header(&st->mjpeg_cinfo, 1);
-#ifdef JCS_EXTENSIONS
-    // requires jpeg-turbo
-    if (back_compat)
-        st->mjpeg_cinfo.out_color_space = JCS_EXT_RGBX;
-    else
-        st->mjpeg_cinfo.out_color_space = JCS_EXT_BGRX;
-#else
-#warning "You should consider building with libjpeg-turbo"
-    st->mjpeg_cinfo.out_color_space = JCS_RGB;
-#endif
-
-#ifndef SPICE_QUALITY
-    st->mjpeg_cinfo.dct_method = JDCT_IFAST;
-    st->mjpeg_cinfo.do_fancy_upsampling = FALSE;
-    st->mjpeg_cinfo.do_block_smoothing = FALSE;
-    st->mjpeg_cinfo.dither_mode = JDITHER_ORDERED;
-#endif
-    // TODO: in theory should check cinfo.output_height match with our height
-    jpeg_start_decompress(&st->mjpeg_cinfo);
-    /* rec_outbuf_height is the recommended size of the output buffer we
-     * pass to libjpeg for optimum performance
-     */
-    if (st->mjpeg_cinfo.rec_outbuf_height > G_N_ELEMENTS(lines)) {
-        jpeg_abort_decompress(&st->mjpeg_cinfo);
-        g_return_if_reached();
-    }
-
-    while (st->mjpeg_cinfo.output_scanline < st->mjpeg_cinfo.output_height) {
-        /* only used when JCS_EXTENSIONS is undefined */
-        G_GNUC_UNUSED unsigned int lines_read;
-
-        for (unsigned int j = 0; j < st->mjpeg_cinfo.rec_outbuf_height; j++) {
-            lines[j] = dest;
-#ifdef JCS_EXTENSIONS
-            dest += 4 * width;
-#else
-            dest += 3 * width;
-#endif
-        }
-        lines_read = jpeg_read_scanlines(&st->mjpeg_cinfo, lines,
-                                st->mjpeg_cinfo.rec_outbuf_height);
-#ifndef JCS_EXTENSIONS
-        {
-            uint8_t *s = lines[0];
-            uint32_t *d = (uint32_t *)s;
-
-            if (back_compat) {
-                for (unsigned int j = lines_read * width; j > 0; ) {
-                    j -= 1; // reverse order, bad for cache?
-                    d[j] = s[j * 3 + 0] |
-                        s[j * 3 + 1] << 8 |
-                        s[j * 3 + 2] << 16;
-                }
-            } else {
-                for (unsigned int j = lines_read * width; j > 0; ) {
-                    j -= 1; // reverse order, bad for cache?
-                    d[j] = s[j * 3 + 0] << 16 |
-                        s[j * 3 + 1] << 8 |
-                        s[j * 3 + 2];
-                }
-            }
-        }
-#endif
-        dest = &st->out_frame[st->mjpeg_cinfo.output_scanline * width * 4];
-    }
-    jpeg_finish_decompress(&st->mjpeg_cinfo);
-}
-
-G_GNUC_INTERNAL
-void stream_mjpeg_cleanup(display_stream *st)
-{
-    jpeg_destroy_decompress(&st->mjpeg_cinfo);
-    g_free(st->out_frame);
-    st->out_frame = NULL;
-}
diff --git a/gtk/channel-display-priv.h b/gtk/channel-display-priv.h
deleted file mode 100644
index 71f5d17..0000000
--- a/gtk/channel-display-priv.h
+++ /dev/null
@@ -1,113 +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 CHANNEL_DISPLAY_PRIV_H_
-# define CHANNEL_DISPLAY_PRIV_H_
-
-#include <pixman.h>
-#ifdef WIN32
-/* We need some hacks to avoid warnings from the jpeg headers */
-#define HAVE_BOOLEAN
-#define XMD_H
-#endif
-#include <jpeglib.h>
-
-#include "common/canvas_utils.h"
-#include "client_sw_canvas.h"
-#include "common/ring.h"
-#include "common/quic.h"
-#include "common/rop3.h"
-
-G_BEGIN_DECLS
-
-
-typedef struct display_surface {
-    guint32                     surface_id;
-    bool                        primary;
-    enum SpiceSurfaceFmt        format;
-    int                         width, height, stride, size;
-    int                         shmid;
-    uint8_t                     *data;
-    SpiceCanvas                 *canvas;
-    SpiceGlzDecoder             *glz_decoder;
-    SpiceZlibDecoder            *zlib_decoder;
-    SpiceJpegDecoder            *jpeg_decoder;
-} display_surface;
-
-typedef struct drops_sequence_stats {
-    uint32_t len;
-    uint32_t start_mm_time;
-    uint32_t duration;
-} drops_sequence_stats;
-
-typedef struct display_stream {
-    SpiceMsgIn                  *msg_create;
-    SpiceMsgIn                  *msg_clip;
-    SpiceMsgIn                  *msg_data;
-
-    /* from messages */
-    display_surface             *surface;
-    SpiceClip                   *clip;
-    QRegion                     region;
-    int                         have_region;
-    int                         codec;
-
-    /* mjpeg decoder */
-    struct jpeg_source_mgr         mjpeg_src;
-    struct jpeg_decompress_struct  mjpeg_cinfo;
-    struct jpeg_error_mgr          mjpeg_jerr;
-
-    uint8_t                     *out_frame;
-    GQueue                      *msgq;
-    guint                       timeout;
-    SpiceChannel                *channel;
-
-    /* stats */
-    uint32_t             first_frame_mm_time;
-    uint32_t             num_drops_on_receive;
-    uint64_t             arrive_late_time;
-    uint32_t             num_drops_on_playback;
-    uint32_t             num_input_frames;
-    drops_sequence_stats cur_drops_seq_stats;
-    GArray               *drops_seqs_stats_arr;
-    uint32_t             num_drops_seqs;
-
-    uint32_t             playback_sync_drops_seq_len;
-
-    /* playback quality report to server */
-    gboolean report_is_active;
-    uint32_t report_id;
-    uint32_t report_max_window;
-    uint32_t report_timeout;
-    uint64_t report_start_time;
-    uint32_t report_start_frame_time;
-    uint32_t report_num_frames;
-    uint32_t report_num_drops;
-    uint32_t report_drops_seq_len;
-} display_stream;
-
-void stream_get_dimensions(display_stream *st, int *width, int *height);
-uint32_t stream_get_current_frame(display_stream *st, uint8_t **data);
-
-/* channel-display-mjpeg.c */
-void stream_mjpeg_init(display_stream *st);
-void stream_mjpeg_data(display_stream *st);
-void stream_mjpeg_cleanup(display_stream *st);
-
-G_END_DECLS
-
-#endif // CHANNEL_DISPLAY_PRIV_H_
diff --git a/gtk/channel-display.c b/gtk/channel-display.c
deleted file mode 100644
index efe2259..0000000
--- a/gtk/channel-display.c
+++ /dev/null
@@ -1,1789 +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"
-
-#ifdef HAVE_SYS_TYPES_H
-#include <sys/types.h>
-#endif
-
-#ifdef HAVE_SYS_SHM_H
-#include <sys/shm.h>
-#endif
-
-#ifdef HAVE_SYS_IPC_H
-#include <sys/ipc.h>
-#endif
-
-#include "glib-compat.h"
-#include "spice-client.h"
-#include "spice-common.h"
-
-#include "spice-marshal.h"
-#include "spice-channel-priv.h"
-#include "spice-session-priv.h"
-#include "channel-display-priv.h"
-#include "decode.h"
-
-/**
- * SECTION:channel-display
- * @short_description: remote display area
- * @title: Display Channel
- * @section_id:
- * @see_also: #SpiceChannel, and the GTK widget #SpiceDisplay
- * @stability: Stable
- * @include: channel-display.h
- *
- * A class that handles the rendering of the remote display and inform
- * of its updates.
- *
- * The creation of the main graphic buffer is signaled with
- * #SpiceDisplayChannel::display-primary-create.
- *
- * The update of regions is notified by
- * #SpiceDisplayChannel::display-invalidate signals.
- */
-
-#define SPICE_DISPLAY_CHANNEL_GET_PRIVATE(obj)                                  \
-    (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_DISPLAY_CHANNEL, SpiceDisplayChannelPrivate))
-
-#define MONITORS_MAX 256
-
-struct _SpiceDisplayChannelPrivate {
-    GHashTable                  *surfaces;
-    display_surface             *primary;
-    display_cache               *images;
-    display_cache               *palettes;
-    SpiceImageCache             image_cache;
-    SpicePaletteCache           palette_cache;
-    SpiceImageSurfaces          image_surfaces;
-    SpiceGlzDecoderWindow       *glz_window;
-    display_stream              **streams;
-    int                         nstreams;
-    gboolean                    mark;
-    guint                       mark_false_event_id;
-    GArray                      *monitors;
-    guint                       monitors_max;
-    gboolean                    enable_adaptive_streaming;
-#ifdef G_OS_WIN32
-    HDC dc;
-#endif
-};
-
-G_DEFINE_TYPE(SpiceDisplayChannel, spice_display_channel, SPICE_TYPE_CHANNEL)
-
-/* Properties */
-enum {
-    PROP_0,
-    PROP_WIDTH,
-    PROP_HEIGHT,
-    PROP_MONITORS,
-    PROP_MONITORS_MAX
-};
-
-enum {
-    SPICE_DISPLAY_PRIMARY_CREATE,
-    SPICE_DISPLAY_PRIMARY_DESTROY,
-    SPICE_DISPLAY_INVALIDATE,
-    SPICE_DISPLAY_MARK,
-
-    SPICE_DISPLAY_LAST_SIGNAL,
-};
-
-static guint signals[SPICE_DISPLAY_LAST_SIGNAL];
-
-static void spice_display_channel_up(SpiceChannel *channel);
-static void channel_set_handlers(SpiceChannelClass *klass);
-
-static void clear_surfaces(SpiceChannel *channel, gboolean keep_primary);
-static void clear_streams(SpiceChannel *channel);
-static display_surface *find_surface(SpiceDisplayChannelPrivate *c, guint32 surface_id);
-static gboolean display_stream_render(display_stream *st);
-static void spice_display_channel_reset(SpiceChannel *channel, gboolean migrating);
-static void spice_display_channel_reset_capabilities(SpiceChannel *channel);
-static void destroy_canvas(display_surface *surface);
-static void _msg_in_unref_func(gpointer data, gpointer user_data);
-static void display_session_mm_time_reset_cb(SpiceSession *session, gpointer data);
-
-/* ------------------------------------------------------------------ */
-
-static void spice_display_channel_dispose(GObject *object)
-{
-    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(object)->priv;
-
-    if (c->mark_false_event_id != 0) {
-        g_source_remove(c->mark_false_event_id);
-        c->mark_false_event_id = 0;
-    }
-
-    if (G_OBJECT_CLASS(spice_display_channel_parent_class)->dispose)
-        G_OBJECT_CLASS(spice_display_channel_parent_class)->dispose(object);
-}
-
-static void spice_display_channel_finalize(GObject *object)
-{
-    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(object)->priv;
-
-    g_clear_pointer(&c->monitors, g_array_unref);
-    clear_surfaces(SPICE_CHANNEL(object), FALSE);
-    g_hash_table_unref(c->surfaces);
-    clear_streams(SPICE_CHANNEL(object));
-    g_clear_pointer(&c->palettes, cache_unref);
-
-    if (G_OBJECT_CLASS(spice_display_channel_parent_class)->finalize)
-        G_OBJECT_CLASS(spice_display_channel_parent_class)->finalize(object);
-}
-
-static void spice_display_channel_constructed(GObject *object)
-{
-    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(object)->priv;
-    SpiceSession *s = spice_channel_get_session(SPICE_CHANNEL(object));
-
-    g_return_if_fail(s != NULL);
-    spice_session_get_caches(s, &c->images, &c->glz_window);
-    c->palettes = cache_new(g_free);
-
-    g_return_if_fail(c->glz_window != NULL);
-    g_return_if_fail(c->images != NULL);
-    g_return_if_fail(c->palettes != NULL);
-
-    c->monitors = g_array_new(FALSE, TRUE, sizeof(SpiceDisplayMonitorConfig));
-    spice_g_signal_connect_object(s, "mm-time-reset",
-                                  G_CALLBACK(display_session_mm_time_reset_cb),
-                                  SPICE_CHANNEL(object), 0);
-
-
-    if (G_OBJECT_CLASS(spice_display_channel_parent_class)->constructed)
-        G_OBJECT_CLASS(spice_display_channel_parent_class)->constructed(object);
-}
-
-
-static void spice_display_get_property(GObject    *object,
-                                       guint       prop_id,
-                                       GValue     *value,
-                                       GParamSpec *pspec)
-{
-    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(object)->priv;
-
-    switch (prop_id) {
-    case PROP_WIDTH: {
-        g_value_set_uint(value, c->primary ? c->primary->width : 0);
-        break;
-    }
-    case PROP_HEIGHT: {
-        g_value_set_uint(value, c->primary ? c->primary->height : 0);
-        break;
-    }
-    case PROP_MONITORS: {
-        g_value_set_boxed(value, c->monitors);
-        break;
-    }
-    case PROP_MONITORS_MAX: {
-        g_value_set_uint(value, c->monitors_max);
-        break;
-    }
-    default:
-        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
-        break;
-    }
-}
-
-static void spice_display_set_property(GObject      *object,
-                                       guint         prop_id,
-                                       const GValue *value,
-                                       GParamSpec   *pspec)
-{
-    switch (prop_id) {
-    default:
-        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
-        break;
-    }
-}
-
-/* main or coroutine context */
-static void spice_display_channel_reset(SpiceChannel *channel, gboolean migrating)
-{
-    /* palettes, images, and glz_window are cleared in the session */
-    clear_streams(channel);
-    clear_surfaces(channel, TRUE);
-
-    SPICE_CHANNEL_CLASS(spice_display_channel_parent_class)->channel_reset(channel, migrating);
-}
-
-static void spice_display_channel_class_init(SpiceDisplayChannelClass *klass)
-{
-    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
-    SpiceChannelClass *channel_class = SPICE_CHANNEL_CLASS(klass);
-
-    gobject_class->finalize     = spice_display_channel_finalize;
-    gobject_class->dispose      = spice_display_channel_dispose;
-    gobject_class->get_property = spice_display_get_property;
-    gobject_class->set_property = spice_display_set_property;
-    gobject_class->constructed = spice_display_channel_constructed;
-
-    channel_class->channel_up   = spice_display_channel_up;
-    channel_class->channel_reset = spice_display_channel_reset;
-    channel_class->channel_reset_capabilities = spice_display_channel_reset_capabilities;
-
-    g_object_class_install_property
-        (gobject_class, PROP_HEIGHT,
-         g_param_spec_uint("height",
-                           "Display height",
-                           "The primary surface height",
-                           0, G_MAXUINT, 0,
-                           G_PARAM_READABLE |
-                           G_PARAM_STATIC_STRINGS));
-
-    g_object_class_install_property
-        (gobject_class, PROP_WIDTH,
-         g_param_spec_uint("width",
-                           "Display width",
-                           "The primary surface width",
-                           0, G_MAXUINT, 0,
-                           G_PARAM_READABLE |
-                           G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceDisplayChannel:monitors:
-     *
-     * Current monitors configuration.
-     *
-     * Since: 0.13
-     */
-    g_object_class_install_property
-        (gobject_class, PROP_MONITORS,
-         g_param_spec_boxed("monitors",
-                            "Display monitors",
-                            "The monitors configuration",
-                            G_TYPE_ARRAY,
-                            G_PARAM_READABLE |
-                            G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceDisplayChannel:monitors-max:
-     *
-     * The maximum number of monitors the server or guest supports.
-     * May change during client lifetime, for instance guest may
-     * reboot or dynamically adjust this.
-     *
-     * Since: 0.13
-     */
-    g_object_class_install_property
-        (gobject_class, PROP_MONITORS_MAX,
-         g_param_spec_uint("monitors-max",
-                           "Max display monitors",
-                           "The current maximum number of monitors",
-                           1, MONITORS_MAX, 1,
-                           G_PARAM_READABLE |
-                           G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceDisplayChannel::display-primary-create:
-     * @display: the #SpiceDisplayChannel that emitted the signal
-     * @format: %SPICE_SURFACE_FMT_32_xRGB or %SPICE_SURFACE_FMT_16_555;
-     * @width: width resolution
-     * @height: height resolution
-     * @stride: the buffer stride ("width" padding)
-     * @shmid: identifier of the shared memory segment associated with
-     * the @imgdata, or -1 if not shm
-     * @imgdata: pointer to surface buffer
-     *
-     * The #SpiceDisplayChannel::display-primary-create signal
-     * provides main display buffer data.
-     **/
-    signals[SPICE_DISPLAY_PRIMARY_CREATE] =
-        g_signal_new("display-primary-create",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_FIRST,
-                     G_STRUCT_OFFSET(SpiceDisplayChannelClass,
-                                     display_primary_create),
-                     NULL, NULL,
-                     g_cclosure_user_marshal_VOID__INT_INT_INT_INT_INT_POINTER,
-                     G_TYPE_NONE,
-                     6,
-                     G_TYPE_INT, G_TYPE_INT, G_TYPE_INT,
-                     G_TYPE_INT, G_TYPE_INT, G_TYPE_POINTER);
-
-    /**
-     * SpiceDisplayChannel::display-primary-destroy:
-     * @display: the #SpiceDisplayChannel that emitted the signal
-     *
-     * The #SpiceDisplayChannel::display-primary-destroy signal is
-     * emitted when the primary surface is freed and should not be
-     * accessed anymore.
-     **/
-    signals[SPICE_DISPLAY_PRIMARY_DESTROY] =
-        g_signal_new("display-primary-destroy",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_FIRST,
-                     G_STRUCT_OFFSET(SpiceDisplayChannelClass,
-                                     display_primary_destroy),
-                     NULL, NULL,
-                     g_cclosure_marshal_VOID__VOID,
-                     G_TYPE_NONE,
-                     0);
-
-    /**
-     * SpiceDisplayChannel::display-invalidate:
-     * @display: the #SpiceDisplayChannel that emitted the signal
-     * @x: x position
-     * @y: y position
-     * @width: width
-     * @height: height
-     *
-     * The #SpiceDisplayChannel::display-invalidate signal is emitted
-     * when the rectangular region x/y/w/h of the primary buffer is
-     * updated.
-     **/
-    signals[SPICE_DISPLAY_INVALIDATE] =
-        g_signal_new("display-invalidate",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_FIRST,
-                     G_STRUCT_OFFSET(SpiceDisplayChannelClass,
-                                     display_invalidate),
-                     NULL, NULL,
-                     g_cclosure_user_marshal_VOID__INT_INT_INT_INT,
-                     G_TYPE_NONE,
-                     4,
-                     G_TYPE_INT, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT);
-
-    /**
-     * SpiceDisplayChannel::display-mark:
-     * @display: the #SpiceDisplayChannel that emitted the signal
-     * @mark: %TRUE when the display mark has been received
-     *
-     * The #SpiceDisplayChannel::display-mark signal is emitted when
-     * the %RED_DISPLAY_MARK command is received, and the display
-     * should be exposed.
-     **/
-    signals[SPICE_DISPLAY_MARK] =
-        g_signal_new("display-mark",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_FIRST,
-                     G_STRUCT_OFFSET(SpiceDisplayChannelClass,
-                                     display_mark),
-                     NULL, NULL,
-                     g_cclosure_marshal_VOID__INT,
-                     G_TYPE_NONE,
-                     1,
-                     G_TYPE_INT);
-
-    g_type_class_add_private(klass, sizeof(SpiceDisplayChannelPrivate));
-
-    sw_canvas_init();
-    quic_init();
-    rop3_init();
-    channel_set_handlers(SPICE_CHANNEL_CLASS(klass));
-}
-
-/**
- * spice_display_get_primary:
- * @channel:
- * @surface_id:
- * @primary:
- *
- * Retrieve primary display surface @surface_id.
- *
- * Returns: %TRUE if the primary surface was found and its details
- * collected in @primary.
- */
-gboolean spice_display_get_primary(SpiceChannel *channel, guint32 surface_id,
-                                   SpiceDisplayPrimary *primary)
-{
-    g_return_val_if_fail(SPICE_IS_DISPLAY_CHANNEL(channel), FALSE);
-    g_return_val_if_fail(primary != NULL, FALSE);
-
-    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
-    display_surface *surface = find_surface(c, surface_id);
-
-    if (surface == NULL)
-        return FALSE;
-
-    g_return_val_if_fail(surface->primary, FALSE);
-
-    primary->format = surface->format;
-    primary->width = surface->width;
-    primary->height = surface->height;
-    primary->stride = surface->stride;
-    primary->shmid = surface->shmid;
-    primary->data = surface->data;
-    primary->marked = c->mark;
-    CHANNEL_DEBUG(channel, "get primary %p", primary->data);
-
-    return TRUE;
-}
-
-/* ------------------------------------------------------------------ */
-
-static void image_put(SpiceImageCache *cache, uint64_t id, pixman_image_t *image)
-{
-    SpiceDisplayChannelPrivate *c =
-        SPICE_CONTAINEROF(cache, SpiceDisplayChannelPrivate, image_cache);
-
-    cache_add(c->images, id, pixman_image_ref(image));
-}
-
-typedef struct _WaitImageData
-{
-    gboolean lossy;
-    SpiceImageCache *cache;
-    uint64_t id;
-    pixman_image_t *image;
-} WaitImageData;
-
-static gboolean wait_image(gpointer data)
-{
-    gboolean lossy;
-    WaitImageData *wait = data;
-    SpiceDisplayChannelPrivate *c =
-        SPICE_CONTAINEROF(wait->cache, SpiceDisplayChannelPrivate, image_cache);
-    pixman_image_t *image = cache_find_lossy(c->images, wait->id, &lossy);
-
-    if (!image || (lossy && !wait->lossy))
-        return FALSE;
-
-    wait->image = pixman_image_ref(image);
-
-    return TRUE;
-}
-
-static pixman_image_t *image_get(SpiceImageCache *cache, uint64_t id)
-{
-    WaitImageData wait = {
-        .lossy = TRUE,
-        .cache = cache,
-        .id = id,
-        .image = NULL
-    };
-    if (!g_coroutine_condition_wait(g_coroutine_self(), wait_image, &wait))
-        SPICE_DEBUG("wait image got cancelled");
-
-    return wait.image;
-}
-
-static void palette_put(SpicePaletteCache *cache, SpicePalette *palette)
-{
-    SpiceDisplayChannelPrivate *c =
-        SPICE_CONTAINEROF(cache, SpiceDisplayChannelPrivate, palette_cache);
-
-    cache_add(c->palettes, palette->unique,
-              g_memdup(palette, sizeof(SpicePalette) +
-                       palette->num_ents * sizeof(palette->ents[0])));
-}
-
-static SpicePalette *palette_get(SpicePaletteCache *cache, uint64_t id)
-{
-    SpiceDisplayChannelPrivate *c =
-        SPICE_CONTAINEROF(cache, SpiceDisplayChannelPrivate, palette_cache);
-
-    /* here the returned pointer is weak, no ref given to caller.  it
-     * seems spice canvas usage is exclusively temporary, so it's ok.
-     * palette_release is a noop. */
-    return cache_find(c->palettes, id);
-}
-
-static void palette_remove(SpicePaletteCache *cache, uint64_t id)
-{
-    SpiceDisplayChannelPrivate *c =
-        SPICE_CONTAINEROF(cache, SpiceDisplayChannelPrivate, palette_cache);
-
-    cache_remove(c->palettes, id);
-}
-
-static void palette_release(SpicePaletteCache *cache, SpicePalette *palette)
-{
-    /* there is no refcount of palette, see palette_get() */
-}
-
-static void image_put_lossy(SpiceImageCache *cache, uint64_t id,
-                            pixman_image_t *surface)
-{
-    SpiceDisplayChannelPrivate *c =
-        SPICE_CONTAINEROF(cache, SpiceDisplayChannelPrivate, image_cache);
-
-#ifndef NDEBUG
-    g_warn_if_fail(cache_find(c->images, id) == NULL);
-#endif
-
-    cache_add_lossy(c->images, id, pixman_image_ref(surface), TRUE);
-}
-
-static void image_replace_lossy(SpiceImageCache *cache, uint64_t id,
-                                pixman_image_t *surface)
-{
-    image_put(cache, id, surface);
-}
-
-static pixman_image_t* image_get_lossless(SpiceImageCache *cache, uint64_t id)
-{
-    WaitImageData wait = {
-        .lossy = FALSE,
-        .cache = cache,
-        .id = id,
-        .image = NULL
-    };
-    if (!g_coroutine_condition_wait(g_coroutine_self(), wait_image, &wait))
-        SPICE_DEBUG("wait lossless got cancelled");
-
-    return wait.image;
-}
-
-static SpiceCanvas *surfaces_get(SpiceImageSurfaces *surfaces,
-                                 uint32_t surface_id)
-{
-    SpiceDisplayChannelPrivate *c =
-        SPICE_CONTAINEROF(surfaces, SpiceDisplayChannelPrivate, image_surfaces);
-
-    display_surface *s =
-        find_surface(c, surface_id);
-
-    return s ? s->canvas : NULL;
-}
-
-static SpiceImageCacheOps image_cache_ops = {
-    .put = image_put,
-    .get = image_get,
-
-    .put_lossy = image_put_lossy,
-    .replace_lossy = image_replace_lossy,
-    .get_lossless = image_get_lossless,
-};
-
-static SpicePaletteCacheOps palette_cache_ops = {
-    .put     = palette_put,
-    .get     = palette_get,
-    .release = palette_release,
-};
-
-static SpiceImageSurfacesOps image_surfaces_ops = {
-    .get = surfaces_get
-};
-
-#if defined(G_OS_WIN32)
-static HDC create_compatible_dc(void)
-{
-    HDC dc = CreateCompatibleDC(NULL);
-    if (!dc) {
-        g_warning("create compatible DC failed");
-    }
-    return dc;
-}
-#endif
-
-static void spice_display_channel_reset_capabilities(SpiceChannel *channel)
-{
-    spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_DISPLAY_CAP_SIZED_STREAM);
-    spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_DISPLAY_CAP_MONITORS_CONFIG);
-    spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_DISPLAY_CAP_COMPOSITE);
-    spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_DISPLAY_CAP_A8_SURFACE);
-#ifdef USE_LZ4
-    spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_DISPLAY_CAP_LZ4_COMPRESSION);
-#endif
-    if (SPICE_DISPLAY_CHANNEL(channel)->priv->enable_adaptive_streaming) {
-        spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_DISPLAY_CAP_STREAM_REPORT);
-    }
-}
-
-static void destroy_surface(gpointer data)
-{
-    display_surface *surface = data;
-
-    destroy_canvas(surface);
-    g_slice_free(display_surface, surface);
-}
-
-static void spice_display_channel_init(SpiceDisplayChannel *channel)
-{
-    SpiceDisplayChannelPrivate *c;
-
-    c = channel->priv = SPICE_DISPLAY_CHANNEL_GET_PRIVATE(channel);
-
-    c->surfaces = g_hash_table_new_full(NULL, NULL, NULL, destroy_surface);
-    c->image_cache.ops = &image_cache_ops;
-    c->palette_cache.ops = &palette_cache_ops;
-    c->image_surfaces.ops = &image_surfaces_ops;
-#if defined(G_OS_WIN32)
-    c->dc = create_compatible_dc();
-#endif
-    c->monitors_max = 1;
-
-    if (g_getenv("SPICE_DISABLE_ADAPTIVE_STREAMING")) {
-        SPICE_DEBUG("adaptive video disabled");
-        c->enable_adaptive_streaming = FALSE;
-    } else {
-        c->enable_adaptive_streaming = TRUE;
-    }
-    spice_display_channel_reset_capabilities(SPICE_CHANNEL(channel));
-}
-
-/* ------------------------------------------------------------------ */
-
-static int create_canvas(SpiceChannel *channel, display_surface *surface)
-{
-    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
-
-    if (surface->primary) {
-        if (c->primary) {
-            if (c->primary->width == surface->width &&
-                c->primary->height == surface->height) {
-                CHANNEL_DEBUG(channel, "Reusing existing primary surface");
-                return 0;
-            }
-
-            g_coroutine_signal_emit(channel, signals[SPICE_DISPLAY_PRIMARY_DESTROY], 0);
-
-            g_hash_table_remove(c->surfaces, GINT_TO_POINTER(c->primary->surface_id));
-        }
-
-        CHANNEL_DEBUG(channel, "Create primary canvas");
-#if defined(WITH_X11) && defined(HAVE_SYS_SHM_H)
-        surface->shmid = shmget(IPC_PRIVATE, surface->size, IPC_CREAT | 0777);
-        if (surface->shmid >= 0) {
-            surface->data = shmat(surface->shmid, 0, 0);
-            if (surface->data == NULL) {
-                shmctl(surface->shmid, IPC_RMID, 0);
-                surface->shmid = -1;
-            }
-        }
-#else
-        surface->shmid = -1;
-#endif
-    } else {
-        surface->shmid = -1;
-    }
-
-    if (surface->shmid == -1)
-        surface->data = g_malloc0(surface->size);
-
-    g_return_val_if_fail(c->glz_window, 0);
-
-    g_warn_if_fail(surface->canvas == NULL);
-    g_warn_if_fail(surface->glz_decoder == NULL);
-    g_warn_if_fail(surface->zlib_decoder == NULL);
-    g_warn_if_fail(surface->jpeg_decoder == NULL);
-
-    surface->glz_decoder = glz_decoder_new(c->glz_window);
-    surface->zlib_decoder = zlib_decoder_new();
-    surface->jpeg_decoder = jpeg_decoder_new();
-
-    surface->canvas = canvas_create_for_data(surface->width,
-                                             surface->height,
-                                             surface->format,
-                                             surface->data,
-                                             surface->stride,
-                                             &c->image_cache,
-                                             &c->palette_cache,
-                                             &c->image_surfaces,
-                                             surface->glz_decoder,
-                                             surface->jpeg_decoder,
-                                             surface->zlib_decoder);
-
-    g_return_val_if_fail(surface->canvas != NULL, 0);
-    g_hash_table_insert(c->surfaces, GINT_TO_POINTER(surface->surface_id), surface);
-
-    if (surface->primary) {
-        g_warn_if_fail(c->primary == NULL);
-        c->primary = surface;
-        g_coroutine_signal_emit(channel, signals[SPICE_DISPLAY_PRIMARY_CREATE], 0,
-                                surface->format, surface->width, surface->height,
-                                surface->stride, surface->shmid, surface->data);
-
-        if (!spice_channel_test_capability(channel, SPICE_DISPLAY_CAP_MONITORS_CONFIG)) {
-            g_array_set_size(c->monitors, 1);
-            SpiceDisplayMonitorConfig *config = &g_array_index(c->monitors, SpiceDisplayMonitorConfig, 0);
-            config->x = config->y = 0;
-            config->width = surface->width;
-            config->height = surface->height;
-            g_coroutine_object_notify(G_OBJECT(channel), "monitors");
-        }
-    }
-
-    return 0;
-}
-
-static void destroy_canvas(display_surface *surface)
-{
-    if (surface == NULL)
-        return;
-
-    glz_decoder_destroy(surface->glz_decoder);
-    zlib_decoder_destroy(surface->zlib_decoder);
-    jpeg_decoder_destroy(surface->jpeg_decoder);
-
-    if (surface->shmid == -1) {
-        g_free(surface->data);
-    }
-#ifdef HAVE_SYS_SHM_H
-    else {
-        shmdt(surface->data);
-        shmctl(surface->shmid, IPC_RMID, 0);
-    }
-#endif
-    surface->shmid = -1;
-    surface->data = NULL;
-
-    surface->canvas->ops->destroy(surface->canvas);
-    surface->canvas = NULL;
-}
-
-static display_surface *find_surface(SpiceDisplayChannelPrivate *c, guint32 surface_id)
-{
-    if (c->primary && c->primary->surface_id == surface_id)
-        return c->primary;
-
-    return g_hash_table_lookup(c->surfaces, GINT_TO_POINTER(surface_id));
-}
-
-/* main or coroutine context */
-static void clear_surfaces(SpiceChannel *channel, gboolean keep_primary)
-{
-    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
-    GHashTableIter iter;
-    display_surface *surface;
-
-    if (!keep_primary) {
-        c->primary = NULL;
-        g_coroutine_signal_emit(channel, signals[SPICE_DISPLAY_PRIMARY_DESTROY], 0);
-    }
-
-    g_hash_table_iter_init(&iter, c->surfaces);
-    while (g_hash_table_iter_next(&iter, NULL, (gpointer*)&surface)) {
-
-        if (keep_primary && surface->primary) {
-            CHANNEL_DEBUG(channel, "keeping existing primary surface, migration or reset");
-            continue;
-        }
-
-        g_hash_table_iter_remove(&iter);
-    }
-}
-
-/* coroutine context */
-static void emit_invalidate(SpiceChannel *channel, SpiceRect *bbox)
-{
-    g_coroutine_signal_emit(channel, signals[SPICE_DISPLAY_INVALIDATE], 0,
-                            bbox->left, bbox->top,
-                            bbox->right - bbox->left,
-                            bbox->bottom - bbox->top);
-}
-
-/* ------------------------------------------------------------------ */
-
-/* coroutine context */
-static void spice_display_channel_up(SpiceChannel *channel)
-{
-    SpiceMsgOut *out;
-    SpiceSession *s = spice_channel_get_session(channel);
-    SpiceMsgcDisplayInit init;
-    int cache_size;
-    int glz_window_size;
-
-    g_object_get(s,
-                 "cache-size", &cache_size,
-                 "glz-window-size", &glz_window_size,
-                 NULL);
-    CHANNEL_DEBUG(channel, "%s: cache_size %d, glz_window_size %d (bytes)", __FUNCTION__,
-                  cache_size, glz_window_size);
-    init.pixmap_cache_id = 1;
-    init.glz_dictionary_id = 1;
-    init.pixmap_cache_size = cache_size / 4; /* pixels */
-    init.glz_dictionary_window_size = glz_window_size / 4; /* pixels */
-    out = spice_msg_out_new(channel, SPICE_MSGC_DISPLAY_INIT);
-    out->marshallers->msgc_display_init(out->marshaller, &init);
-    spice_msg_out_send_internal(out);
-
-    /* if we are not using monitors config, notify of existence of
-       this monitor */
-    if (channel->priv->channel_id != 0)
-        g_coroutine_object_notify(G_OBJECT(channel), "monitors");
-}
-
-#define DRAW(type) {                                                    \
-        display_surface *surface =                                      \
-            find_surface(SPICE_DISPLAY_CHANNEL(channel)->priv,          \
-                op->base.surface_id);                                   \
-        g_return_if_fail(surface != NULL);                              \
-        surface->canvas->ops->draw_##type(surface->canvas, &op->base.box, \
-                                          &op->base.clip, &op->data);   \
-        if (surface->primary) {                                         \
-            emit_invalidate(channel, &op->base.box);                    \
-        }                                                               \
-}
-
-/* coroutine context */
-static void display_handle_mode(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
-    SpiceMsgDisplayMode *mode = spice_msg_in_parsed(in);
-    display_surface *surface;
-
-    g_warn_if_fail(c->mark == FALSE);
-
-    surface = g_slice_new0(display_surface);
-    surface->format  = mode->bits == 32 ?
-        SPICE_SURFACE_FMT_32_xRGB : SPICE_SURFACE_FMT_16_555;
-    surface->width   = mode->x_res;
-    surface->height  = mode->y_res;
-    surface->stride  = surface->width * 4;
-    surface->size    = surface->height * surface->stride;
-    surface->primary = true;
-    create_canvas(channel, surface);
-}
-
-/* coroutine context */
-static void display_handle_mark(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
-
-    CHANNEL_DEBUG(channel, "%s", __FUNCTION__);
-    g_return_if_fail(c->primary != NULL);
-#ifdef EXTRA_CHECKS
-    g_warn_if_fail(c->mark == FALSE);
-#endif
-
-    c->mark = TRUE;
-    g_coroutine_signal_emit(channel, signals[SPICE_DISPLAY_MARK], 0, TRUE);
-}
-
-/* coroutine context */
-static void display_handle_reset(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
-    display_surface *surface = c->primary;
-
-    CHANNEL_DEBUG(channel, "%s: TODO detach_from_screen", __FUNCTION__);
-
-    if (surface != NULL)
-        surface->canvas->ops->clear(surface->canvas);
-
-    cache_clear(c->palettes);
-
-    c->mark = FALSE;
-    g_coroutine_signal_emit(channel, signals[SPICE_DISPLAY_MARK], 0, FALSE);
-}
-
-/* coroutine context */
-static void display_handle_copy_bits(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceMsgDisplayCopyBits *op = spice_msg_in_parsed(in);
-    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
-    display_surface *surface = find_surface(c, op->base.surface_id);
-
-    g_return_if_fail(surface != NULL);
-    surface->canvas->ops->copy_bits(surface->canvas, &op->base.box,
-                                    &op->base.clip, &op->src_pos);
-    if (surface->primary) {
-        emit_invalidate(channel, &op->base.box);
-    }
-}
-
-/* coroutine context */
-static void display_handle_inv_list(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
-    SpiceResourceList *list = spice_msg_in_parsed(in);
-    int i;
-
-    for (i = 0; i < list->count; i++) {
-        guint64 id = list->resources[i].id;
-
-        switch (list->resources[i].type) {
-        case SPICE_RES_TYPE_PIXMAP:
-            if (!cache_remove(c->images, id))
-                SPICE_DEBUG("fail to remove image %" G_GUINT64_FORMAT, id);
-            break;
-        default:
-            g_return_if_reached();
-            break;
-        }
-    }
-}
-
-/* coroutine context */
-static void display_handle_inv_pixmap_all(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
-
-    spice_channel_handle_wait_for_channels(channel, in);
-    cache_clear(c->images);
-}
-
-/* coroutine context */
-static void display_handle_inv_palette(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
-    SpiceMsgDisplayInvalOne* op = spice_msg_in_parsed(in);
-
-    palette_remove(&c->palette_cache, op->id);
-}
-
-/* coroutine context */
-static void display_handle_inv_palette_all(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
-
-    cache_clear(c->palettes);
-}
-
-/* ------------------------------------------------------------------ */
-
-static void display_update_stream_region(display_stream *st)
-{
-    int i;
-
-    switch (st->clip->type) {
-    case SPICE_CLIP_TYPE_RECTS:
-        region_clear(&st->region);
-        for (i = 0; i < st->clip->rects->num_rects; i++) {
-            region_add(&st->region, &st->clip->rects->rects[i]);
-        }
-        st->have_region = true;
-        break;
-    case SPICE_CLIP_TYPE_NONE:
-    default:
-        st->have_region = false;
-        break;
-    }
-}
-
-/* coroutine context */
-static void display_handle_stream_create(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
-    SpiceMsgDisplayStreamCreate *op = spice_msg_in_parsed(in);
-    display_stream *st;
-
-    CHANNEL_DEBUG(channel, "%s: id %d", __FUNCTION__, op->id);
-
-    if (op->id >= c->nstreams) {
-        int n = c->nstreams;
-        if (!c->nstreams) {
-            c->nstreams = 1;
-        }
-        while (op->id >= c->nstreams) {
-            c->nstreams *= 2;
-        }
-        c->streams = realloc(c->streams, c->nstreams * sizeof(c->streams[0]));
-        memset(c->streams + n, 0, (c->nstreams - n) * sizeof(c->streams[0]));
-    }
-    g_return_if_fail(c->streams[op->id] == NULL);
-    c->streams[op->id] = g_new0(display_stream, 1);
-    st = c->streams[op->id];
-
-    st->msg_create = in;
-    spice_msg_in_ref(in);
-    st->clip = &op->clip;
-    st->codec = op->codec_type;
-    st->surface = find_surface(c, op->surface_id);
-    st->msgq = g_queue_new();
-    st->channel = channel;
-    st->drops_seqs_stats_arr = g_array_new(FALSE, FALSE, sizeof(drops_sequence_stats));
-
-    region_init(&st->region);
-    display_update_stream_region(st);
-
-    switch (st->codec) {
-    case SPICE_VIDEO_CODEC_TYPE_MJPEG:
-        stream_mjpeg_init(st);
-        break;
-    }
-}
-
-/* coroutine or main context */
-static gboolean display_stream_schedule(display_stream *st)
-{
-    SpiceSession *session = spice_channel_get_session(st->channel);
-    guint32 time, d;
-    SpiceStreamDataHeader *op;
-    SpiceMsgIn *in;
-
-    SPICE_DEBUG("%s", __FUNCTION__);
-    if (st->timeout || !session)
-        return TRUE;
-
-    time = spice_session_get_mm_time(session);
-    in = g_queue_peek_head(st->msgq);
-
-    if (in == NULL) {
-        return TRUE;
-    }
-
-    op = spice_msg_in_parsed(in);
-    if (time < op->multi_media_time) {
-        d = op->multi_media_time - time;
-        SPICE_DEBUG("scheduling next stream render in %u ms", d);
-        st->timeout = g_timeout_add(d, (GSourceFunc)display_stream_render, st);
-        return TRUE;
-    } else {
-        SPICE_DEBUG("%s: rendering too late by %u ms (ts: %u, mmtime: %u), dropping ",
-                    __FUNCTION__, time - op->multi_media_time,
-                    op->multi_media_time, time);
-        in = g_queue_pop_head(st->msgq);
-        spice_msg_in_unref(in);
-        st->num_drops_on_playback++;
-        if (g_queue_get_length(st->msgq) == 0)
-            return TRUE;
-    }
-
-    return FALSE;
-}
-
-static SpiceRect *stream_get_dest(display_stream *st)
-{
-    if (st->msg_data == NULL ||
-        spice_msg_in_type(st->msg_data) != SPICE_MSG_DISPLAY_STREAM_DATA_SIZED) {
-        SpiceMsgDisplayStreamCreate *info = spice_msg_in_parsed(st->msg_create);
-
-        return &info->dest;
-    } else {
-        SpiceMsgDisplayStreamDataSized *op = spice_msg_in_parsed(st->msg_data);
-
-        return &op->dest;
-   }
-
-}
-
-static uint32_t stream_get_flags(display_stream *st)
-{
-    SpiceMsgDisplayStreamCreate *info = spice_msg_in_parsed(st->msg_create);
-
-    return info->flags;
-}
-
-G_GNUC_INTERNAL
-uint32_t stream_get_current_frame(display_stream *st, uint8_t **data)
-{
-    if (st->msg_data == NULL) {
-        *data = NULL;
-        return 0;
-    }
-
-    if (spice_msg_in_type(st->msg_data) == SPICE_MSG_DISPLAY_STREAM_DATA) {
-        SpiceMsgDisplayStreamData *op = spice_msg_in_parsed(st->msg_data);
-
-        *data = op->data;
-        return op->data_size;
-    } else {
-        SpiceMsgDisplayStreamDataSized *op = spice_msg_in_parsed(st->msg_data);
-
-        g_return_val_if_fail(spice_msg_in_type(st->msg_data) ==
-                             SPICE_MSG_DISPLAY_STREAM_DATA_SIZED, 0);
-        *data = op->data;
-        return op->data_size;
-   }
-
-}
-
-G_GNUC_INTERNAL
-void stream_get_dimensions(display_stream *st, int *width, int *height)
-{
-    g_return_if_fail(width != NULL);
-    g_return_if_fail(height != NULL);
-
-    if (st->msg_data == NULL ||
-        spice_msg_in_type(st->msg_data) != SPICE_MSG_DISPLAY_STREAM_DATA_SIZED) {
-        SpiceMsgDisplayStreamCreate *info = spice_msg_in_parsed(st->msg_create);
-
-        *width = info->stream_width;
-        *height = info->stream_height;
-    } else {
-        SpiceMsgDisplayStreamDataSized *op = spice_msg_in_parsed(st->msg_data);
-
-        *width = op->width;
-        *height = op->height;
-   }
-}
-
-/* main context */
-static gboolean display_stream_render(display_stream *st)
-{
-    SpiceMsgIn *in;
-
-    st->timeout = 0;
-    do {
-        in = g_queue_pop_head(st->msgq);
-
-        g_return_val_if_fail(in != NULL, FALSE);
-
-        st->msg_data = in;
-        switch (st->codec) {
-        case SPICE_VIDEO_CODEC_TYPE_MJPEG:
-            stream_mjpeg_data(st);
-            break;
-        }
-
-        if (st->out_frame) {
-            int width;
-            int height;
-            SpiceRect *dest;
-            uint8_t *data;
-            int stride;
-
-            stream_get_dimensions(st, &width, &height);
-            dest = stream_get_dest(st);
-
-            data = st->out_frame;
-            stride = width * sizeof(uint32_t);
-            if (!(stream_get_flags(st) & SPICE_STREAM_FLAGS_TOP_DOWN)) {
-                data += stride * (height - 1);
-                stride = -stride;
-            }
-
-            st->surface->canvas->ops->put_image(
-                st->surface->canvas,
-#ifdef G_OS_WIN32
-                SPICE_DISPLAY_CHANNEL(st->channel)->priv->dc,
-#endif
-                dest, data,
-                width, height, stride,
-                st->have_region ? &st->region : NULL);
-
-            if (st->surface->primary)
-                g_signal_emit(st->channel, signals[SPICE_DISPLAY_INVALIDATE], 0,
-                    dest->left, dest->top,
-                    dest->right - dest->left,
-                    dest->bottom - dest->top);
-        }
-
-        st->msg_data = NULL;
-        spice_msg_in_unref(in);
-
-        in = g_queue_peek_head(st->msgq);
-        if (in == NULL)
-            break;
-
-        if (display_stream_schedule(st))
-            return FALSE;
-    } while (1);
-
-    return FALSE;
-}
-/* after a sequence of 3 drops, push a report to the server, even
- * if the report window is bigger */
-#define STREAM_REPORT_DROP_SEQ_LEN_LIMIT 3
-
-static void display_update_stream_report(SpiceDisplayChannel *channel, uint32_t stream_id,
-                                         uint32_t frame_time, int32_t latency)
-{
-    display_stream *st = channel->priv->streams[stream_id];
-    guint64 now;
-
-    if (!st->report_is_active) {
-        return;
-    }
-    now = g_get_monotonic_time();
-
-    if (st->report_num_frames == 0) {
-        st->report_start_frame_time = frame_time;
-        st->report_start_time = now;
-    }
-    st->report_num_frames++;
-
-    if (latency < 0) { // drop
-        st->report_num_drops++;
-        st->report_drops_seq_len++;
-    } else {
-        st->report_drops_seq_len = 0;
-    }
-
-    if (st->report_num_frames >= st->report_max_window ||
-        now - st->report_start_time >= st->report_timeout ||
-        st->report_drops_seq_len >= STREAM_REPORT_DROP_SEQ_LEN_LIMIT) {
-        SpiceMsgcDisplayStreamReport report;
-        SpiceSession *session = spice_channel_get_session(SPICE_CHANNEL(channel));
-        SpiceMsgOut *msg;
-
-        report.stream_id = stream_id;
-        report.unique_id = st->report_id;
-        report.start_frame_mm_time = st->report_start_frame_time;
-        report.end_frame_mm_time = frame_time;
-        report.num_frames = st->report_num_frames;
-        report.num_drops = st-> report_num_drops;
-        report.last_frame_delay = latency;
-        if (spice_session_is_playback_active(session)) {
-            report.audio_delay = spice_session_get_playback_latency(session);
-        } else {
-            report.audio_delay = UINT_MAX;
-        }
-
-        msg = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_DISPLAY_STREAM_REPORT);
-        msg->marshallers->msgc_display_stream_report(msg->marshaller, &report);
-        spice_msg_out_send(msg);
-
-        st->report_start_time = 0;
-        st->report_start_frame_time = 0;
-        st->report_num_frames = 0;
-        st->report_num_drops = 0;
-        st->report_drops_seq_len = 0;
-    }
-}
-
-static void display_stream_reset_rendering_timer(display_stream *st)
-{
-    SPICE_DEBUG("%s", __FUNCTION__);
-    if (st->timeout != 0) {
-        g_source_remove(st->timeout);
-        st->timeout = 0;
-    }
-    while (!display_stream_schedule(st)) {
-    }
-}
-
-/*
- * Migration can occur between 2 spice-servers with different mm-times.
- * Then, the following cases can happen after migration completes:
- * (We refer to src/dst-time as the mm-times on the src/dst servers):
- *
- * (case 1) Frames with time ~= dst-time arrive to the client before the
- *          playback-channel updates the session's mm-time (i.e., the mm_time
- *          of the session is still based on the src-time).
- *     (a) If src-time < dst-time:
- *         display_stream_schedule schedules the next rendering to
- *         ~(dst-time - src-time) milliseconds from now.
- *         Since we assume monotonic mm_time, display_stream_schedule,
- *         returns immediately when a rendering timeout
- *         has already been set, and doesn't update the timeout,
- *         even after the mm_time is updated.
- *         When src-time << dst-time, a significant video frames loss will occur.
- *     (b) If src-time > dst-time
- *         Frames will be dropped till the mm-time will be updated.
- * (case 2) mm-time is synced with dst-time, but frames that were in the command
- *         ring during migration still arrive (such frames hold src-time).
- *    (a) If src-time < dst-time
- *        The frames that hold src-time will be dropped, since their
- *        mm_time < session-mm_time. But all the new frames that are generated in
- *        the driver after migration, will be rendered appropriately.
- *    (b) If src-time > dst-time
- *        Similar consequences as in 1 (a)
- * case 2 is less likely, since at takes at least 20 frames till the dst-server re-identifies
- * the video stream and starts sending stream data
- *
- * display_session_mm_time_reset_cb handles case 1.a, and
- * display_stream_test_frames_mm_time_reset handles case 2.b
- */
-
-/* main context */
-static void display_session_mm_time_reset_cb(SpiceSession *session, gpointer data)
-{
-    SpiceChannel *channel = data;
-    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
-    guint i;
-
-    CHANNEL_DEBUG(channel, "%s", __FUNCTION__);
-
-    for (i = 0; i < c->nstreams; i++) {
-        display_stream *st;
-
-        if (c->streams[i] == NULL) {
-            continue;
-        }
-        SPICE_DEBUG("%s: stream-id %d", __FUNCTION__, i);
-        st = c->streams[i];
-        display_stream_reset_rendering_timer(st);
-    }
-}
-
-/* coroutine context */
-static void display_stream_test_frames_mm_time_reset(display_stream *st,
-                                                     SpiceMsgIn *new_frame_msg,
-                                                     guint32 mm_time)
-{
-    SpiceStreamDataHeader *tail_op, *new_op;
-    SpiceMsgIn *tail_msg;
-
-    SPICE_DEBUG("%s", __FUNCTION__);
-    g_return_if_fail(new_frame_msg != NULL);
-    tail_msg = g_queue_peek_tail(st->msgq);
-    if (!tail_msg) {
-        return;
-    }
-    tail_op = spice_msg_in_parsed(tail_msg);
-    new_op = spice_msg_in_parsed(new_frame_msg);
-
-    if (new_op->multi_media_time < tail_op->multi_media_time) {
-        SPICE_DEBUG("new-frame-time < tail-frame-time (%u < %u):"
-                    " reseting stream, id %d",
-                    new_op->multi_media_time,
-                    tail_op->multi_media_time,
-                    new_op->id);
-        g_queue_foreach(st->msgq, _msg_in_unref_func, NULL);
-        g_queue_clear(st->msgq);
-        display_stream_reset_rendering_timer(st);
-    }
-}
-
-#define STREAM_PLAYBACK_SYNC_DROP_SEQ_LEN_LIMIT 5
-
-/* coroutine context */
-static void display_handle_stream_data(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
-    SpiceStreamDataHeader *op = spice_msg_in_parsed(in);
-    display_stream *st;
-    guint32 mmtime;
-    int32_t latency;
-
-    g_return_if_fail(c != NULL);
-    g_return_if_fail(c->streams != NULL);
-    g_return_if_fail(c->nstreams > op->id);
-
-    st =  c->streams[op->id];
-    mmtime = spice_session_get_mm_time(spice_channel_get_session(channel));
-
-    if (spice_msg_in_type(in) == SPICE_MSG_DISPLAY_STREAM_DATA_SIZED) {
-        CHANNEL_DEBUG(channel, "stream %d contains sized data", op->id);
-    }
-
-    if (op->multi_media_time == 0) {
-        g_critical("Received frame with invalid 0 timestamp! perhaps wrong graphic driver?");
-        op->multi_media_time = mmtime + 100; /* workaround... */
-    }
-
-    if (!st->num_input_frames) {
-        st->first_frame_mm_time = op->multi_media_time;
-    }
-    st->num_input_frames++;
-
-    latency = op->multi_media_time - mmtime;
-    if (latency < 0) {
-        CHANNEL_DEBUG(channel, "stream data too late by %u ms (ts: %u, mmtime: %u), dropping",
-                      mmtime - op->multi_media_time, op->multi_media_time, mmtime);
-        st->arrive_late_time += mmtime - op->multi_media_time;
-        st->num_drops_on_receive++;
-
-        if (!st->cur_drops_seq_stats.len) {
-            st->cur_drops_seq_stats.start_mm_time = op->multi_media_time;
-        }
-        st->cur_drops_seq_stats.len++;
-        st->playback_sync_drops_seq_len++;
-    } else {
-        CHANNEL_DEBUG(channel, "video latency: %d", latency);
-        spice_msg_in_ref(in);
-        display_stream_test_frames_mm_time_reset(st, in, mmtime);
-        g_queue_push_tail(st->msgq, in);
-        while (!display_stream_schedule(st)) {
-        }
-        if (st->cur_drops_seq_stats.len) {
-            st->cur_drops_seq_stats.duration = op->multi_media_time -
-                                               st->cur_drops_seq_stats.start_mm_time;
-            g_array_append_val(st->drops_seqs_stats_arr, st->cur_drops_seq_stats);
-            memset(&st->cur_drops_seq_stats, 0, sizeof(st->cur_drops_seq_stats));
-            st->num_drops_seqs++;
-        }
-        st->playback_sync_drops_seq_len = 0;
-    }
-    if (c->enable_adaptive_streaming) {
-        display_update_stream_report(SPICE_DISPLAY_CHANNEL(channel), op->id,
-                                     op->multi_media_time, latency);
-        if (st->playback_sync_drops_seq_len >= STREAM_PLAYBACK_SYNC_DROP_SEQ_LEN_LIMIT) {
-            spice_session_sync_playback_latency(spice_channel_get_session(channel));
-            st->playback_sync_drops_seq_len = 0;
-        }
-    }
-}
-
-/* coroutine context */
-static void display_handle_stream_clip(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
-    SpiceMsgDisplayStreamClip *op = spice_msg_in_parsed(in);
-    display_stream *st;
-
-    g_return_if_fail(c != NULL);
-    g_return_if_fail(c->streams != NULL);
-    g_return_if_fail(c->nstreams > op->id);
-
-    st = c->streams[op->id];
-
-    if (st->msg_clip) {
-        spice_msg_in_unref(st->msg_clip);
-    }
-    spice_msg_in_ref(in);
-    st->msg_clip = in;
-    st->clip = &op->clip;
-    display_update_stream_region(st);
-}
-
-static void _msg_in_unref_func(gpointer data, gpointer user_data)
-{
-    spice_msg_in_unref(data);
-}
-
-static void destroy_stream(SpiceChannel *channel, int id)
-{
-    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
-    display_stream *st;
-    guint64 drops_duration_total = 0;
-    guint32 num_out_frames;
-    int i;
-
-    g_return_if_fail(c != NULL);
-    g_return_if_fail(c->streams != NULL);
-    g_return_if_fail(c->nstreams > id);
-
-    st = c->streams[id];
-    if (!st)
-        return;
-
-    num_out_frames = st->num_input_frames - st->num_drops_on_receive - st->num_drops_on_playback;
-    CHANNEL_DEBUG(channel, "%s: id=%d #in-frames=%d out/in=%.2f "
-        "#drops-on-receive=%d avg-late-time(ms)=%.2f "
-        "#drops-on-playback=%d", __FUNCTION__,
-        id,
-        st->num_input_frames,
-        num_out_frames / (double)st->num_input_frames,
-        st->num_drops_on_receive,
-        st->num_drops_on_receive ? st->arrive_late_time / ((double)st->num_drops_on_receive): 0,
-        st->num_drops_on_playback);
-    if (st->num_drops_seqs) {
-        CHANNEL_DEBUG(channel, "%s: #drops-sequences=%u ==>", __FUNCTION__, st->num_drops_seqs);
-    }
-    for (i = 0; i < st->num_drops_seqs; i++) {
-            drops_sequence_stats *stats = &g_array_index(st->drops_seqs_stats_arr,
-                                                         drops_sequence_stats,
-                                                         i);
-            drops_duration_total += stats->duration;
-            CHANNEL_DEBUG(channel, "%s: \t len=%u start-ms=%u duration-ms=%u", __FUNCTION__,
-                                   stats->len,
-                                   stats->start_mm_time - st->first_frame_mm_time,
-                                   stats->duration);
-    }
-    if (st->num_drops_seqs) {
-        CHANNEL_DEBUG(channel, "%s: drops-total-duration=%"G_GUINT64_FORMAT" ==>", __FUNCTION__, drops_duration_total);
-    }
-
-    g_array_free(st->drops_seqs_stats_arr, TRUE);
-
-    switch (st->codec) {
-    case SPICE_VIDEO_CODEC_TYPE_MJPEG:
-        stream_mjpeg_cleanup(st);
-        break;
-    }
-
-    if (st->msg_clip)
-        spice_msg_in_unref(st->msg_clip);
-    spice_msg_in_unref(st->msg_create);
-
-    g_queue_foreach(st->msgq, _msg_in_unref_func, NULL);
-    g_queue_free(st->msgq);
-    if (st->timeout != 0)
-        g_source_remove(st->timeout);
-    g_free(st);
-    c->streams[id] = NULL;
-}
-
-static void clear_streams(SpiceChannel *channel)
-{
-    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
-    int i;
-
-    for (i = 0; i < c->nstreams; i++) {
-        destroy_stream(channel, i);
-    }
-    g_free(c->streams);
-    c->streams = NULL;
-    c->nstreams = 0;
-}
-
-/* coroutine context */
-static void display_handle_stream_destroy(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceMsgDisplayStreamDestroy *op = spice_msg_in_parsed(in);
-
-    g_return_if_fail(op != NULL);
-    CHANNEL_DEBUG(channel, "%s: id %d", __FUNCTION__, op->id);
-    destroy_stream(channel, op->id);
-}
-
-/* coroutine context */
-static void display_handle_stream_destroy_all(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    clear_streams(channel);
-}
-
-/* coroutine context */
-static void display_handle_stream_activate_report(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
-    SpiceMsgDisplayStreamActivateReport *op = spice_msg_in_parsed(in);
-    display_stream *st;
-
-    g_return_if_fail(c != NULL);
-    g_return_if_fail(c->streams != NULL);
-    g_return_if_fail(c->nstreams > op->stream_id);
-
-    st = c->streams[op->stream_id];
-    g_return_if_fail(st != NULL);
-
-    st->report_is_active = TRUE;
-    st->report_id = op->unique_id;
-    st->report_max_window = op->max_window_size;
-    st->report_timeout = op->timeout_ms * 1000;
-    st->report_start_time = 0;
-    st->report_start_frame_time = 0;
-    st->report_num_frames = 0;
-    st->report_num_drops = 0;
-    st->report_drops_seq_len = 0;
-}
-
-/* ------------------------------------------------------------------ */
-
-/* coroutine context */
-static void display_handle_draw_fill(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceMsgDisplayDrawFill *op = spice_msg_in_parsed(in);
-    DRAW(fill);
-}
-
-/* coroutine context */
-static void display_handle_draw_opaque(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceMsgDisplayDrawOpaque *op = spice_msg_in_parsed(in);
-    DRAW(opaque);
-}
-
-/* coroutine context */
-static void display_handle_draw_copy(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceMsgDisplayDrawCopy *op = spice_msg_in_parsed(in);
-    DRAW(copy);
-}
-
-/* coroutine context */
-static void display_handle_draw_blend(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceMsgDisplayDrawBlend *op = spice_msg_in_parsed(in);
-    DRAW(blend);
-}
-
-/* coroutine context */
-static void display_handle_draw_blackness(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceMsgDisplayDrawBlackness *op = spice_msg_in_parsed(in);
-    DRAW(blackness);
-}
-
-static void display_handle_draw_whiteness(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceMsgDisplayDrawWhiteness *op = spice_msg_in_parsed(in);
-    DRAW(whiteness);
-}
-
-/* coroutine context */
-static void display_handle_draw_invers(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceMsgDisplayDrawInvers *op = spice_msg_in_parsed(in);
-    DRAW(invers);
-}
-
-/* coroutine context */
-static void display_handle_draw_rop3(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceMsgDisplayDrawRop3 *op = spice_msg_in_parsed(in);
-    DRAW(rop3);
-}
-
-/* coroutine context */
-static void display_handle_draw_stroke(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceMsgDisplayDrawStroke *op = spice_msg_in_parsed(in);
-    DRAW(stroke);
-}
-
-/* coroutine context */
-static void display_handle_draw_text(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceMsgDisplayDrawText *op = spice_msg_in_parsed(in);
-    DRAW(text);
-}
-
-/* coroutine context */
-static void display_handle_draw_transparent(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceMsgDisplayDrawTransparent *op = spice_msg_in_parsed(in);
-    DRAW(transparent);
-}
-
-/* coroutine context */
-static void display_handle_draw_alpha_blend(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceMsgDisplayDrawAlphaBlend *op = spice_msg_in_parsed(in);
-    DRAW(alpha_blend);
-}
-
-/* coroutine context */
-static void display_handle_draw_composite(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceMsgDisplayDrawComposite *op = spice_msg_in_parsed(in);
-    DRAW(composite);
-}
-
-/* coroutine context */
-static void display_handle_surface_create(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
-    SpiceMsgSurfaceCreate *create = spice_msg_in_parsed(in);
-    display_surface *surface = g_slice_new0(display_surface);
-
-    surface->surface_id = create->surface_id;
-    surface->format = create->format;
-    surface->width  = create->width;
-    surface->height = create->height;
-    surface->stride = create->width * 4;
-    surface->size   = surface->height * surface->stride;
-
-    if (create->flags & SPICE_SURFACE_FLAGS_PRIMARY) {
-        SPICE_DEBUG("primary flags: %d", create->flags);
-        surface->primary = true;
-        create_canvas(channel, surface);
-        if (c->mark_false_event_id != 0) {
-            g_source_remove(c->mark_false_event_id);
-            c->mark_false_event_id = FALSE;
-        }
-    } else {
-        surface->primary = false;
-        create_canvas(channel, surface);
-    }
-}
-
-static gboolean display_mark_false(gpointer data)
-{
-    SpiceChannel *channel = data;
-    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
-
-    c->mark = FALSE;
-    g_signal_emit(channel, signals[SPICE_DISPLAY_MARK], 0, FALSE);
-
-    c->mark_false_event_id = 0;
-    return FALSE;
-}
-
-/* coroutine context */
-static void display_handle_surface_destroy(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceMsgSurfaceDestroy *destroy = spice_msg_in_parsed(in);
-    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
-    display_surface *surface;
-
-    g_return_if_fail(destroy != NULL);
-
-    surface = find_surface(c, destroy->surface_id);
-    if (surface == NULL) {
-        /* this is not a problem in spicec, it happens as well and returns.. */
-        /* g_warn_if_reached(); */
-        return;
-    }
-    if (surface->primary) {
-        int id = spice_channel_get_channel_id(channel);
-        CHANNEL_DEBUG(channel, "%d: FIXME primary destroy, but is display really disabled?", id);
-        /* this is done with a timeout in spicec as well, it's *ugly* */
-        if (id != 0 && c->mark_false_event_id == 0) {
-            c->mark_false_event_id = g_timeout_add_seconds(1, display_mark_false, channel);
-        }
-        c->primary = NULL;
-        g_coroutine_signal_emit(channel, signals[SPICE_DISPLAY_PRIMARY_DESTROY], 0);
-    }
-
-    g_hash_table_remove(c->surfaces, GINT_TO_POINTER(surface->surface_id));
-}
-
-#define CLAMP_CHECK(x, low, high)  (((x) > (high)) ? TRUE : (((x) < (low)) ? TRUE : FALSE))
-
-/* coroutine context */
-static void display_handle_monitors_config(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceMsgDisplayMonitorsConfig *config = spice_msg_in_parsed(in);
-    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
-    guint i;
-
-    g_return_if_fail(config != NULL);
-    g_return_if_fail(config->count > 0);
-
-    CHANNEL_DEBUG(channel, "monitors config: n: %d/%d", config->count, config->max_allowed);
-
-    c->monitors_max = config->max_allowed;
-    if (CLAMP_CHECK(c->monitors_max, 1, MONITORS_MAX)) {
-        g_warning("MonitorConfig max_allowed is not within permitted range, clamping");
-        c->monitors_max = CLAMP(c->monitors_max, 1, MONITORS_MAX);
-    }
-
-    if (CLAMP_CHECK(config->count, 1, c->monitors_max)) {
-        g_warning("MonitorConfig count is not within permitted range, clamping");
-        config->count = CLAMP(config->count, 1, c->monitors_max);
-    }
-
-    c->monitors = g_array_set_size(c->monitors, config->count);
-
-    for (i = 0; i < config->count; i++) {
-        SpiceDisplayMonitorConfig *mc = &g_array_index(c->monitors, SpiceDisplayMonitorConfig, i);
-        SpiceHead *head = &config->heads[i];
-        CHANNEL_DEBUG(channel, "monitor id: %u, surface id: %u, +%u+%u-%ux%u",
-                    head->id, head->surface_id,
-                    head->x, head->y, head->width, head->height);
-        mc->id = head->id;
-        mc->surface_id = head->surface_id;
-        mc->x = head->x;
-        mc->y = head->y;
-        mc->width = head->width;
-        mc->height = head->height;
-    }
-
-    g_coroutine_object_notify(G_OBJECT(channel), "monitors");
-}
-
-static void channel_set_handlers(SpiceChannelClass *klass)
-{
-    static const spice_msg_handler handlers[] = {
-        [ SPICE_MSG_DISPLAY_MODE ]               = display_handle_mode,
-        [ SPICE_MSG_DISPLAY_MARK ]               = display_handle_mark,
-        [ SPICE_MSG_DISPLAY_RESET ]              = display_handle_reset,
-        [ SPICE_MSG_DISPLAY_COPY_BITS ]          = display_handle_copy_bits,
-        [ SPICE_MSG_DISPLAY_INVAL_LIST ]         = display_handle_inv_list,
-        [ SPICE_MSG_DISPLAY_INVAL_ALL_PIXMAPS ]  = display_handle_inv_pixmap_all,
-        [ SPICE_MSG_DISPLAY_INVAL_PALETTE ]      = display_handle_inv_palette,
-        [ SPICE_MSG_DISPLAY_INVAL_ALL_PALETTES ] = display_handle_inv_palette_all,
-
-        [ SPICE_MSG_DISPLAY_STREAM_CREATE ]      = display_handle_stream_create,
-        [ SPICE_MSG_DISPLAY_STREAM_DATA ]        = display_handle_stream_data,
-        [ SPICE_MSG_DISPLAY_STREAM_CLIP ]        = display_handle_stream_clip,
-        [ SPICE_MSG_DISPLAY_STREAM_DESTROY ]     = display_handle_stream_destroy,
-        [ SPICE_MSG_DISPLAY_STREAM_DESTROY_ALL ] = display_handle_stream_destroy_all,
-        [ SPICE_MSG_DISPLAY_STREAM_DATA_SIZED ]  = display_handle_stream_data,
-        [ SPICE_MSG_DISPLAY_STREAM_ACTIVATE_REPORT ] = display_handle_stream_activate_report,
-
-        [ SPICE_MSG_DISPLAY_DRAW_FILL ]          = display_handle_draw_fill,
-        [ SPICE_MSG_DISPLAY_DRAW_OPAQUE ]        = display_handle_draw_opaque,
-        [ SPICE_MSG_DISPLAY_DRAW_COPY ]          = display_handle_draw_copy,
-        [ SPICE_MSG_DISPLAY_DRAW_BLEND ]         = display_handle_draw_blend,
-        [ SPICE_MSG_DISPLAY_DRAW_BLACKNESS ]     = display_handle_draw_blackness,
-        [ SPICE_MSG_DISPLAY_DRAW_WHITENESS ]     = display_handle_draw_whiteness,
-        [ SPICE_MSG_DISPLAY_DRAW_INVERS ]        = display_handle_draw_invers,
-        [ SPICE_MSG_DISPLAY_DRAW_ROP3 ]          = display_handle_draw_rop3,
-        [ SPICE_MSG_DISPLAY_DRAW_STROKE ]        = display_handle_draw_stroke,
-        [ SPICE_MSG_DISPLAY_DRAW_TEXT ]          = display_handle_draw_text,
-        [ SPICE_MSG_DISPLAY_DRAW_TRANSPARENT ]   = display_handle_draw_transparent,
-        [ SPICE_MSG_DISPLAY_DRAW_ALPHA_BLEND ]   = display_handle_draw_alpha_blend,
-        [ SPICE_MSG_DISPLAY_DRAW_COMPOSITE ]     = display_handle_draw_composite,
-
-        [ SPICE_MSG_DISPLAY_SURFACE_CREATE ]     = display_handle_surface_create,
-        [ SPICE_MSG_DISPLAY_SURFACE_DESTROY ]    = display_handle_surface_destroy,
-
-        [ SPICE_MSG_DISPLAY_MONITORS_CONFIG ]    = display_handle_monitors_config,
-    };
-
-    spice_channel_set_handlers(klass, handlers, G_N_ELEMENTS(handlers));
-}
diff --git a/gtk/channel-display.h b/gtk/channel-display.h
deleted file mode 100644
index 88e60d9..0000000
--- a/gtk/channel-display.h
+++ /dev/null
@@ -1,102 +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_CLIENT_DISPLAY_CHANNEL_H__
-#define __SPICE_CLIENT_DISPLAY_CHANNEL_H__
-
-#include "spice-client.h"
-
-G_BEGIN_DECLS
-
-#define SPICE_TYPE_DISPLAY_CHANNEL            (spice_display_channel_get_type())
-#define SPICE_DISPLAY_CHANNEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), SPICE_TYPE_DISPLAY_CHANNEL, SpiceDisplayChannel))
-#define SPICE_DISPLAY_CHANNEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), SPICE_TYPE_DISPLAY_CHANNEL, SpiceDisplayChannelClass))
-#define SPICE_IS_DISPLAY_CHANNEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), SPICE_TYPE_DISPLAY_CHANNEL))
-#define SPICE_IS_DISPLAY_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SPICE_TYPE_DISPLAY_CHANNEL))
-#define SPICE_DISPLAY_CHANNEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), SPICE_TYPE_DISPLAY_CHANNEL, SpiceDisplayChannelClass))
-
-typedef struct _SpiceDisplayChannel SpiceDisplayChannel;
-typedef struct _SpiceDisplayChannelClass SpiceDisplayChannelClass;
-typedef struct _SpiceDisplayChannelPrivate SpiceDisplayChannelPrivate;
-
-typedef struct _SpiceDisplayMonitorConfig SpiceDisplayMonitorConfig;
-struct _SpiceDisplayMonitorConfig {
-    guint id;
-    guint surface_id;
-    guint x;
-    guint y;
-    guint width;
-    guint height;
-};
-
-typedef struct _SpiceDisplayPrimary SpiceDisplayPrimary;
-struct _SpiceDisplayPrimary {
-    enum SpiceSurfaceFmt format;
-    gint width;
-    gint height;
-    gint stride;
-    gint shmid;
-    guint8 *data;
-    gboolean marked;
-};
-
-/**
- * SpiceDisplayChannel:
- *
- * The #SpiceDisplayChannel struct is opaque and should not be accessed directly.
- */
-struct _SpiceDisplayChannel {
-    SpiceChannel parent;
-
-    /*< private >*/
-    SpiceDisplayChannelPrivate *priv;
-    /* Do not add fields to this struct */
-};
-
-/**
- * SpiceDisplayChannelClass:
- * @parent_class: Parent class.
- * @display_primary_create: Signal class handler for the #SpiceDisplayChannel::display-primary-create signal.
- * @display_primary_destroy: Signal class handler for the #SpiceDisplayChannel::display-primary-destroy signal.
- * @display_invalidate: Signal class handler for the #SpiceDisplayChannel::display-invalidate signal.
- * @display_mark: Signal class handler for the #SpiceDisplayChannel::display-mark signal.
- *
- * Class structure for #SpiceDisplayChannel.
- */
-struct _SpiceDisplayChannelClass {
-    SpiceChannelClass parent_class;
-
-    /* signals */
-    void (*display_primary_create)(SpiceChannel *channel, gint format,
-                                   gint width, gint height, gint stride,
-                                   gint shmid, gpointer data);
-    void (*display_primary_destroy)(SpiceChannel *channel);
-    void (*display_invalidate)(SpiceChannel *channel,
-                               gint x, gint y, gint w, gint h);
-    void (*display_mark)(SpiceChannel *channel,
-                         gboolean mark);
-
-    /*< private >*/
-};
-
-GType	        spice_display_channel_get_type(void);
-gboolean        spice_display_get_primary(SpiceChannel *channel, guint32 surface_id,
-                                          SpiceDisplayPrimary *primary);
-
-G_END_DECLS
-
-#endif /* __SPICE_CLIENT_DISPLAY_CHANNEL_H__ */
diff --git a/gtk/channel-inputs.c b/gtk/channel-inputs.c
deleted file mode 100644
index df1ffe1..0000000
--- a/gtk/channel-inputs.c
+++ /dev/null
@@ -1,603 +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-channel-priv.h"
-
-/**
- * SECTION:channel-inputs
- * @short_description: control the server mouse and keyboard
- * @title: Inputs Channel
- * @section_id:
- * @see_also: #SpiceChannel, and the GTK widget #SpiceDisplay
- * @stability: Stable
- * @include: channel-inputs.h
- *
- * Spice supports sending keyboard key events and keyboard leds
- * synchronization. The key events are sent using
- * spice_inputs_key_press() and spice_inputs_key_release() using
- * a modified variant of PC XT scancodes.
- *
- * Guest keyboard leds state can be manipulated with
- * spice_inputs_set_key_locks(). When key lock change, a notification
- * is emitted with #SpiceInputsChannel::inputs-modifiers signal.
- */
-
-#define SPICE_INPUTS_CHANNEL_GET_PRIVATE(obj)                                  \
-    (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_INPUTS_CHANNEL, SpiceInputsChannelPrivate))
-
-struct _SpiceInputsChannelPrivate {
-    int                         bs;
-    int                         dx, dy;
-    unsigned int                x, y, dpy;
-    int                         motion_count;
-    int                         modifiers;
-    guint32                     locks;
-};
-
-G_DEFINE_TYPE(SpiceInputsChannel, spice_inputs_channel, SPICE_TYPE_CHANNEL)
-
-/* Properties */
-enum {
-    PROP_0,
-    PROP_KEY_MODIFIERS,
-};
-
-/* Signals */
-enum {
-    SPICE_INPUTS_MODIFIERS,
-
-    SPICE_INPUTS_LAST_SIGNAL,
-};
-
-static guint signals[SPICE_INPUTS_LAST_SIGNAL];
-
-static void spice_inputs_channel_up(SpiceChannel *channel);
-static void spice_inputs_channel_reset(SpiceChannel *channel, gboolean migrating);
-static void channel_set_handlers(SpiceChannelClass *klass);
-
-/* ------------------------------------------------------------------ */
-
-static void spice_inputs_channel_init(SpiceInputsChannel *channel)
-{
-    channel->priv = SPICE_INPUTS_CHANNEL_GET_PRIVATE(channel);
-}
-
-static void spice_inputs_get_property(GObject    *object,
-                                      guint       prop_id,
-                                      GValue     *value,
-                                      GParamSpec *pspec)
-{
-    SpiceInputsChannelPrivate *c = SPICE_INPUTS_CHANNEL(object)->priv;
-
-    switch (prop_id) {
-    case PROP_KEY_MODIFIERS:
-        g_value_set_int(value, c->modifiers);
-        break;
-    default:
-        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
-        break;
-    }
-}
-
-static void spice_inputs_channel_finalize(GObject *obj)
-{
-    if (G_OBJECT_CLASS(spice_inputs_channel_parent_class)->finalize)
-        G_OBJECT_CLASS(spice_inputs_channel_parent_class)->finalize(obj);
-}
-
-static void spice_inputs_channel_class_init(SpiceInputsChannelClass *klass)
-{
-    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
-    SpiceChannelClass *channel_class = SPICE_CHANNEL_CLASS(klass);
-
-    gobject_class->finalize     = spice_inputs_channel_finalize;
-    gobject_class->get_property = spice_inputs_get_property;
-    channel_class->channel_up   = spice_inputs_channel_up;
-    channel_class->channel_reset = spice_inputs_channel_reset;
-
-    g_object_class_install_property
-        (gobject_class, PROP_KEY_MODIFIERS,
-         g_param_spec_int("key-modifiers",
-                          "Key modifiers",
-                          "Guest keyboard lock/led state",
-                          0, INT_MAX, 0,
-                          G_PARAM_READABLE |
-                          G_PARAM_STATIC_NAME |
-                          G_PARAM_STATIC_NICK |
-                          G_PARAM_STATIC_BLURB));
-
-    /**
-     * SpiceInputsChannel::inputs-modifier:
-     * @display: the #SpiceInputsChannel that emitted the signal
-     *
-     * The #SpiceInputsChannel::inputs-modifier signal is emitted when
-     * the guest keyboard locks are changed. You can read the current
-     * state from #SpiceInputsChannel:key-modifiers property.
-     **/
-    /* TODO: use notify instead? */
-    signals[SPICE_INPUTS_MODIFIERS] =
-        g_signal_new("inputs-modifiers",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_FIRST,
-                     G_STRUCT_OFFSET(SpiceInputsChannelClass, inputs_modifiers),
-                     NULL, NULL,
-                     g_cclosure_marshal_VOID__VOID,
-                     G_TYPE_NONE,
-                     0);
-
-    g_type_class_add_private(klass, sizeof(SpiceInputsChannelPrivate));
-    channel_set_handlers(SPICE_CHANNEL_CLASS(klass));
-}
-
-/* ------------------------------------------------------------------ */
-
-static SpiceMsgOut* mouse_motion(SpiceInputsChannel *channel)
-{
-    SpiceInputsChannelPrivate *c = channel->priv;
-    SpiceMsgcMouseMotion motion;
-    SpiceMsgOut *msg;
-
-    if (!c->dx && !c->dy)
-        return NULL;
-
-    motion.buttons_state = c->bs;
-    motion.dx            = c->dx;
-    motion.dy            = c->dy;
-    msg = spice_msg_out_new(SPICE_CHANNEL(channel),
-                            SPICE_MSGC_INPUTS_MOUSE_MOTION);
-    msg->marshallers->msgc_inputs_mouse_motion(msg->marshaller, &motion);
-
-    c->motion_count++;
-    c->dx = 0;
-    c->dy = 0;
-
-    return msg;
-}
-
-static SpiceMsgOut* mouse_position(SpiceInputsChannel *channel)
-{
-    SpiceInputsChannelPrivate *c = channel->priv;
-    SpiceMsgcMousePosition position;
-    SpiceMsgOut *msg;
-
-    if (c->dpy == -1)
-        return NULL;
-
-    /* CHANNEL_DEBUG(channel, "%s: +%d+%d", __FUNCTION__, c->x, c->y); */
-    position.buttons_state = c->bs;
-    position.x             = c->x;
-    position.y             = c->y;
-    position.display_id    = c->dpy;
-    msg = spice_msg_out_new(SPICE_CHANNEL(channel),
-                            SPICE_MSGC_INPUTS_MOUSE_POSITION);
-    msg->marshallers->msgc_inputs_mouse_position(msg->marshaller, &position);
-
-    c->motion_count++;
-    c->dpy = -1;
-
-    return msg;
-}
-
-/* main context */
-static void send_position(SpiceInputsChannel *channel)
-{
-    SpiceMsgOut *msg;
-
-    if (spice_channel_get_read_only(SPICE_CHANNEL(channel)))
-        return;
-
-    msg = mouse_position(channel);
-    if (!msg) /* if no motion */
-        return;
-
-    spice_msg_out_send(msg);
-}
-
-/* main context */
-static void send_motion(SpiceInputsChannel *channel)
-{
-    SpiceMsgOut *msg;
-
-    if (spice_channel_get_read_only(SPICE_CHANNEL(channel)))
-        return;
-
-    msg = mouse_motion(channel);
-    if (!msg) /* if no motion */
-        return;
-
-    spice_msg_out_send(msg);
-}
-
-/* coroutine context */
-static void inputs_handle_init(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceInputsChannelPrivate *c = SPICE_INPUTS_CHANNEL(channel)->priv;
-    SpiceMsgInputsInit *init = spice_msg_in_parsed(in);
-
-    c->modifiers = init->keyboard_modifiers;
-    g_coroutine_signal_emit(channel, signals[SPICE_INPUTS_MODIFIERS], 0);
-}
-
-/* coroutine context */
-static void inputs_handle_modifiers(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceInputsChannelPrivate *c = SPICE_INPUTS_CHANNEL(channel)->priv;
-    SpiceMsgInputsKeyModifiers *modifiers = spice_msg_in_parsed(in);
-
-    c->modifiers = modifiers->modifiers;
-    g_coroutine_signal_emit(channel, signals[SPICE_INPUTS_MODIFIERS], 0);
-}
-
-/* coroutine context */
-static void inputs_handle_ack(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceInputsChannelPrivate *c = SPICE_INPUTS_CHANNEL(channel)->priv;
-    SpiceMsgOut *msg;
-
-    c->motion_count -= SPICE_INPUT_MOTION_ACK_BUNCH;
-
-    msg = mouse_motion(SPICE_INPUTS_CHANNEL(channel));
-    if (msg) { /* if no motion, msg == NULL */
-        spice_msg_out_send_internal(msg);
-    }
-
-    msg = mouse_position(SPICE_INPUTS_CHANNEL(channel));
-    if (msg) {
-        spice_msg_out_send_internal(msg);
-    }
-}
-
-static void channel_set_handlers(SpiceChannelClass *klass)
-{
-    static const spice_msg_handler handlers[] = {
-        [ SPICE_MSG_INPUTS_INIT ]              = inputs_handle_init,
-        [ SPICE_MSG_INPUTS_KEY_MODIFIERS ]     = inputs_handle_modifiers,
-        [ SPICE_MSG_INPUTS_MOUSE_MOTION_ACK ]  = inputs_handle_ack,
-    };
-
-    spice_channel_set_handlers(klass, handlers, G_N_ELEMENTS(handlers));
-}
-
-/**
- * spice_inputs_motion:
- * @channel:
- * @dx: delta X mouse coordinates
- * @dy: delta Y mouse coordinates
- * @button_state: SPICE_MOUSE_BUTTON_MASK flags
- *
- * Change mouse position (used in SPICE_MOUSE_MODE_CLIENT).
- **/
-void spice_inputs_motion(SpiceInputsChannel *channel, gint dx, gint dy,
-                         gint button_state)
-{
-    SpiceInputsChannelPrivate *c;
-
-    g_return_if_fail(channel != NULL);
-    g_return_if_fail(SPICE_CHANNEL(channel)->priv->state != SPICE_CHANNEL_STATE_UNCONNECTED);
-    if (SPICE_CHANNEL(channel)->priv->state != SPICE_CHANNEL_STATE_READY)
-        return;
-
-    if (dx == 0 && dy == 0)
-        return;
-
-    c = channel->priv;
-    c->bs  = button_state;
-    c->dx += dx;
-    c->dy += dy;
-
-    if (c->motion_count < SPICE_INPUT_MOTION_ACK_BUNCH * 2) {
-        send_motion(channel);
-    }
-}
-
-/**
- * spice_inputs_position:
- * @channel:
- * @x: X mouse coordinates
- * @y: Y mouse coordinates
- * @display: display channel id
- * @button_state: SPICE_MOUSE_BUTTON_MASK flags
- *
- * Change mouse position (used in SPICE_MOUSE_MODE_CLIENT).
- **/
-void spice_inputs_position(SpiceInputsChannel *channel, gint x, gint y,
-                           gint display, gint button_state)
-{
-    SpiceInputsChannelPrivate *c;
-
-    g_return_if_fail(channel != NULL);
-
-    if (SPICE_CHANNEL(channel)->priv->state != SPICE_CHANNEL_STATE_READY)
-        return;
-
-    c = channel->priv;
-    c->bs  = button_state;
-    c->x   = x;
-    c->y   = y;
-    c->dpy = display;
-
-    if (c->motion_count < SPICE_INPUT_MOTION_ACK_BUNCH * 2) {
-        send_position(channel);
-    } else {
-        CHANNEL_DEBUG(channel, "over SPICE_INPUT_MOTION_ACK_BUNCH * 2, dropping");
-    }
-}
-
-/**
- * spice_inputs_button_press:
- * @channel:
- * @button: a SPICE_MOUSE_BUTTON
- * @button_state: SPICE_MOUSE_BUTTON_MASK flags
- *
- * Press a mouse button.
- **/
-void spice_inputs_button_press(SpiceInputsChannel *channel, gint button,
-                               gint button_state)
-{
-    SpiceInputsChannelPrivate *c;
-    SpiceMsgcMousePress press;
-    SpiceMsgOut *msg;
-
-    g_return_if_fail(channel != NULL);
-
-    if (SPICE_CHANNEL(channel)->priv->state != SPICE_CHANNEL_STATE_READY)
-        return;
-    if (spice_channel_get_read_only(SPICE_CHANNEL(channel)))
-        return;
-
-    c = channel->priv;
-    switch (button) {
-    case SPICE_MOUSE_BUTTON_LEFT:
-        button_state |= SPICE_MOUSE_BUTTON_MASK_LEFT;
-        break;
-    case SPICE_MOUSE_BUTTON_MIDDLE:
-        button_state |= SPICE_MOUSE_BUTTON_MASK_MIDDLE;
-        break;
-    case SPICE_MOUSE_BUTTON_RIGHT:
-        button_state |= SPICE_MOUSE_BUTTON_MASK_RIGHT;
-        break;
-    }
-
-    c->bs  = button_state;
-    send_motion(channel);
-    send_position(channel);
-
-    msg = spice_msg_out_new(SPICE_CHANNEL(channel),
-                            SPICE_MSGC_INPUTS_MOUSE_PRESS);
-    press.button = button;
-    press.buttons_state = button_state;
-    msg->marshallers->msgc_inputs_mouse_press(msg->marshaller, &press);
-    spice_msg_out_send(msg);
-}
-
-/**
- * spice_inputs_button_release:
- * @channel:
- * @button: a SPICE_MOUSE_BUTTON
- * @button_state: SPICE_MOUSE_BUTTON_MASK flags
- *
- * Release a button.
- **/
-void spice_inputs_button_release(SpiceInputsChannel *channel, gint button,
-                                 gint button_state)
-{
-    SpiceInputsChannelPrivate *c;
-    SpiceMsgcMouseRelease release;
-    SpiceMsgOut *msg;
-
-    g_return_if_fail(channel != NULL);
-
-    if (SPICE_CHANNEL(channel)->priv->state != SPICE_CHANNEL_STATE_READY)
-        return;
-    if (spice_channel_get_read_only(SPICE_CHANNEL(channel)))
-        return;
-
-    c = channel->priv;
-    switch (button) {
-    case SPICE_MOUSE_BUTTON_LEFT:
-        button_state &= ~SPICE_MOUSE_BUTTON_MASK_LEFT;
-        break;
-    case SPICE_MOUSE_BUTTON_MIDDLE:
-        button_state &= ~SPICE_MOUSE_BUTTON_MASK_MIDDLE;
-        break;
-    case SPICE_MOUSE_BUTTON_RIGHT:
-        button_state &= ~SPICE_MOUSE_BUTTON_MASK_RIGHT;
-        break;
-    }
-
-    c->bs = button_state;
-    send_motion(channel);
-    send_position(channel);
-
-    msg = spice_msg_out_new(SPICE_CHANNEL(channel),
-                            SPICE_MSGC_INPUTS_MOUSE_RELEASE);
-    release.button = button;
-    release.buttons_state = button_state;
-    msg->marshallers->msgc_inputs_mouse_release(msg->marshaller, &release);
-    spice_msg_out_send(msg);
-}
-
-/**
- * spice_inputs_key_press:
- * @channel:
- * @scancode: a PC XT (set 1) key scancode.  For scancodes with an %0xe0
- *            prefix, drop the prefix and OR the scancode with %0x100.
- *
- * Press a key.
- **/
-void spice_inputs_key_press(SpiceInputsChannel *channel, guint scancode)
-{
-    SpiceMsgcKeyDown down;
-    SpiceMsgOut *msg;
-
-    g_return_if_fail(channel != NULL);
-    g_return_if_fail(SPICE_CHANNEL(channel)->priv->state != SPICE_CHANNEL_STATE_UNCONNECTED);
-    if (SPICE_CHANNEL(channel)->priv->state != SPICE_CHANNEL_STATE_READY)
-        return;
-    if (spice_channel_get_read_only(SPICE_CHANNEL(channel)))
-        return;
-
-    down.code = spice_make_scancode(scancode, FALSE);
-    msg = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_INPUTS_KEY_DOWN);
-    msg->marshallers->msgc_inputs_key_down(msg->marshaller, &down);
-    spice_msg_out_send(msg);
-}
-
-/**
- * spice_inputs_key_release:
- * @channel:
- * @scancode: a PC XT (set 1) key scancode.  For scancodes with an %0xe0
- *            prefix, drop the prefix and OR the scancode with %0x100.
- *
- * Release a key.
- **/
-void spice_inputs_key_release(SpiceInputsChannel *channel, guint scancode)
-{
-    SpiceMsgcKeyUp up;
-    SpiceMsgOut *msg;
-
-    g_return_if_fail(channel != NULL);
-    g_return_if_fail(SPICE_CHANNEL(channel)->priv->state != SPICE_CHANNEL_STATE_UNCONNECTED);
-    if (SPICE_CHANNEL(channel)->priv->state != SPICE_CHANNEL_STATE_READY)
-        return;
-    if (spice_channel_get_read_only(SPICE_CHANNEL(channel)))
-        return;
-
-    up.code = spice_make_scancode(scancode, TRUE);
-    msg = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_INPUTS_KEY_UP);
-    msg->marshallers->msgc_inputs_key_up(msg->marshaller, &up);
-    spice_msg_out_send(msg);
-}
-
-/**
- * spice_inputs_key_press_and_release:
- * @channel:
- * @scancode: a PC XT (set 1) key scancode.  For scancodes with an %0xe0
- *            prefix, drop the prefix and OR the scancode with %0x100.
- *
- * Press and release a key event atomically (in the same message).
- *
- * Since: 0.13
- **/
-void spice_inputs_key_press_and_release(SpiceInputsChannel *input_channel, guint scancode)
-{
-    SpiceChannel *channel = SPICE_CHANNEL(input_channel);
-
-    g_return_if_fail(channel != NULL);
-    g_return_if_fail(channel->priv->state != SPICE_CHANNEL_STATE_UNCONNECTED);
-
-    if (channel->priv->state != SPICE_CHANNEL_STATE_READY)
-        return;
-    if (spice_channel_get_read_only(channel))
-        return;
-
-    if (spice_channel_test_capability(channel, SPICE_INPUTS_CAP_KEY_SCANCODE)) {
-        SpiceMsgOut *msg;
-        guint16 code;
-        guint8 *buf;
-
-        msg = spice_msg_out_new(channel, SPICE_MSGC_INPUTS_KEY_SCANCODE);
-        if (scancode < 0x100) {
-            buf = (guint8*)spice_marshaller_reserve_space(msg->marshaller, 2);
-            buf[0] = spice_make_scancode(scancode, FALSE);
-            buf[1] = spice_make_scancode(scancode, TRUE);
-        } else {
-            buf = (guint8*)spice_marshaller_reserve_space(msg->marshaller, 4);
-            code = spice_make_scancode(scancode, FALSE);
-            buf[0] = code & 0xff;
-            buf[1] = code >> 8;
-            code = spice_make_scancode(scancode, TRUE);
-            buf[2] = code & 0xff;
-            buf[3] = code >> 8;
-        }
-        spice_msg_out_send(msg);
-    } else {
-        CHANNEL_DEBUG(channel, "The server doesn't support atomic press and release");
-        spice_inputs_key_press(input_channel, scancode);
-        spice_inputs_key_release(input_channel, scancode);
-    }
-}
-
-/* main or coroutine context */
-static SpiceMsgOut* set_key_locks(SpiceInputsChannel *channel, guint locks)
-{
-    SpiceMsgcKeyModifiers modifiers;
-    SpiceMsgOut *msg;
-    SpiceInputsChannelPrivate *ic;
-    SpiceChannelPrivate *c;
-
-    g_return_val_if_fail(SPICE_IS_INPUTS_CHANNEL(channel), NULL);
-
-    ic = channel->priv;
-    c = SPICE_CHANNEL(channel)->priv;
-
-    ic->locks = locks;
-    if (c->state != SPICE_CHANNEL_STATE_READY)
-        return NULL;
-
-    msg = spice_msg_out_new(SPICE_CHANNEL(channel),
-                            SPICE_MSGC_INPUTS_KEY_MODIFIERS);
-    modifiers.modifiers = locks;
-    msg->marshallers->msgc_inputs_key_modifiers(msg->marshaller, &modifiers);
-    return msg;
-}
-
-/**
- * spice_inputs_set_key_locks:
- * @channel:
- * @locks: #SpiceInputsLock modifiers flags
- *
- * Set the keyboard locks on the guest (Caps, Num, Scroll..)
- **/
-void spice_inputs_set_key_locks(SpiceInputsChannel *channel, guint locks)
-{
-    SpiceMsgOut *msg;
-
-    if (spice_channel_get_read_only(SPICE_CHANNEL(channel)))
-        return;
-
-    msg = set_key_locks(channel, locks);
-    if (!msg) /* you can set_key_locks() even if the channel is not ready */
-        return;
-
-    spice_msg_out_send(msg); /* main -> coroutine */
-}
-
-/* coroutine context */
-static void spice_inputs_channel_up(SpiceChannel *channel)
-{
-    SpiceInputsChannelPrivate *c = SPICE_INPUTS_CHANNEL(channel)->priv;
-    SpiceMsgOut *msg;
-
-    if (spice_channel_get_read_only(channel))
-        return;
-
-    msg = set_key_locks(SPICE_INPUTS_CHANNEL(channel), c->locks);
-    spice_msg_out_send_internal(msg);
-}
-
-static void spice_inputs_channel_reset(SpiceChannel *channel, gboolean migrating)
-{
-    SpiceInputsChannelPrivate *c = SPICE_INPUTS_CHANNEL(channel)->priv;
-    c->motion_count = 0;
-
-    SPICE_CHANNEL_CLASS(spice_inputs_channel_parent_class)->channel_reset(channel, migrating);
-}
diff --git a/gtk/channel-inputs.h b/gtk/channel-inputs.h
deleted file mode 100644
index 3179a76..0000000
--- a/gtk/channel-inputs.h
+++ /dev/null
@@ -1,89 +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_CLIENT_INPUTS_CHANNEL_H__
-#define __SPICE_CLIENT_INPUTS_CHANNEL_H__
-
-#include "spice-client.h"
-
-G_BEGIN_DECLS
-
-#define SPICE_TYPE_INPUTS_CHANNEL            (spice_inputs_channel_get_type())
-#define SPICE_INPUTS_CHANNEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), SPICE_TYPE_INPUTS_CHANNEL, SpiceInputsChannel))
-#define SPICE_INPUTS_CHANNEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), SPICE_TYPE_INPUTS_CHANNEL, SpiceInputsChannelClass))
-#define SPICE_IS_INPUTS_CHANNEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), SPICE_TYPE_INPUTS_CHANNEL))
-#define SPICE_IS_INPUTS_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SPICE_TYPE_INPUTS_CHANNEL))
-#define SPICE_INPUTS_CHANNEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), SPICE_TYPE_INPUTS_CHANNEL, SpiceInputsChannelClass))
-
-typedef struct _SpiceInputsChannel SpiceInputsChannel;
-typedef struct _SpiceInputsChannelClass SpiceInputsChannelClass;
-typedef struct _SpiceInputsChannelPrivate SpiceInputsChannelPrivate;
-
-typedef enum {
-    SPICE_INPUTS_SCROLL_LOCK = (1 << 0),
-    SPICE_INPUTS_NUM_LOCK    = (1 << 1),
-    SPICE_INPUTS_CAPS_LOCK   = (1 << 2)
-} SpiceInputsLock;
-
-/**
- * SpiceInputsChannel:
- *
- * The #SpiceInputsChannel struct is opaque and should not be accessed directly.
- */
-struct _SpiceInputsChannel {
-    SpiceChannel parent;
-
-    /*< private >*/
-    SpiceInputsChannelPrivate *priv;
-    /* Do not add fields to this struct */
-};
-
-/**
- * SpiceInputsChannelClass:
- * @parent_class: Parent class.
- * @inputs_modifiers: Signal class handler for the #SpiceInputsChannel::inputs-modifiers signal.
- *
- * Class structure for #SpiceInputsChannel.
- */
-struct _SpiceInputsChannelClass {
-    SpiceChannelClass parent_class;
-
-    /* signals */
-    void (*inputs_modifiers)(SpiceChannel *channel);
-
-    /*< private >*/
-    /* Do not add fields to this struct */
-};
-
-GType spice_inputs_channel_get_type(void);
-
-void spice_inputs_motion(SpiceInputsChannel *channel, gint dx, gint dy,
-                         gint button_state);
-void spice_inputs_position(SpiceInputsChannel *channel, gint x, gint y,
-                           gint display, gint button_state);
-void spice_inputs_button_press(SpiceInputsChannel *channel, gint button,
-                               gint button_state);
-void spice_inputs_button_release(SpiceInputsChannel *channel, gint button,
-                                 gint button_state);
-void spice_inputs_key_press(SpiceInputsChannel *channel, guint scancode);
-void spice_inputs_key_release(SpiceInputsChannel *channel, guint scancode);
-void spice_inputs_set_key_locks(SpiceInputsChannel *channel, guint locks);
-void spice_inputs_key_press_and_release(SpiceInputsChannel *channel, guint scancode);
-
-G_END_DECLS
-
-#endif /* __SPICE_CLIENT_INPUTS_CHANNEL_H__ */
diff --git a/gtk/channel-main.c b/gtk/channel-main.c
deleted file mode 100644
index c55d097..0000000
--- a/gtk/channel-main.c
+++ /dev/null
@@ -1,2993 +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 <math.h>
-#include <spice/vd_agent.h>
-#include <common/rect.h>
-#include <glib/gstdio.h>
-
-#include "glib-compat.h"
-#include "spice-client.h"
-#include "spice-common.h"
-#include "spice-marshal.h"
-
-#include "spice-util-priv.h"
-#include "spice-channel-priv.h"
-#include "spice-session-priv.h"
-#include "spice-audio-priv.h"
-
-/**
- * SECTION:channel-main
- * @short_description: the main Spice channel
- * @title: Main Channel
- * @section_id:
- * @see_also: #SpiceChannel, and the GTK widget #SpiceDisplay
- * @stability: Stable
- * @include: channel-main.h
- *
- * The main channel is the Spice session control channel. It handles
- * communication initialization (channels list), migrations, mouse
- * modes, multimedia time, and agent communication.
- *
- *
- */
-
-#define SPICE_MAIN_CHANNEL_GET_PRIVATE(obj)                             \
-    (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_MAIN_CHANNEL, SpiceMainChannelPrivate))
-
-#define MAX_DISPLAY 16 /* Note must fit in a guint32, see monitors_align */
-
-typedef struct spice_migrate spice_migrate;
-
-#define FILE_XFER_CHUNK_SIZE (VD_AGENT_MAX_DATA_SIZE * 32)
-typedef struct SpiceFileXferTask {
-    uint32_t                       id;
-    gboolean                       pending;
-    GFile                          *file;
-    SpiceMainChannel               *channel;
-    GFileInputStream               *file_stream;
-    GFileCopyFlags                 flags;
-    GCancellable                   *cancellable;
-    GFileProgressCallback          progress_callback;
-    gpointer                       progress_callback_data;
-    GAsyncReadyCallback            callback;
-    gpointer                       user_data;
-    char                           buffer[FILE_XFER_CHUNK_SIZE];
-    uint64_t                       read_bytes;
-    uint64_t                       file_size;
-    GError                         *error;
-} SpiceFileXferTask;
-
-struct _SpiceMainChannelPrivate  {
-    enum SpiceMouseMode         mouse_mode;
-    bool                        agent_connected;
-    bool                        agent_caps_received;
-
-    gboolean                    agent_display_config_sent;
-    guint8                      display_color_depth;
-    gboolean                    display_disable_wallpaper:1;
-    gboolean                    display_disable_font_smooth:1;
-    gboolean                    display_disable_animation:1;
-    gboolean                    disable_display_position:1;
-    gboolean                    disable_display_align:1;
-
-    int                         agent_tokens;
-    VDAgentMessage              agent_msg; /* partial msg reconstruction */
-    guint8                      *agent_msg_data;
-    guint                       agent_msg_pos;
-    uint8_t                     agent_msg_size;
-    uint32_t                    agent_caps[VD_AGENT_CAPS_SIZE];
-    struct {
-        int                     x;
-        int                     y;
-        int                     width;
-        int                     height;
-        gboolean                enabled;
-        gboolean                enabled_set;
-    } display[MAX_DISPLAY];
-    gint                        timer_id;
-    GQueue                      *agent_msg_queue;
-    GHashTable                  *file_xfer_tasks;
-    GSList                      *flushing;
-
-    guint                       switch_host_delayed_id;
-    guint                       migrate_delayed_id;
-    spice_migrate               *migrate_data;
-    int                         max_clipboard;
-
-    gboolean                    agent_volume_playback_sync;
-    gboolean                    agent_volume_record_sync;
-    GCancellable                *cancellable_volume_info;
-};
-
-struct spice_migrate {
-    struct coroutine *from;
-    SpiceMigrationDstInfo *info;
-    SpiceSession *session;
-    guint nchannels;
-    SpiceChannel *src_channel;
-    SpiceChannel *dst_channel;
-    bool do_seamless; /* used as input and output for the seamless migration handshake.
-                         input: whether to send to the dest SPICE_MSGC_MAIN_MIGRATE_DST_DO_SEAMLESS
-                         output: whether the dest approved seamless migration
-                         (SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_ACK/NACK)
-                       */
-    uint32_t src_mig_version;
-};
-
-G_DEFINE_TYPE(SpiceMainChannel, spice_main_channel, SPICE_TYPE_CHANNEL)
-
-/* Properties */
-enum {
-    PROP_0,
-    PROP_MOUSE_MODE,
-    PROP_AGENT_CONNECTED,
-    PROP_AGENT_CAPS_0,
-    PROP_DISPLAY_DISABLE_WALLPAPER,
-    PROP_DISPLAY_DISABLE_FONT_SMOOTH,
-    PROP_DISPLAY_DISABLE_ANIMATION,
-    PROP_DISPLAY_COLOR_DEPTH,
-    PROP_DISABLE_DISPLAY_POSITION,
-    PROP_DISABLE_DISPLAY_ALIGN,
-    PROP_MAX_CLIPBOARD,
-};
-
-/* Signals */
-enum {
-    SPICE_MAIN_MOUSE_UPDATE,
-    SPICE_MAIN_AGENT_UPDATE,
-    SPICE_MAIN_CLIPBOARD,
-    SPICE_MAIN_CLIPBOARD_GRAB,
-    SPICE_MAIN_CLIPBOARD_REQUEST,
-    SPICE_MAIN_CLIPBOARD_RELEASE,
-    SPICE_MAIN_CLIPBOARD_SELECTION,
-    SPICE_MAIN_CLIPBOARD_SELECTION_GRAB,
-    SPICE_MAIN_CLIPBOARD_SELECTION_REQUEST,
-    SPICE_MAIN_CLIPBOARD_SELECTION_RELEASE,
-    SPICE_MIGRATION_STARTED,
-    SPICE_MAIN_LAST_SIGNAL,
-};
-
-static guint signals[SPICE_MAIN_LAST_SIGNAL];
-
-static void spice_main_handle_msg(SpiceChannel *channel, SpiceMsgIn *msg);
-static void channel_set_handlers(SpiceChannelClass *klass);
-static void agent_send_msg_queue(SpiceMainChannel *channel);
-static void agent_free_msg_queue(SpiceMainChannel *channel);
-static void migrate_channel_event_cb(SpiceChannel *channel, SpiceChannelEvent event,
-                                     gpointer data);
-static gboolean main_migrate_handshake_done(gpointer data);
-static void spice_main_channel_send_migration_handshake(SpiceChannel *channel);
-static void file_xfer_continue_read(SpiceFileXferTask *task);
-static void file_xfer_completed(SpiceFileXferTask *task, GError *error);
-static void file_xfer_flushed(SpiceMainChannel *channel, gboolean success);
-static void spice_main_set_max_clipboard(SpiceMainChannel *self, gint max);
-static void set_agent_connected(SpiceMainChannel *channel, gboolean connected);
-
-/* ------------------------------------------------------------------ */
-
-static const char *agent_msg_types[] = {
-    [ VD_AGENT_MOUSE_STATE             ] = "mouse state",
-    [ VD_AGENT_MONITORS_CONFIG         ] = "monitors config",
-    [ VD_AGENT_REPLY                   ] = "reply",
-    [ VD_AGENT_CLIPBOARD               ] = "clipboard",
-    [ VD_AGENT_DISPLAY_CONFIG          ] = "display config",
-    [ VD_AGENT_ANNOUNCE_CAPABILITIES   ] = "announce caps",
-    [ VD_AGENT_CLIPBOARD_GRAB          ] = "clipboard grab",
-    [ VD_AGENT_CLIPBOARD_REQUEST       ] = "clipboard request",
-    [ VD_AGENT_CLIPBOARD_RELEASE       ] = "clipboard release",
-    [ VD_AGENT_AUDIO_VOLUME_SYNC       ] = "volume-sync",
-};
-
-static const char *agent_caps[] = {
-    [ VD_AGENT_CAP_MOUSE_STATE         ] = "mouse state",
-    [ VD_AGENT_CAP_MONITORS_CONFIG     ] = "monitors config",
-    [ VD_AGENT_CAP_REPLY               ] = "reply",
-    [ VD_AGENT_CAP_CLIPBOARD           ] = "clipboard (old)",
-    [ VD_AGENT_CAP_DISPLAY_CONFIG      ] = "display config",
-    [ VD_AGENT_CAP_CLIPBOARD_BY_DEMAND ] = "clipboard",
-    [ VD_AGENT_CAP_CLIPBOARD_SELECTION ] = "clipboard selection",
-    [ VD_AGENT_CAP_SPARSE_MONITORS_CONFIG ] = "sparse monitors",
-    [ VD_AGENT_CAP_GUEST_LINEEND_LF    ] = "line-end lf",
-    [ VD_AGENT_CAP_GUEST_LINEEND_CRLF  ] = "line-end crlf",
-    [ VD_AGENT_CAP_MAX_CLIPBOARD       ] = "max-clipboard",
-    [ VD_AGENT_CAP_AUDIO_VOLUME_SYNC   ] = "volume-sync",
-};
-#define NAME(_a, _i) ((_i) < SPICE_N_ELEMENTS(_a) ? (_a[(_i)] ?: "?") : "?")
-
-/* ------------------------------------------------------------------ */
-
-static gboolean test_agent_cap(SpiceMainChannel *channel, guint32 cap)
-{
-    SpiceMainChannelPrivate *c = channel->priv;
-
-    if (!c->agent_caps_received)
-        return FALSE;
-
-    return VD_AGENT_HAS_CAPABILITY(c->agent_caps, G_N_ELEMENTS(c->agent_caps), cap);
-}
-
-static void spice_main_channel_reset_capabilties(SpiceChannel *channel)
-{
-    spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_MAIN_CAP_SEMI_SEAMLESS_MIGRATE);
-    spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_MAIN_CAP_NAME_AND_UUID);
-    spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_MAIN_CAP_AGENT_CONNECTED_TOKENS);
-    spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_MAIN_CAP_SEAMLESS_MIGRATE);
-}
-
-static void spice_main_channel_init(SpiceMainChannel *channel)
-{
-    SpiceMainChannelPrivate *c;
-
-    c = channel->priv = SPICE_MAIN_CHANNEL_GET_PRIVATE(channel);
-    c->agent_msg_queue = g_queue_new();
-    c->file_xfer_tasks = g_hash_table_new(g_direct_hash, g_direct_equal);
-    c->cancellable_volume_info = g_cancellable_new();
-
-    spice_main_channel_reset_capabilties(SPICE_CHANNEL(channel));
-}
-
-static gint spice_main_get_max_clipboard(SpiceMainChannel *self)
-{
-    g_return_val_if_fail(SPICE_IS_MAIN_CHANNEL(self), 0);
-
-    if (g_getenv("SPICE_MAX_CLIPBOARD"))
-        return atoi(g_getenv("SPICE_MAX_CLIPBOARD"));
-
-    return self->priv->max_clipboard;
-}
-
-static void spice_main_get_property(GObject    *object,
-                                    guint       prop_id,
-                                    GValue     *value,
-                                    GParamSpec *pspec)
-{
-    SpiceMainChannel *self = SPICE_MAIN_CHANNEL(object);
-    SpiceMainChannelPrivate *c = self->priv;
-
-    switch (prop_id) {
-    case PROP_MOUSE_MODE:
-        g_value_set_int(value, c->mouse_mode);
-	break;
-    case PROP_AGENT_CONNECTED:
-        g_value_set_boolean(value, c->agent_connected);
-	break;
-    case PROP_AGENT_CAPS_0:
-        g_value_set_int(value, c->agent_caps[0]);
-	break;
-    case PROP_DISPLAY_DISABLE_WALLPAPER:
-        g_value_set_boolean(value, c->display_disable_wallpaper);
-        break;
-    case PROP_DISPLAY_DISABLE_FONT_SMOOTH:
-        g_value_set_boolean(value, c->display_disable_font_smooth);
-        break;
-    case PROP_DISPLAY_DISABLE_ANIMATION:
-        g_value_set_boolean(value, c->display_disable_animation);
-        break;
-    case PROP_DISPLAY_COLOR_DEPTH:
-        g_value_set_uint(value, c->display_color_depth);
-        break;
-    case PROP_DISABLE_DISPLAY_POSITION:
-        g_value_set_boolean(value, c->disable_display_position);
-        break;
-    case PROP_DISABLE_DISPLAY_ALIGN:
-        g_value_set_boolean(value, c->disable_display_align);
-        break;
-    case PROP_MAX_CLIPBOARD:
-        g_value_set_int(value, spice_main_get_max_clipboard(self));
-        break;
-    default:
-	G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
-	break;
-    }
-}
-
-static void spice_main_set_property(GObject *gobject, guint prop_id,
-                                    const GValue *value, GParamSpec *pspec)
-{
-    SpiceMainChannel *self = SPICE_MAIN_CHANNEL(gobject);
-    SpiceMainChannelPrivate *c = self->priv;
-
-    switch (prop_id) {
-    case PROP_DISPLAY_DISABLE_WALLPAPER:
-        c->display_disable_wallpaper = g_value_get_boolean(value);
-        break;
-    case PROP_DISPLAY_DISABLE_FONT_SMOOTH:
-        c->display_disable_font_smooth = g_value_get_boolean(value);
-        break;
-    case PROP_DISPLAY_DISABLE_ANIMATION:
-        c->display_disable_animation = g_value_get_boolean(value);
-        break;
-    case PROP_DISPLAY_COLOR_DEPTH: {
-        guint color_depth = g_value_get_uint(value);
-        g_return_if_fail(color_depth % 8 == 0);
-        c->display_color_depth = color_depth;
-        break;
-    }
-    case PROP_DISABLE_DISPLAY_POSITION:
-        c->disable_display_position = g_value_get_boolean(value);
-        break;
-    case PROP_DISABLE_DISPLAY_ALIGN:
-        c->disable_display_align = g_value_get_boolean(value);
-        break;
-    case PROP_MAX_CLIPBOARD:
-        spice_main_set_max_clipboard(self, g_value_get_int(value));
-        break;
-    default:
-	G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
-	break;
-    }
-}
-
-static void spice_main_channel_dispose(GObject *obj)
-{
-    SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(obj)->priv;
-
-    if (c->timer_id) {
-        g_source_remove(c->timer_id);
-        c->timer_id = 0;
-    }
-
-    if (c->switch_host_delayed_id) {
-        g_source_remove(c->switch_host_delayed_id);
-        c->switch_host_delayed_id = 0;
-    }
-
-    if (c->migrate_delayed_id) {
-        g_source_remove(c->migrate_delayed_id);
-        c->migrate_delayed_id = 0;
-    }
-
-    g_cancellable_cancel(c->cancellable_volume_info);
-    g_clear_object(&c->cancellable_volume_info);
-
-    if (G_OBJECT_CLASS(spice_main_channel_parent_class)->dispose)
-        G_OBJECT_CLASS(spice_main_channel_parent_class)->dispose(obj);
-}
-
-static void spice_main_channel_finalize(GObject *obj)
-{
-    SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(obj)->priv;
-
-    g_free(c->agent_msg_data);
-    agent_free_msg_queue(SPICE_MAIN_CHANNEL(obj));
-    if (c->file_xfer_tasks)
-        g_hash_table_unref(c->file_xfer_tasks);
-
-    if (G_OBJECT_CLASS(spice_main_channel_parent_class)->finalize)
-        G_OBJECT_CLASS(spice_main_channel_parent_class)->finalize(obj);
-}
-
-/* coroutine context */
-static void spice_channel_iterate_write(SpiceChannel *channel)
-{
-    agent_send_msg_queue(SPICE_MAIN_CHANNEL(channel));
-
-    if (SPICE_CHANNEL_CLASS(spice_main_channel_parent_class)->iterate_write)
-        SPICE_CHANNEL_CLASS(spice_main_channel_parent_class)->iterate_write(channel);
-}
-
-/* main or coroutine context */
-static void spice_main_channel_reset_agent(SpiceMainChannel *channel)
-{
-    SpiceMainChannelPrivate *c = channel->priv;
-    GError *error;
-    GList *tasks;
-    GList *l;
-
-    c->agent_connected = FALSE;
-    c->agent_caps_received = FALSE;
-    c->agent_display_config_sent = FALSE;
-    c->agent_msg_pos = 0;
-    g_free(c->agent_msg_data);
-    c->agent_msg_data = NULL;
-    c->agent_msg_size = 0;
-
-    tasks = g_hash_table_get_values(c->file_xfer_tasks);
-    for (l = tasks; l != NULL; l = l->next) {
-        SpiceFileXferTask *task = (SpiceFileXferTask *)l->data;
-
-        error = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
-                            "Agent connection closed");
-        file_xfer_completed(task, error);
-    }
-    g_list_free(tasks);
-    file_xfer_flushed(channel, FALSE);
-}
-
-/* main or coroutine context */
-static void spice_main_channel_reset(SpiceChannel *channel, gboolean migrating)
-{
-    SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
-
-    /* This is not part of reset_agent, since the spice-server expects any
-       pending multi-chunk messages to be completed by the client, even after
-       it has send an agent-disconnected msg as that is what the original
-       spicec did. Also see the TODO in server/reds.c reds_reset_vdp() */
-    c->agent_tokens = 0;
-    agent_free_msg_queue(SPICE_MAIN_CHANNEL(channel));
-    c->agent_msg_queue = g_queue_new();
-
-    c->agent_volume_playback_sync = FALSE;
-    c->agent_volume_record_sync = FALSE;
-
-    set_agent_connected(SPICE_MAIN_CHANNEL(channel), FALSE);
-
-    SPICE_CHANNEL_CLASS(spice_main_channel_parent_class)->channel_reset(channel, migrating);
-}
-
-static void spice_main_constructed(GObject *object)
-{
-    SpiceMainChannel *self = SPICE_MAIN_CHANNEL(object);
-    SpiceMainChannelPrivate *c = self->priv;
-
-    /* update default value */
-    c->max_clipboard = spice_main_get_max_clipboard(self);
-
-    if (G_OBJECT_CLASS(spice_main_channel_parent_class)->constructed)
-        G_OBJECT_CLASS(spice_main_channel_parent_class)->constructed(object);
-}
-
-static void spice_main_channel_class_init(SpiceMainChannelClass *klass)
-{
-    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
-    SpiceChannelClass *channel_class = SPICE_CHANNEL_CLASS(klass);
-
-    gobject_class->dispose      = spice_main_channel_dispose;
-    gobject_class->finalize     = spice_main_channel_finalize;
-    gobject_class->get_property = spice_main_get_property;
-    gobject_class->set_property = spice_main_set_property;
-    gobject_class->constructed  = spice_main_constructed;
-
-    channel_class->handle_msg    = spice_main_handle_msg;
-    channel_class->iterate_write = spice_channel_iterate_write;
-    channel_class->channel_reset = spice_main_channel_reset;
-    channel_class->channel_reset_capabilities = spice_main_channel_reset_capabilties;
-    channel_class->channel_send_migration_handshake = spice_main_channel_send_migration_handshake;
-
-    /**
-     * SpiceMainChannel:mouse-mode:
-     *
-     * Spice protocol specifies two mouse modes, client mode and
-     * server mode. In client mode (%SPICE_MOUSE_MODE_CLIENT), the
-     * affective mouse is the client side mouse: the client sends
-     * mouse position within the display and the server sends mouse
-     * shape messages. In server mode (%SPICE_MOUSE_MODE_SERVER), the
-     * client sends relative mouse movements and the server sends
-     * position and shape commands.
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_MOUSE_MODE,
-         g_param_spec_int("mouse-mode",
-                          "Mouse mode",
-                          "Mouse mode",
-                          0, INT_MAX, 0,
-                          G_PARAM_READABLE |
-                          G_PARAM_STATIC_NAME |
-                          G_PARAM_STATIC_NICK |
-                          G_PARAM_STATIC_BLURB));
-
-    g_object_class_install_property
-        (gobject_class, PROP_AGENT_CONNECTED,
-         g_param_spec_boolean("agent-connected",
-                              "Agent connected",
-                              "Whether the agent is connected",
-                              FALSE,
-                              G_PARAM_READABLE |
-                              G_PARAM_STATIC_NAME |
-                              G_PARAM_STATIC_NICK |
-                              G_PARAM_STATIC_BLURB));
-
-    g_object_class_install_property
-        (gobject_class, PROP_AGENT_CAPS_0,
-         g_param_spec_int("agent-caps-0",
-                          "Agent caps 0",
-                          "Agent capability bits 0 -> 31",
-                          0, INT_MAX, 0,
-                          G_PARAM_READABLE |
-                          G_PARAM_STATIC_NAME |
-                          G_PARAM_STATIC_NICK |
-                          G_PARAM_STATIC_BLURB));
-
-    g_object_class_install_property
-        (gobject_class, PROP_DISPLAY_DISABLE_WALLPAPER,
-         g_param_spec_boolean("disable-wallpaper",
-                              "Disable guest wallpaper",
-                              "Disable guest wallpaper",
-                              FALSE,
-                              G_PARAM_READWRITE |
-                              G_PARAM_CONSTRUCT |
-                              G_PARAM_STATIC_STRINGS));
-
-    g_object_class_install_property
-        (gobject_class, PROP_DISPLAY_DISABLE_FONT_SMOOTH,
-         g_param_spec_boolean("disable-font-smooth",
-                              "Disable guest font smooth",
-                              "Disable guest font smoothing",
-                              FALSE,
-                              G_PARAM_READWRITE |
-                              G_PARAM_CONSTRUCT |
-                              G_PARAM_STATIC_STRINGS));
-
-    g_object_class_install_property
-        (gobject_class, PROP_DISPLAY_DISABLE_ANIMATION,
-         g_param_spec_boolean("disable-animation",
-                              "Disable guest animations",
-                              "Disable guest animations",
-                              FALSE,
-                              G_PARAM_READWRITE |
-                              G_PARAM_CONSTRUCT |
-                              G_PARAM_STATIC_STRINGS));
-
-    g_object_class_install_property
-        (gobject_class, PROP_DISABLE_DISPLAY_POSITION,
-         g_param_spec_boolean("disable-display-position",
-                              "Disable display position",
-                              "Disable using display position when setting monitor config",
-                              TRUE,
-                              G_PARAM_READWRITE |
-                              G_PARAM_CONSTRUCT |
-                              G_PARAM_STATIC_STRINGS));
-
-    g_object_class_install_property
-        (gobject_class, PROP_DISPLAY_COLOR_DEPTH,
-         g_param_spec_uint("color-depth",
-                           "Color depth",
-                           "Color depth", 0, 32, 0,
-                           G_PARAM_READWRITE |
-                           G_PARAM_CONSTRUCT |
-                           G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceMainChannel:disable-display-align:
-     *
-     * Disable automatic horizontal display position alignment.
-     *
-     * Since: 0.13
-     */
-    g_object_class_install_property
-        (gobject_class, PROP_DISABLE_DISPLAY_ALIGN,
-         g_param_spec_boolean("disable-display-align",
-                              "Disable display align",
-                              "Disable display position alignment",
-                              FALSE,
-                              G_PARAM_READWRITE |
-                              G_PARAM_CONSTRUCT |
-                              G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceMainChannel:max-clipboard:
-     *
-     * Maximum size of clipboard operations in bytes (default 100MB,
-     * -1 for unlimited size);
-     *
-     * Since: 0.22
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_MAX_CLIPBOARD,
-         g_param_spec_int("max-clipboard",
-                          "max clipboard",
-                          "Maximum clipboard data size",
-                          -1, G_MAXINT, 100 * 1024 * 1024,
-                          G_PARAM_READWRITE |
-                          G_PARAM_CONSTRUCT |
-                          G_PARAM_STATIC_STRINGS));
-
-    /* TODO use notify instead */
-    /**
-     * SpiceMainChannel::main-mouse-update:
-     * @main: the #SpiceMainChannel that emitted the signal
-     *
-     * Notify when the mouse mode has changed.
-     **/
-    signals[SPICE_MAIN_MOUSE_UPDATE] =
-        g_signal_new("main-mouse-update",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_FIRST,
-                     G_STRUCT_OFFSET(SpiceMainChannelClass, mouse_update),
-                     NULL, NULL,
-                     g_cclosure_marshal_VOID__VOID,
-                     G_TYPE_NONE,
-                     0);
-
-    /* TODO use notify instead */
-    /**
-     * SpiceMainChannel::main-agent-update:
-     * @main: the #SpiceMainChannel that emitted the signal
-     *
-     * Notify when the %SpiceMainChannel:agent-connected or
-     * %SpiceMainChannel:agent-caps-0 property change.
-     **/
-    signals[SPICE_MAIN_AGENT_UPDATE] =
-        g_signal_new("main-agent-update",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_FIRST,
-                     G_STRUCT_OFFSET(SpiceMainChannelClass, agent_update),
-                     NULL, NULL,
-                     g_cclosure_marshal_VOID__VOID,
-                     G_TYPE_NONE,
-                     0);
-    /**
-     * SpiceMainChannel::main-clipboard:
-     * @main: the #SpiceMainChannel that emitted the signal
-     * @type: the VD_AGENT_CLIPBOARD data type
-     * @data: clipboard data
-     * @size: size of @data in bytes
-     *
-     * Provides guest clipboard data requested by spice_main_clipboard_request().
-     *
-     * Deprecated: 0.6: use SpiceMainChannel::main-clipboard-selection instead.
-     **/
-    signals[SPICE_MAIN_CLIPBOARD] =
-        g_signal_new("main-clipboard",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_LAST | G_SIGNAL_DEPRECATED,
-                     0,
-                     NULL, NULL,
-                     g_cclosure_user_marshal_VOID__UINT_POINTER_UINT,
-                     G_TYPE_NONE,
-                     3,
-                     G_TYPE_UINT, G_TYPE_POINTER, G_TYPE_UINT);
-
-    /**
-     * SpiceMainChannel::main-clipboard-selection:
-     * @main: the #SpiceMainChannel that emitted the signal
-     * @selection: a VD_AGENT_CLIPBOARD_SELECTION clipboard
-     * @type: the VD_AGENT_CLIPBOARD data type
-     * @data: clipboard data
-     * @size: size of @data in bytes
-     *
-     * Since: 0.6
-     **/
-    signals[SPICE_MAIN_CLIPBOARD_SELECTION] =
-        g_signal_new("main-clipboard-selection",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_LAST,
-                     0,
-                     NULL, NULL,
-                     g_cclosure_user_marshal_VOID__UINT_UINT_POINTER_UINT,
-                     G_TYPE_NONE,
-                     4,
-                     G_TYPE_UINT, G_TYPE_UINT, G_TYPE_POINTER, G_TYPE_UINT);
-
-    /**
-     * SpiceMainChannel::main-clipboard-grab:
-     * @main: the #SpiceMainChannel that emitted the signal
-     * @types: the VD_AGENT_CLIPBOARD data types
-     * @ntypes: the number of @types
-     *
-     * Inform when clipboard data is available from the guest, and for
-     * which @types.
-     *
-     * Deprecated: 0.6: use SpiceMainChannel::main-clipboard-selection-grab instead.
-     **/
-    signals[SPICE_MAIN_CLIPBOARD_GRAB] =
-        g_signal_new("main-clipboard-grab",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_LAST | G_SIGNAL_DEPRECATED,
-                     0,
-                     NULL, NULL,
-                     g_cclosure_user_marshal_BOOLEAN__POINTER_UINT,
-                     G_TYPE_BOOLEAN,
-                     2,
-                     G_TYPE_POINTER, G_TYPE_UINT);
-
-    /**
-     * SpiceMainChannel::main-clipboard-selection-grab:
-     * @main: the #SpiceMainChannel that emitted the signal
-     * @selection: a VD_AGENT_CLIPBOARD_SELECTION clipboard
-     * @types: the VD_AGENT_CLIPBOARD data types
-     * @ntypes: the number of @types
-     *
-     * Inform when clipboard data is available from the guest, and for
-     * which @types.
-     *
-     * Since: 0.6
-     **/
-    signals[SPICE_MAIN_CLIPBOARD_SELECTION_GRAB] =
-        g_signal_new("main-clipboard-selection-grab",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_LAST,
-                     0,
-                     NULL, NULL,
-                     g_cclosure_user_marshal_BOOLEAN__UINT_POINTER_UINT,
-                     G_TYPE_BOOLEAN,
-                     3,
-                     G_TYPE_UINT, G_TYPE_POINTER, G_TYPE_UINT);
-
-    /**
-     * SpiceMainChannel::main-clipboard-request:
-     * @main: the #SpiceMainChannel that emitted the signal
-     * @types: the VD_AGENT_CLIPBOARD request type
-     *
-     * Return value: %TRUE if the request is successful
-     *
-     * Request clipbard data from the client.
-     *
-     * Deprecated: 0.6: use SpiceMainChannel::main-clipboard-selection-request instead.
-     **/
-    signals[SPICE_MAIN_CLIPBOARD_REQUEST] =
-        g_signal_new("main-clipboard-request",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_LAST | G_SIGNAL_DEPRECATED,
-                     0,
-                     NULL, NULL,
-                     g_cclosure_user_marshal_BOOLEAN__UINT,
-                     G_TYPE_BOOLEAN,
-                     1,
-                     G_TYPE_UINT);
-
-    /**
-     * SpiceMainChannel::main-clipboard-selection-request:
-     * @main: the #SpiceMainChannel that emitted the signal
-     * @selection: a VD_AGENT_CLIPBOARD_SELECTION clipboard
-     * @types: the VD_AGENT_CLIPBOARD request type
-     *
-     * Return value: %TRUE if the request is successful
-     *
-     * Request clipbard data from the client.
-     *
-     * Since: 0.6
-     **/
-    signals[SPICE_MAIN_CLIPBOARD_SELECTION_REQUEST] =
-        g_signal_new("main-clipboard-selection-request",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_LAST,
-                     0,
-                     NULL, NULL,
-                     g_cclosure_user_marshal_BOOLEAN__UINT_UINT,
-                     G_TYPE_BOOLEAN,
-                     2,
-                     G_TYPE_UINT, G_TYPE_UINT);
-
-    /**
-     * SpiceMainChannel::main-clipboard-release:
-     * @main: the #SpiceMainChannel that emitted the signal
-     *
-     * Inform when the clipboard is released from the guest, when no
-     * clipboard data is available from the guest.
-     *
-     * Deprecated: 0.6: use SpiceMainChannel::main-clipboard-selection-release instead.
-     **/
-    signals[SPICE_MAIN_CLIPBOARD_RELEASE] =
-        g_signal_new("main-clipboard-release",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_LAST | G_SIGNAL_DEPRECATED,
-                     0,
-                     NULL, NULL,
-                     g_cclosure_marshal_VOID__VOID,
-                     G_TYPE_NONE,
-                     0);
-
-    /**
-     * SpiceMainChannel::main-clipboard-selection-release:
-     * @main: the #SpiceMainChannel that emitted the signal
-     * @selection: a VD_AGENT_CLIPBOARD_SELECTION clipboard
-     *
-     * Inform when the clipboard is released from the guest, when no
-     * clipboard data is available from the guest.
-     *
-     * Since: 0.6
-     **/
-    signals[SPICE_MAIN_CLIPBOARD_SELECTION_RELEASE] =
-        g_signal_new("main-clipboard-selection-release",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_LAST,
-                     0,
-                     NULL, NULL,
-                     g_cclosure_marshal_VOID__UINT,
-                     G_TYPE_NONE,
-                     1,
-                     G_TYPE_UINT);
-
-    /**
-     * SpiceMainChannel::migration-started:
-     * @main: the #SpiceMainChannel that emitted the signal
-     * @session: a migration #SpiceSession
-     *
-     * Inform when migration is starting. Application wishing to make
-     * connections themself can set the #SpiceSession:client-sockets
-     * to @TRUE, then follow #SpiceSession::channel-new creation, and
-     * use spice_channel_open_fd() once the socket is created.
-     *
-     **/
-    signals[SPICE_MIGRATION_STARTED] =
-        g_signal_new("migration-started",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_LAST,
-                     0,
-                     NULL, NULL,
-                     g_cclosure_marshal_VOID__OBJECT,
-                     G_TYPE_NONE,
-                     1,
-                     G_TYPE_OBJECT);
-
-    g_type_class_add_private(klass, sizeof(SpiceMainChannelPrivate));
-    channel_set_handlers(SPICE_CHANNEL_CLASS(klass));
-}
-
-/* ------------------------------------------------------------------ */
-
-
-static void agent_free_msg_queue(SpiceMainChannel *channel)
-{
-    SpiceMainChannelPrivate *c = channel->priv;
-    SpiceMsgOut *out;
-
-    if (!c->agent_msg_queue)
-        return;
-
-    while (!g_queue_is_empty(c->agent_msg_queue)) {
-        out = g_queue_pop_head(c->agent_msg_queue);
-        spice_msg_out_unref(out);
-    }
-
-    g_queue_free(c->agent_msg_queue);
-    c->agent_msg_queue = NULL;
-}
-
-/* Here, flushing algorithm is stolen from spice-channel.c */
-static void file_xfer_flushed(SpiceMainChannel *channel, gboolean success)
-{
-    SpiceMainChannelPrivate *c = channel->priv;
-    GSList *l;
-
-    for (l = c->flushing; l != NULL; l = l->next) {
-        GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT(l->data);
-        g_simple_async_result_set_op_res_gboolean(result, success);
-        g_simple_async_result_complete_in_idle(result);
-    }
-
-    g_slist_free_full(c->flushing, g_object_unref);
-    c->flushing = NULL;
-}
-
-static void file_xfer_flush_async(SpiceMainChannel *channel, GCancellable *cancellable,
-                                  GAsyncReadyCallback callback, gpointer user_data)
-{
-    GSimpleAsyncResult *simple;
-    SpiceMainChannelPrivate *c = channel->priv;
-    gboolean was_empty;
-
-    simple = g_simple_async_result_new(G_OBJECT(channel), callback, user_data,
-                                       file_xfer_flush_async);
-
-    was_empty = g_queue_is_empty(c->agent_msg_queue);
-    if (was_empty) {
-        g_simple_async_result_set_op_res_gboolean(simple, TRUE);
-        g_simple_async_result_complete_in_idle(simple);
-        g_object_unref(simple);
-        return;
-    }
-
-    c->flushing = g_slist_append(c->flushing, simple);
-}
-
-static gboolean file_xfer_flush_finish(SpiceMainChannel *channel, GAsyncResult *result,
-                                       GError **error)
-{
-    GSimpleAsyncResult *simple = (GSimpleAsyncResult *)result;
-
-    g_return_val_if_fail(g_simple_async_result_is_valid(result,
-        G_OBJECT(channel), file_xfer_flush_async), FALSE);
-
-    if (g_simple_async_result_propagate_error(simple, error)) {
-        return FALSE;
-    }
-
-    CHANNEL_DEBUG(channel, "flushed finished!");
-    return g_simple_async_result_get_op_res_gboolean(simple);
-}
-
-/* coroutine context */
-static void agent_send_msg_queue(SpiceMainChannel *channel)
-{
-    SpiceMainChannelPrivate *c = channel->priv;
-    SpiceMsgOut *out;
-
-    while (c->agent_tokens > 0 &&
-           !g_queue_is_empty(c->agent_msg_queue)) {
-        c->agent_tokens--;
-        out = g_queue_pop_head(c->agent_msg_queue);
-        spice_msg_out_send_internal(out);
-    }
-    if (g_queue_is_empty(c->agent_msg_queue) && c->flushing != NULL) {
-        file_xfer_flushed(channel, TRUE);
-    }
-}
-
-/* any context: the message is not flushed immediately,
-   you can wakeup() the channel coroutine or send_msg_queue()
-
-   expected arguments, pair of data/data_size to send terminated with NULL:
-   agent_msg_queue_many(main, VD_AGENT_...,
-                        &foo, sizeof(Foo),
-                        data, data_size, NULL);
-*/
-G_GNUC_NULL_TERMINATED
-static void agent_msg_queue_many(SpiceMainChannel *channel, int type, const void *data, ...)
-{
-    va_list args;
-    SpiceMainChannelPrivate *c = channel->priv;
-    SpiceMsgOut *out;
-    VDAgentMessage msg;
-    guint8 *payload;
-    gsize paysize, s, mins, size = 0;
-    const guint8 *d;
-
-    G_STATIC_ASSERT(VD_AGENT_MAX_DATA_SIZE > sizeof(VDAgentMessage));
-
-    va_start(args, data);
-    for (d = data; d != NULL; d = va_arg(args, void*)) {
-        size += va_arg(args, gsize);
-    }
-    va_end(args);
-
-    msg.protocol = VD_AGENT_PROTOCOL;
-    msg.type = type;
-    msg.opaque = 0;
-    msg.size = size;
-
-    paysize = MIN(VD_AGENT_MAX_DATA_SIZE, size + sizeof(VDAgentMessage));
-    out = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_MAIN_AGENT_DATA);
-    payload = spice_marshaller_reserve_space(out->marshaller, paysize);
-    memcpy(payload, &msg, sizeof(VDAgentMessage));
-    payload += sizeof(VDAgentMessage);
-    paysize -= sizeof(VDAgentMessage);
-    if (paysize == 0) {
-        g_queue_push_tail(c->agent_msg_queue, out);
-        out = NULL;
-    }
-
-    va_start(args, data);
-    for (d = data; size > 0; d = va_arg(args, void*)) {
-        s = va_arg(args, gsize);
-        while (s > 0) {
-            if (out == NULL) {
-                paysize = MIN(VD_AGENT_MAX_DATA_SIZE, size);
-                out = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_MAIN_AGENT_DATA);
-                payload = spice_marshaller_reserve_space(out->marshaller, paysize);
-            }
-            mins = MIN(paysize, s);
-            memcpy(payload, d, mins);
-            d += mins;
-            payload += mins;
-            s -= mins;
-            size -= mins;
-            paysize -= mins;
-            if (paysize == 0) {
-                g_queue_push_tail(c->agent_msg_queue, out);
-                out = NULL;
-            }
-        }
-    }
-    va_end(args);
-    g_warn_if_fail(out == NULL);
-}
-
-static int monitors_cmp(const void *p1, const void *p2, gpointer user_data)
-{
-    const VDAgentMonConfig *m1 = p1;
-    const VDAgentMonConfig *m2 = p2;
-    double d1 = sqrt(m1->x * m1->x + m1->y * m1->y);
-    double d2 = sqrt(m2->x * m2->x + m2->y * m2->y);
-    int diff = d1 - d2;
-
-    return diff == 0 ? (char*)p1 - (char*)p2 : diff;
-}
-
-static void monitors_align(VDAgentMonConfig *monitors, int nmonitors)
-{
-    gint i, j, x = 0;
-    guint32 used = 0;
-    VDAgentMonConfig *sorted_monitors;
-
-    if (nmonitors == 0)
-        return;
-
-    /* sort by distance from origin */
-    sorted_monitors = g_memdup(monitors, nmonitors * sizeof(VDAgentMonConfig));
-    g_qsort_with_data(sorted_monitors, nmonitors, sizeof(VDAgentMonConfig), monitors_cmp, NULL);
-
-    /* super-KISS ltr alignment, feel free to improve */
-    for (i = 0; i < nmonitors; i++) {
-        /* Find where this monitor is in the sorted order */
-        for (j = 0; j < nmonitors; j++) {
-            /* Avoid using the same entry twice, this happens with older
-               virt-viewer versions which always set x and y to 0 */
-            if (used & (1 << j))
-                continue;
-            if (memcmp(&monitors[j], &sorted_monitors[i],
-                       sizeof(VDAgentMonConfig)) == 0)
-                break;
-        }
-        used |= 1 << j;
-        monitors[j].x = x;
-        monitors[j].y = 0;
-        x += monitors[j].width;
-        if (monitors[j].width || monitors[j].height)
-            SPICE_DEBUG("#%d +%d+%d-%dx%d", j, monitors[j].x, monitors[j].y,
-                        monitors[j].width, monitors[j].height);
-    }
-    g_free(sorted_monitors);
-}
-
-
-#define agent_msg_queue(Channel, Type, Size, Data) \
-    agent_msg_queue_many((Channel), (Type), (Data), (Size), NULL)
-
-/**
- * spice_main_send_monitor_config:
- * @channel:
- *
- * Send monitors configuration previously set with
- * spice_main_set_display() and spice_main_set_display_enabled()
- *
- * Returns: %TRUE on success.
- **/
-gboolean spice_main_send_monitor_config(SpiceMainChannel *channel)
-{
-    SpiceMainChannelPrivate *c;
-    VDAgentMonitorsConfig *mon;
-    int i, j, monitors;
-    size_t size;
-
-    g_return_val_if_fail(SPICE_IS_MAIN_CHANNEL(channel), FALSE);
-    c = channel->priv;
-    g_return_val_if_fail(c->agent_connected, FALSE);
-
-    if (spice_main_agent_test_capability(channel,
-                                     VD_AGENT_CAP_SPARSE_MONITORS_CONFIG)) {
-        monitors = SPICE_N_ELEMENTS(c->display);
-    } else {
-        monitors = 0;
-        for (i = 0; i < SPICE_N_ELEMENTS(c->display); i++) {
-            if (c->display[i].enabled)
-                monitors += 1;
-        }
-    }
-
-    size = sizeof(VDAgentMonitorsConfig) + sizeof(VDAgentMonConfig) * monitors;
-    mon = g_malloc0(size);
-
-    mon->num_of_monitors = monitors;
-    if (c->disable_display_position == FALSE ||
-        c->disable_display_align == FALSE)
-        mon->flags |= VD_AGENT_CONFIG_MONITORS_FLAG_USE_POS;
-
-    j = 0;
-    for (i = 0; i < SPICE_N_ELEMENTS(c->display); i++) {
-        if (!c->display[i].enabled) {
-            if (spice_main_agent_test_capability(channel,
-                                     VD_AGENT_CAP_SPARSE_MONITORS_CONFIG))
-                j++;
-            continue;
-        }
-        mon->monitors[j].depth  = c->display_color_depth ? c->display_color_depth : 32;
-        mon->monitors[j].width  = c->display[i].width;
-        mon->monitors[j].height = c->display[i].height;
-        mon->monitors[j].x = c->display[i].x;
-        mon->monitors[j].y = c->display[i].y;
-        CHANNEL_DEBUG(channel, "monitor config: #%d %dx%d+%d+%d @ %d bpp", j,
-                      mon->monitors[j].width, mon->monitors[j].height,
-                      mon->monitors[j].x, mon->monitors[j].y,
-                      mon->monitors[j].depth);
-        j++;
-    }
-
-    if (c->disable_display_align == FALSE)
-        monitors_align(mon->monitors, mon->num_of_monitors);
-
-    agent_msg_queue(channel, VD_AGENT_MONITORS_CONFIG, size, mon);
-    g_free(mon);
-
-    spice_channel_wakeup(SPICE_CHANNEL(channel), FALSE);
-    if (c->timer_id != 0) {
-        g_source_remove(c->timer_id);
-        c->timer_id = 0;
-    }
-
-    return TRUE;
-}
-
-static void audio_playback_volume_info_cb(GObject *object, GAsyncResult *res, gpointer user_data)
-{
-    SpiceMainChannel *main_channel = user_data;
-    SpiceSession *session = spice_channel_get_session(SPICE_CHANNEL(main_channel));
-    SpiceAudio *audio = spice_audio_get(session, NULL);
-    VDAgentAudioVolumeSync *avs;
-    guint16 *volume;
-    guint8 nchannels;
-    gboolean mute, ret;
-    gsize array_size;
-    GError *error = NULL;
-
-    ret = spice_audio_get_playback_volume_info_finish(audio, res, &mute, &nchannels,
-                                                      &volume, &error);
-    if (ret == FALSE || volume == NULL || nchannels == 0) {
-        if (error != NULL) {
-            spice_warning("Failed to get playback async volume info: %s", error->message);
-            g_error_free (error);
-        } else {
-            SPICE_DEBUG("Failed to get playback async volume info");
-        }
-        main_channel->priv->agent_volume_playback_sync = FALSE;
-        return;
-    }
-
-    array_size = sizeof(uint16_t) * nchannels;
-    avs = g_malloc0(sizeof(VDAgentAudioVolumeSync) + array_size);
-    avs->is_playback = TRUE;
-    avs->mute = mute;
-    avs->nchannels = nchannels;
-    memcpy(avs->volume, volume, array_size);
-
-    SPICE_DEBUG("%s mute=%s nchannels=%u volume[0]=%u",
-                __func__, spice_yes_no(mute), nchannels, volume[0]);
-    g_free(volume);
-    agent_msg_queue(main_channel, VD_AGENT_AUDIO_VOLUME_SYNC,
-                    sizeof(VDAgentAudioVolumeSync) + array_size, avs);
-}
-
-static void agent_sync_audio_playback(SpiceMainChannel *main_channel)
-{
-    SpiceSession *session = spice_channel_get_session(SPICE_CHANNEL(main_channel));
-    SpiceAudio *audio = spice_audio_get(session, NULL);
-    SpiceMainChannelPrivate *c = main_channel->priv;
-
-    if (!test_agent_cap(main_channel, VD_AGENT_CAP_AUDIO_VOLUME_SYNC) ||
-        c->agent_volume_playback_sync == TRUE) {
-        SPICE_DEBUG("%s - is not going to sync audio with guest", __func__);
-        return;
-    }
-    /* only one per connection */
-    g_cancellable_reset(c->cancellable_volume_info);
-    c->agent_volume_playback_sync = TRUE;
-    spice_audio_get_playback_volume_info_async(audio, c->cancellable_volume_info, main_channel,
-                                               audio_playback_volume_info_cb, main_channel);
-}
-
-static void audio_record_volume_info_cb(GObject *object, GAsyncResult *res, gpointer user_data)
-{
-    SpiceMainChannel *main_channel = user_data;
-    SpiceSession *session = spice_channel_get_session(SPICE_CHANNEL(main_channel));
-    SpiceAudio *audio = spice_audio_get(session, NULL);
-    VDAgentAudioVolumeSync *avs;
-    guint16 *volume;
-    guint8 nchannels;
-    gboolean ret, mute;
-    gsize array_size;
-    GError *error = NULL;
-
-    ret = spice_audio_get_record_volume_info_finish(audio, res, &mute, &nchannels, &volume, &error);
-    if (ret == FALSE || volume == NULL || nchannels == 0) {
-        if (error != NULL) {
-            spice_warning ("Failed to get record async volume info: %s", error->message);
-            g_error_free (error);
-        } else {
-            SPICE_DEBUG("Failed to get record async volume info");
-        }
-        main_channel->priv->agent_volume_record_sync = FALSE;
-        return;
-    }
-
-    array_size = sizeof(uint16_t) * nchannels;
-    avs = g_malloc0(sizeof(VDAgentAudioVolumeSync) + array_size);
-    avs->is_playback = FALSE;
-    avs->mute = mute;
-    avs->nchannels = nchannels;
-    memcpy(avs->volume, volume, array_size);
-
-    SPICE_DEBUG("%s mute=%s nchannels=%u volume[0]=%u",
-                __func__, spice_yes_no(mute), nchannels, volume[0]);
-    g_free(volume);
-    agent_msg_queue(main_channel, VD_AGENT_AUDIO_VOLUME_SYNC,
-                    sizeof(VDAgentAudioVolumeSync) + array_size, avs);
-}
-
-static void agent_sync_audio_record(SpiceMainChannel *main_channel)
-{
-    SpiceSession *session = spice_channel_get_session(SPICE_CHANNEL(main_channel));
-    SpiceAudio *audio = spice_audio_get(session, NULL);
-    SpiceMainChannelPrivate *c = main_channel->priv;
-
-    if (!test_agent_cap(main_channel, VD_AGENT_CAP_AUDIO_VOLUME_SYNC) ||
-        c->agent_volume_record_sync == TRUE) {
-        SPICE_DEBUG("%s - is not going to sync audio with guest", __func__);
-        return;
-    }
-    /* only one per connection */
-    g_cancellable_reset(c->cancellable_volume_info);
-    c->agent_volume_record_sync = TRUE;
-    spice_audio_get_record_volume_info_async(audio, c->cancellable_volume_info, main_channel,
-                                             audio_record_volume_info_cb, main_channel);
-}
-
-/* any context: the message is not flushed immediately,
-   you can wakeup() the channel coroutine or send_msg_queue() */
-static void agent_display_config(SpiceMainChannel *channel)
-{
-    SpiceMainChannelPrivate *c = channel->priv;
-    VDAgentDisplayConfig config = { 0, };
-
-    if (c->display_disable_wallpaper) {
-        config.flags |= VD_AGENT_DISPLAY_CONFIG_FLAG_DISABLE_WALLPAPER;
-    }
-
-    if (c->display_disable_font_smooth) {
-        config.flags |= VD_AGENT_DISPLAY_CONFIG_FLAG_DISABLE_FONT_SMOOTH;
-    }
-
-    if (c->display_disable_animation) {
-        config.flags |= VD_AGENT_DISPLAY_CONFIG_FLAG_DISABLE_ANIMATION;
-    }
-
-    if (c->display_color_depth != 0) {
-        config.flags |= VD_AGENT_DISPLAY_CONFIG_FLAG_SET_COLOR_DEPTH;
-        config.depth = c->display_color_depth;
-    }
-
-    CHANNEL_DEBUG(channel, "display_config: flags: %u, depth: %u", config.flags, config.depth);
-
-    agent_msg_queue(channel, VD_AGENT_DISPLAY_CONFIG, sizeof(VDAgentDisplayConfig), &config);
-}
-
-/* any context: the message is not flushed immediately,
-   you can wakeup() the channel coroutine or send_msg_queue() */
-static void agent_announce_caps(SpiceMainChannel *channel)
-{
-    SpiceMainChannelPrivate *c = channel->priv;
-    VDAgentAnnounceCapabilities *caps;
-    size_t size;
-
-    if (!c->agent_connected)
-        return;
-
-    size = sizeof(VDAgentAnnounceCapabilities) + VD_AGENT_CAPS_BYTES;
-    caps = g_malloc0(size);
-    if (!c->agent_caps_received)
-        caps->request = 1;
-    VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_MOUSE_STATE);
-    VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_MONITORS_CONFIG);
-    VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_REPLY);
-    VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_DISPLAY_CONFIG);
-    VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_CLIPBOARD_BY_DEMAND);
-    VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_CLIPBOARD_SELECTION);
-
-    agent_msg_queue(channel, VD_AGENT_ANNOUNCE_CAPABILITIES, size, caps);
-    g_free(caps);
-}
-
-/* any context: the message is not flushed immediately,
-   you can wakeup() the channel coroutine or send_msg_queue() */
-static void agent_clipboard_grab(SpiceMainChannel *channel, guint selection,
-                                 guint32 *types, int ntypes)
-{
-    SpiceMainChannelPrivate *c = channel->priv;
-    guint8 *msg;
-    VDAgentClipboardGrab *grab;
-    size_t size;
-    int i;
-
-    if (!c->agent_connected)
-        return;
-
-    g_return_if_fail(test_agent_cap(channel, VD_AGENT_CAP_CLIPBOARD_BY_DEMAND));
-
-    size = sizeof(VDAgentClipboardGrab) + sizeof(uint32_t) * ntypes;
-    if (test_agent_cap(channel, VD_AGENT_CAP_CLIPBOARD_SELECTION)) {
-        size += 4;
-    } else if (selection != VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD) {
-        CHANNEL_DEBUG(channel, "Ignoring clipboard grab");
-        return;
-    }
-
-    msg = g_alloca(size);
-    memset(msg, 0, size);
-
-    grab = (VDAgentClipboardGrab *)msg;
-
-    if (test_agent_cap(channel, VD_AGENT_CAP_CLIPBOARD_SELECTION)) {
-        msg[0] = selection;
-        grab = (VDAgentClipboardGrab *)(msg + 4);
-    }
-
-    for (i = 0; i < ntypes; i++) {
-        grab->types[i] = types[i];
-    }
-
-    agent_msg_queue(channel, VD_AGENT_CLIPBOARD_GRAB, size, msg);
-}
-
-/* any context: the message is not flushed immediately,
-   you can wakeup() the channel coroutine or send_msg_queue() */
-static void agent_clipboard_notify(SpiceMainChannel *self, guint selection,
-                                   guint32 type, const guchar *data, size_t size)
-{
-    SpiceMainChannelPrivate *c = self->priv;
-    VDAgentClipboard *cb;
-    guint8 *msg;
-    size_t msgsize;
-    gint max_clipboard = spice_main_get_max_clipboard(self);
-
-    g_return_if_fail(c->agent_connected);
-    g_return_if_fail(test_agent_cap(self, VD_AGENT_CAP_CLIPBOARD_BY_DEMAND));
-    g_return_if_fail(max_clipboard == -1 || size < max_clipboard);
-
-    msgsize = sizeof(VDAgentClipboard);
-    if (test_agent_cap(self, VD_AGENT_CAP_CLIPBOARD_SELECTION)) {
-        msgsize += 4;
-    } else if (selection != VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD) {
-        CHANNEL_DEBUG(self, "Ignoring clipboard notify");
-        return;
-    }
-
-    msg = g_alloca(msgsize);
-    memset(msg, 0, msgsize);
-
-    cb = (VDAgentClipboard *)msg;
-
-    if (test_agent_cap(self, VD_AGENT_CAP_CLIPBOARD_SELECTION)) {
-        msg[0] = selection;
-        cb = (VDAgentClipboard *)(msg + 4);
-    }
-
-    cb->type = type;
-    agent_msg_queue_many(self, VD_AGENT_CLIPBOARD, msg, msgsize, data, size, NULL);
-}
-
-/* any context: the message is not flushed immediately,
-   you can wakeup() the channel coroutine or send_msg_queue() */
-static void agent_clipboard_request(SpiceMainChannel *channel, guint selection, guint32 type)
-{
-    SpiceMainChannelPrivate *c = channel->priv;
-    VDAgentClipboardRequest *request;
-    guint8 *msg;
-    size_t msgsize;
-
-    g_return_if_fail(c->agent_connected);
-    g_return_if_fail(test_agent_cap(channel, VD_AGENT_CAP_CLIPBOARD_BY_DEMAND));
-
-    msgsize = sizeof(VDAgentClipboardRequest);
-    if (test_agent_cap(channel, VD_AGENT_CAP_CLIPBOARD_SELECTION)) {
-        msgsize += 4;
-    } else if (selection != VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD) {
-        SPICE_DEBUG("Ignoring clipboard request");
-        return;
-    }
-
-    msg = g_alloca(msgsize);
-    memset(msg, 0, msgsize);
-
-    request = (VDAgentClipboardRequest *)msg;
-
-    if (test_agent_cap(channel, VD_AGENT_CAP_CLIPBOARD_SELECTION)) {
-        msg[0] = selection;
-        request = (VDAgentClipboardRequest *)(msg + 4);
-    }
-
-    request->type = type;
-
-    agent_msg_queue(channel, VD_AGENT_CLIPBOARD_REQUEST, msgsize, msg);
-}
-
-/* any context: the message is not flushed immediately,
-   you can wakeup() the channel coroutine or send_msg_queue() */
-static void agent_clipboard_release(SpiceMainChannel *channel, guint selection)
-{
-    SpiceMainChannelPrivate *c = channel->priv;
-    guint8 msg[4] = { 0, };
-    guint8 msgsize = 0;
-
-    g_return_if_fail(c->agent_connected);
-    g_return_if_fail(test_agent_cap(channel, VD_AGENT_CAP_CLIPBOARD_BY_DEMAND));
-
-    if (test_agent_cap(channel, VD_AGENT_CAP_CLIPBOARD_SELECTION)) {
-        msg[0] = selection;
-        msgsize += 4;
-    } else if (selection != VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD) {
-        SPICE_DEBUG("Ignoring clipboard release");
-        return;
-    }
-
-    agent_msg_queue(channel, VD_AGENT_CLIPBOARD_RELEASE, msgsize, msg);
-}
-
-/* main context*/
-static gboolean timer_set_display(gpointer data)
-{
-    SpiceMainChannel *channel = data;
-    SpiceMainChannelPrivate *c = channel->priv;
-    SpiceSession *session;
-    gint i;
-
-    c->timer_id = 0;
-    if (!c->agent_connected)
-        return FALSE;
-
-    session = spice_channel_get_session(SPICE_CHANNEL(channel));
-
-    /* ensure we have an explicit monitor configuration at least for
-       number of display channels */
-    for (i = 0; i < spice_session_get_n_display_channels(session); i++)
-        if (!c->display[i].enabled_set) {
-            SPICE_DEBUG("Not sending monitors config, missing monitors");
-            return FALSE;
-        }
-
-    spice_main_send_monitor_config(channel);
-
-    return FALSE;
-}
-
-/* any context  */
-static void update_display_timer(SpiceMainChannel *channel, guint seconds)
-{
-    SpiceMainChannelPrivate *c = channel->priv;
-
-    if (c->timer_id)
-        g_source_remove(c->timer_id);
-
-    c->timer_id = g_timeout_add_seconds(seconds, timer_set_display, channel);
-}
-
-/* coroutine context  */
-static void set_agent_connected(SpiceMainChannel *channel, gboolean connected)
-{
-    SpiceMainChannelPrivate *c = channel->priv;
-
-    SPICE_DEBUG("agent connected: %s", spice_yes_no(connected));
-    if (connected != c->agent_connected) {
-        c->agent_connected = connected;
-        g_coroutine_object_notify(G_OBJECT(channel), "agent-connected");
-    }
-    if (!connected)
-        spice_main_channel_reset_agent(SPICE_MAIN_CHANNEL(channel));
-
-    g_coroutine_signal_emit(channel, signals[SPICE_MAIN_AGENT_UPDATE], 0);
-}
-
-/* coroutine context  */
-static void agent_start(SpiceMainChannel *channel)
-{
-    SpiceMainChannelPrivate *c = channel->priv;
-    SpiceMsgcMainAgentStart agent_start = {
-        .num_tokens = ~0,
-    };
-    SpiceMsgOut *out;
-
-    c->agent_volume_playback_sync = FALSE;
-    c->agent_volume_record_sync = FALSE;
-    c->agent_caps_received = false;
-    set_agent_connected(channel, TRUE);
-
-    out = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_MAIN_AGENT_START);
-    out->marshallers->msgc_main_agent_start(out->marshaller, &agent_start);
-    spice_msg_out_send_internal(out);
-
-    if (c->agent_connected) {
-        agent_announce_caps(channel);
-        agent_send_msg_queue(channel);
-    }
-}
-
-/* coroutine context  */
-static void agent_stopped(SpiceMainChannel *channel)
-{
-    set_agent_connected(channel, FALSE);
-}
-
-/* coroutine context */
-static void set_mouse_mode(SpiceMainChannel *channel, uint32_t supported, uint32_t current)
-{
-    SpiceMainChannelPrivate *c = channel->priv;
-
-    if (c->mouse_mode != current) {
-        c->mouse_mode = current;
-        g_coroutine_signal_emit(channel, signals[SPICE_MAIN_MOUSE_UPDATE], 0);
-        g_coroutine_object_notify(G_OBJECT(channel), "mouse-mode");
-    }
-
-    /* switch to client mode if possible */
-    if (!spice_channel_get_read_only(SPICE_CHANNEL(channel)) &&
-        supported & SPICE_MOUSE_MODE_CLIENT &&
-        current != SPICE_MOUSE_MODE_CLIENT) {
-        SpiceMsgcMainMouseModeRequest req = {
-            .mode = SPICE_MOUSE_MODE_CLIENT,
-        };
-        SpiceMsgOut *out;
-
-        out = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_MAIN_MOUSE_MODE_REQUEST);
-        out->marshallers->msgc_main_mouse_mode_request(out->marshaller, &req);
-        spice_msg_out_send_internal(out);
-    }
-}
-
-/* coroutine context */
-static void main_handle_init(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
-    SpiceMsgMainInit *init = spice_msg_in_parsed(in);
-    SpiceSession *session;
-    SpiceMsgOut *out;
-
-    session = spice_channel_get_session(channel);
-    spice_session_set_connection_id(session, init->session_id);
-
-    set_mouse_mode(SPICE_MAIN_CHANNEL(channel), init->supported_mouse_modes,
-                   init->current_mouse_mode);
-
-    spice_session_set_mm_time(session, init->multi_media_time);
-    spice_session_set_caches_hints(session, init->ram_hint, init->display_channels_hint);
-
-    c->agent_tokens = init->agent_tokens;
-    if (init->agent_connected)
-        agent_start(SPICE_MAIN_CHANNEL(channel));
-
-    if (spice_session_migrate_after_main_init(session))
-        return;
-
-    out = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_MAIN_ATTACH_CHANNELS);
-    spice_msg_out_send_internal(out);
-}
-
-/* coroutine context */
-static void main_handle_name(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceMsgMainName *name = spice_msg_in_parsed(in);
-    SpiceSession *session = spice_channel_get_session(channel);
-
-    SPICE_DEBUG("server name: %s", name->name);
-    spice_session_set_name(session, (const gchar *)name->name);
-}
-
-/* coroutine context */
-static void main_handle_uuid(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceMsgMainUuid *uuid = spice_msg_in_parsed(in);
-    SpiceSession *session = spice_channel_get_session(channel);
-    gchar *uuid_str = spice_uuid_to_string(uuid->uuid);
-
-    SPICE_DEBUG("server uuid: %s", uuid_str);
-    spice_session_set_uuid(session, uuid->uuid);
-
-    g_free(uuid_str);
-}
-
-/* coroutine context */
-static void main_handle_mm_time(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceSession *session;
-    SpiceMsgMainMultiMediaTime *msg = spice_msg_in_parsed(in);
-
-    session = spice_channel_get_session(channel);
-    spice_session_set_mm_time(session, msg->time);
-}
-
-typedef struct channel_new {
-    SpiceSession *session;
-    int type;
-    int id;
-} channel_new_t;
-
-/* main context */
-static gboolean _channel_new(channel_new_t *c)
-{
-    g_return_val_if_fail(c != NULL, FALSE);
-
-    spice_channel_new(c->session, c->type, c->id);
-
-    g_object_unref(c->session);
-    g_free(c);
-
-    return FALSE;
-}
-
-/* coroutine context */
-static void main_handle_channels_list(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceMsgChannels *msg = spice_msg_in_parsed(in);
-    SpiceSession *session;
-    int i;
-
-    session = spice_channel_get_session(channel);
-
-    /* guarantee that uuid is notified before setting up the channels, even if
-     * the server is older and doesn't actually send the uuid */
-    g_coroutine_object_notify(G_OBJECT(session), "uuid");
-
-    for (i = 0; i < msg->num_of_channels; i++) {
-        channel_new_t *c;
-
-        c = g_new(channel_new_t, 1);
-        c->session = g_object_ref(session);
-        c->type = msg->channels[i].type;
-        c->id = msg->channels[i].id;
-        /* no need to explicitely switch to main context, since
-           synchronous call is not needed. */
-        /* no need to track idle, session is refed */
-        g_idle_add((GSourceFunc)_channel_new, c);
-    }
-}
-
-/* coroutine context */
-static void main_handle_mouse_mode(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceMsgMainMouseMode *msg = spice_msg_in_parsed(in);
-    set_mouse_mode(SPICE_MAIN_CHANNEL(channel), msg->supported_modes, msg->current_mode);
-}
-
-/* coroutine context */
-static void main_handle_agent_connected(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    agent_start(SPICE_MAIN_CHANNEL(channel));
-}
-
-/* coroutine context */
-static void main_handle_agent_connected_tokens(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
-    SpiceMsgMainAgentConnectedTokens *msg = spice_msg_in_parsed(in);
-
-    c->agent_tokens = msg->num_tokens;
-    agent_start(SPICE_MAIN_CHANNEL(channel));
-}
-
-/* coroutine context */
-static void main_handle_agent_disconnected(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    agent_stopped(SPICE_MAIN_CHANNEL(channel));
-}
-
-static void file_xfer_task_free(SpiceFileXferTask *task)
-{
-    SpiceMainChannelPrivate *c;
-
-    g_return_if_fail(task != NULL);
-
-    c = task->channel->priv;
-    g_hash_table_remove(c->file_xfer_tasks, GUINT_TO_POINTER(task->id));
-
-    g_clear_object(&task->channel);
-    g_clear_object(&task->file);
-    g_clear_object(&task->file_stream);
-    g_free(task);
-}
-
-/* main context */
-static void file_xfer_close_cb(GObject      *object,
-                               GAsyncResult *close_res,
-                               gpointer      user_data)
-{
-    GSimpleAsyncResult *res;
-    SpiceFileXferTask *task;
-    GError *error = NULL;
-
-    task = user_data;
-
-    if (object) {
-        GInputStream *stream = G_INPUT_STREAM(object);
-        g_input_stream_close_finish(stream, close_res, &error);
-        if (error) {
-            /* This error dont need to report to user, just print a log */
-            SPICE_DEBUG("close file error: %s", error->message);
-            g_clear_error(&error);
-        }
-    }
-
-    /* Notify to user that files have been transferred or something error
-       happened. */
-    res = g_simple_async_result_new(G_OBJECT(task->channel),
-                                    task->callback,
-                                    task->user_data,
-                                    spice_main_file_copy_async);
-    if (task->error) {
-        g_simple_async_result_take_error(res, task->error);
-        g_simple_async_result_set_op_res_gboolean(res, FALSE);
-    } else {
-        g_simple_async_result_set_op_res_gboolean(res, TRUE);
-    }
-    g_simple_async_result_complete_in_idle(res);
-    g_object_unref(res);
-
-    file_xfer_task_free(task);
-}
-
-static void file_xfer_data_flushed_cb(GObject *source_object,
-                                      GAsyncResult *res,
-                                      gpointer user_data)
-{
-    SpiceFileXferTask *task = user_data;
-    SpiceMainChannel *channel = (SpiceMainChannel *)source_object;
-    GError *error = NULL;
-
-    task->pending = FALSE;
-    file_xfer_flush_finish(channel, res, &error);
-    if (error || task->error) {
-        file_xfer_completed(task, error);
-        return;
-    }
-
-    if (task->progress_callback)
-        task->progress_callback(task->read_bytes, task->file_size,
-                                task->progress_callback_data);
-
-    /* Read more data */
-    file_xfer_continue_read(task);
-}
-
-static void file_xfer_queue(SpiceFileXferTask *task, int data_size)
-{
-    VDAgentFileXferDataMessage msg;
-    SpiceMainChannel *channel = SPICE_MAIN_CHANNEL(task->channel);
-
-    msg.id = task->id;
-    msg.size = data_size;
-    agent_msg_queue_many(channel, VD_AGENT_FILE_XFER_DATA,
-                         &msg, sizeof(msg),
-                         task->buffer, data_size, NULL);
-    spice_channel_wakeup(SPICE_CHANNEL(channel), FALSE);
-}
-
-/* main context */
-static void file_xfer_read_cb(GObject *source_object,
-                              GAsyncResult *res,
-                              gpointer user_data)
-{
-    SpiceFileXferTask *task = user_data;
-    SpiceMainChannel *channel = task->channel;
-    gssize count;
-    GError *error = NULL;
-
-    task->pending = FALSE;
-    count = g_input_stream_read_finish(G_INPUT_STREAM(task->file_stream),
-                                       res, &error);
-    /* Check for pending earlier errors */
-    if (task->error) {
-        file_xfer_completed(task, error);
-        return;
-    }
-
-    if (count > 0 || task->file_size == 0) {
-        task->read_bytes += count;
-        file_xfer_queue(task, count);
-        if (count == 0)
-            return;
-        file_xfer_flush_async(channel, task->cancellable,
-                              file_xfer_data_flushed_cb, task);
-        task->pending = TRUE;
-    } else if (error) {
-        VDAgentFileXferStatusMessage msg = {
-            .id = task->id,
-            .result = VD_AGENT_FILE_XFER_STATUS_ERROR,
-        };
-        agent_msg_queue_many(task->channel, VD_AGENT_FILE_XFER_STATUS,
-                             &msg, sizeof(msg), NULL);
-        spice_channel_wakeup(SPICE_CHANNEL(task->channel), FALSE);
-        file_xfer_completed(task, error);
-    }
-    /* else EOF, do nothing (wait for VD_AGENT_FILE_XFER_STATUS from agent) */
-}
-
-/* coroutine context */
-static void file_xfer_continue_read(SpiceFileXferTask *task)
-{
-    g_input_stream_read_async(G_INPUT_STREAM(task->file_stream),
-                              task->buffer,
-                              FILE_XFER_CHUNK_SIZE,
-                              G_PRIORITY_DEFAULT,
-                              task->cancellable,
-                              file_xfer_read_cb,
-                              task);
-    task->pending = TRUE;
-}
-
-/* coroutine context */
-static void file_xfer_handle_status(SpiceMainChannel *channel,
-                                    VDAgentFileXferStatusMessage *msg)
-{
-    SpiceMainChannelPrivate *c = channel->priv;
-    SpiceFileXferTask *task;
-    GError *error = NULL;
-
-
-    task = g_hash_table_lookup(c->file_xfer_tasks, GUINT_TO_POINTER(msg->id));
-    if (task == NULL) {
-        SPICE_DEBUG("cannot find task %d", msg->id);
-        return;
-    }
-
-    SPICE_DEBUG("task %d received response %d", msg->id, msg->result);
-
-    switch (msg->result) {
-    case VD_AGENT_FILE_XFER_STATUS_CAN_SEND_DATA:
-        if (task->pending) {
-            error = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
-                           "transfer received CAN_SEND_DATA in pending state");
-            break;
-        }
-        file_xfer_continue_read(task);
-        return;
-    case VD_AGENT_FILE_XFER_STATUS_CANCELLED:
-        error = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
-                            "transfer is cancelled by spice agent");
-        break;
-    case VD_AGENT_FILE_XFER_STATUS_ERROR:
-        error = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
-                            "some errors occurred in the spice agent");
-        break;
-    case VD_AGENT_FILE_XFER_STATUS_SUCCESS:
-        if (task->pending)
-            error = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
-                                "transfer received success in pending state");
-        break;
-    default:
-        g_warn_if_reached();
-        error = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
-                            "unhandled status type: %u", msg->result);
-        break;
-    }
-
-    file_xfer_completed(task, error);
-}
-
-/* any context: the message is not flushed immediately,
-   you can wakeup() the channel coroutine or send_msg_queue() */
-static void agent_max_clipboard(SpiceMainChannel *self)
-{
-    VDAgentMaxClipboard msg = { .max = spice_main_get_max_clipboard(self) };
-
-    if (!test_agent_cap(self, VD_AGENT_CAP_MAX_CLIPBOARD))
-        return;
-
-    agent_msg_queue(self, VD_AGENT_MAX_CLIPBOARD, sizeof(VDAgentMaxClipboard), &msg);
-}
-
-static void spice_main_set_max_clipboard(SpiceMainChannel *self, gint max)
-{
-    SpiceMainChannelPrivate *c;
-
-    g_return_if_fail(SPICE_IS_MAIN_CHANNEL(self));
-    g_return_if_fail(max >= -1);
-
-    c = self->priv;
-    if (max == spice_main_get_max_clipboard(self))
-        return;
-
-    c->max_clipboard = max;
-    agent_max_clipboard(self);
-    spice_channel_wakeup(SPICE_CHANNEL(self), FALSE);
-}
-
-/* coroutine context */
-static void main_agent_handle_msg(SpiceChannel *channel,
-                                  VDAgentMessage *msg, gpointer payload)
-{
-    SpiceMainChannel *self = SPICE_MAIN_CHANNEL(channel);
-    SpiceMainChannelPrivate *c = self->priv;
-    guint8 selection = VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD;
-
-    g_return_if_fail(msg->protocol == VD_AGENT_PROTOCOL);
-
-    switch (msg->type) {
-    case VD_AGENT_CLIPBOARD_RELEASE:
-    case VD_AGENT_CLIPBOARD_REQUEST:
-    case VD_AGENT_CLIPBOARD_GRAB:
-    case VD_AGENT_CLIPBOARD:
-        if (test_agent_cap(self, VD_AGENT_CAP_CLIPBOARD_SELECTION)) {
-            selection = *((guint8*)payload);
-            payload = ((guint8*)payload) + 4;
-            msg->size -= 4;
-        }
-        break;
-    default:
-        break;
-    }
-
-    switch (msg->type) {
-    case VD_AGENT_ANNOUNCE_CAPABILITIES:
-    {
-        VDAgentAnnounceCapabilities *caps = payload;
-        int i, size;
-
-        size = VD_AGENT_CAPS_SIZE_FROM_MSG_SIZE(msg->size);
-        if (size > VD_AGENT_CAPS_SIZE)
-            size = VD_AGENT_CAPS_SIZE;
-        memset(c->agent_caps, 0, sizeof(c->agent_caps));
-        for (i = 0; i < size * 32; i++) {
-            if (!VD_AGENT_HAS_CAPABILITY(caps->caps, size, i))
-                continue;
-            SPICE_DEBUG("%s: cap: %d (%s)", __FUNCTION__,
-                        i, NAME(agent_caps, i));
-            VD_AGENT_SET_CAPABILITY(c->agent_caps, i);
-        }
-        c->agent_caps_received = true;
-        g_coroutine_signal_emit(self, signals[SPICE_MAIN_AGENT_UPDATE], 0);
-        update_display_timer(SPICE_MAIN_CHANNEL(channel), 0);
-
-        if (caps->request)
-            agent_announce_caps(self);
-
-        if (test_agent_cap(self, VD_AGENT_CAP_DISPLAY_CONFIG) &&
-            !c->agent_display_config_sent) {
-            agent_display_config(self);
-            c->agent_display_config_sent = true;
-        }
-
-        agent_sync_audio_playback(self);
-        agent_sync_audio_record(self);
-
-        agent_max_clipboard(self);
-
-        agent_send_msg_queue(self);
-
-        break;
-    }
-    case VD_AGENT_CLIPBOARD:
-    {
-        VDAgentClipboard *cb = payload;
-        g_coroutine_signal_emit(self, signals[SPICE_MAIN_CLIPBOARD_SELECTION], 0, selection,
-                                cb->type, cb->data, msg->size - sizeof(VDAgentClipboard));
-
-       if (selection == VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD)
-           g_coroutine_signal_emit(self, signals[SPICE_MAIN_CLIPBOARD], 0,
-                              cb->type, cb->data, msg->size - sizeof(VDAgentClipboard));
-        break;
-    }
-    case VD_AGENT_CLIPBOARD_GRAB:
-    {
-        gboolean ret;
-        g_coroutine_signal_emit(self, signals[SPICE_MAIN_CLIPBOARD_SELECTION_GRAB], 0, selection,
-                          (guint8*)payload, msg->size / sizeof(uint32_t), &ret);
-        if (selection == VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD)
-            g_coroutine_signal_emit(self, signals[SPICE_MAIN_CLIPBOARD_GRAB], 0,
-                              payload, msg->size / sizeof(uint32_t), &ret);
-        break;
-    }
-    case VD_AGENT_CLIPBOARD_REQUEST:
-    {
-        gboolean ret;
-        VDAgentClipboardRequest *req = payload;
-        g_coroutine_signal_emit(self, signals[SPICE_MAIN_CLIPBOARD_SELECTION_REQUEST], 0, selection,
-                          req->type, &ret);
-
-        if (selection == VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD)
-            g_coroutine_signal_emit(self, signals[SPICE_MAIN_CLIPBOARD_REQUEST], 0,
-                              req->type, &ret);
-        break;
-    }
-    case VD_AGENT_CLIPBOARD_RELEASE:
-    {
-        g_coroutine_signal_emit(self, signals[SPICE_MAIN_CLIPBOARD_SELECTION_RELEASE], 0, selection);
-
-        if (selection == VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD)
-            g_coroutine_signal_emit(self, signals[SPICE_MAIN_CLIPBOARD_RELEASE], 0);
-        break;
-    }
-    case VD_AGENT_REPLY:
-    {
-        VDAgentReply *reply = payload;
-        SPICE_DEBUG("%s: reply: type %d, %s", __FUNCTION__, reply->type,
-                    reply->error == VD_AGENT_SUCCESS ? "success" : "error");
-        break;
-    }
-    case VD_AGENT_FILE_XFER_STATUS:
-        file_xfer_handle_status(self, payload);
-        break;
-    default:
-        g_warning("unhandled agent message type: %u (%s), size %u",
-                  msg->type, NAME(agent_msg_types, msg->type), msg->size);
-    }
-}
-
-/* coroutine context */
-static void main_handle_agent_data_msg(SpiceChannel* channel, int* msg_size, guchar** msg_pos)
-{
-    SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
-    int n;
-
-    if (c->agent_msg_pos < sizeof(VDAgentMessage)) {
-        n = MIN(sizeof(VDAgentMessage) - c->agent_msg_pos, *msg_size);
-        memcpy((uint8_t*)&c->agent_msg + c->agent_msg_pos, *msg_pos, n);
-        c->agent_msg_pos += n;
-        *msg_size -= n;
-        *msg_pos += n;
-        if (c->agent_msg_pos == sizeof(VDAgentMessage)) {
-            SPICE_DEBUG("agent msg start: msg_size=%d, protocol=%d, type=%d",
-                        c->agent_msg.size, c->agent_msg.protocol, c->agent_msg.type);
-            g_return_if_fail(c->agent_msg_data == NULL);
-            c->agent_msg_data = g_malloc0(c->agent_msg.size);
-        }
-    }
-
-    if (c->agent_msg_pos >= sizeof(VDAgentMessage)) {
-        n = MIN(sizeof(VDAgentMessage) + c->agent_msg.size - c->agent_msg_pos, *msg_size);
-        memcpy(c->agent_msg_data + c->agent_msg_pos - sizeof(VDAgentMessage), *msg_pos, n);
-        c->agent_msg_pos += n;
-        *msg_size -= n;
-        *msg_pos += n;
-    }
-
-    if (c->agent_msg_pos == sizeof(VDAgentMessage) + c->agent_msg.size) {
-        main_agent_handle_msg(channel, &c->agent_msg, c->agent_msg_data);
-        g_free(c->agent_msg_data);
-        c->agent_msg_data = NULL;
-        c->agent_msg_pos = 0;
-    }
-}
-
-/* coroutine context */
-static void main_handle_agent_data(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
-    guint8 *data;
-    int len;
-
-    g_warn_if_fail(c->agent_connected);
-
-    /* shortcut to avoid extra message allocation & copy if possible */
-    if (c->agent_msg_pos == 0) {
-        VDAgentMessage *msg;
-        guint msg_size;
-
-        msg = spice_msg_in_raw(in, &len);
-        msg_size = msg->size;
-
-        if (msg_size + sizeof(VDAgentMessage) == len) {
-            main_agent_handle_msg(channel, msg, msg->data);
-            return;
-        }
-    }
-
-    data = spice_msg_in_raw(in, &len);
-    while (len > 0) {
-        main_handle_agent_data_msg(channel, &len, &data);
-    }
-}
-
-/* coroutine context */
-static void main_handle_agent_token(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceMsgMainAgentTokens *tokens = spice_msg_in_parsed(in);
-    SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
-
-    c->agent_tokens += tokens->num_tokens;
-
-    agent_send_msg_queue(SPICE_MAIN_CHANNEL(channel));
-}
-
-/* main context */
-static void migrate_channel_new_cb(SpiceSession *s, SpiceChannel *channel, gpointer data)
-{
-    g_signal_connect(channel, "channel-event",
-                     G_CALLBACK(migrate_channel_event_cb), data);
-}
-
-static SpiceChannel* migrate_channel_connect(spice_migrate *mig, int type, int id)
-{
-    SPICE_DEBUG("migrate_channel_connect %d:%d", type, id);
-
-    SpiceChannel *newc = spice_channel_new(mig->session, type, id);
-    spice_channel_connect(newc);
-    mig->nchannels++;
-
-    return newc;
-}
-
-/* coroutine context */
-static void spice_main_channel_send_migration_handshake(SpiceChannel *channel)
-{
-    SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
-
-    if (!spice_channel_test_capability(channel, SPICE_MAIN_CAP_SEAMLESS_MIGRATE)) {
-        c->migrate_data->do_seamless = false;
-        g_idle_add(main_migrate_handshake_done, c->migrate_data);
-    } else {
-        SpiceMsgcMainMigrateDstDoSeamless msg_data;
-        SpiceMsgOut *msg_out;
-
-        msg_data.src_version = c->migrate_data->src_mig_version;
-
-        msg_out = spice_msg_out_new(channel, SPICE_MSGC_MAIN_MIGRATE_DST_DO_SEAMLESS);
-        msg_out->marshallers->msgc_main_migrate_dst_do_seamless(msg_out->marshaller, &msg_data);
-        spice_msg_out_send_internal(msg_out);
-    }
-}
-
-/* main context */
-static void migrate_channel_event_cb(SpiceChannel *channel, SpiceChannelEvent event,
-                                     gpointer data)
-{
-    spice_migrate *mig = data;
-    SpiceChannelPrivate  *c = SPICE_CHANNEL(channel)->priv;
-    SpiceSession *session;
-
-    g_return_if_fail(mig->nchannels > 0);
-    g_signal_handlers_disconnect_by_func(channel, migrate_channel_event_cb, data);
-
-    session = spice_channel_get_session(mig->src_channel);
-
-    switch (event) {
-    case SPICE_CHANNEL_OPENED:
-
-        if (c->channel_type == SPICE_CHANNEL_MAIN) {
-            if (mig->do_seamless) {
-                SpiceMainChannelPrivate *main_priv = SPICE_MAIN_CHANNEL(channel)->priv;
-
-                c->state = SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE;
-                mig->dst_channel = channel;
-                main_priv->migrate_data = mig;
-            } else {
-                c->state = SPICE_CHANNEL_STATE_MIGRATING;
-                mig->nchannels--;
-            }
-            /* now connect the rest of the channels */
-            GList *channels, *l;
-            l = channels = spice_session_get_channels(session);
-            while (l != NULL) {
-                SpiceChannelPrivate  *curc = SPICE_CHANNEL(l->data)->priv;
-                l = l->next;
-                if (curc->channel_type == SPICE_CHANNEL_MAIN)
-                    continue;
-                migrate_channel_connect(mig, curc->channel_type, curc->channel_id);
-            }
-            g_list_free(channels);
-        } else {
-            c->state = SPICE_CHANNEL_STATE_MIGRATING;
-            mig->nchannels--;
-        }
-
-        SPICE_DEBUG("migration: channel opened chan:%p, left %d", channel, mig->nchannels);
-        if (mig->nchannels == 0)
-            coroutine_yieldto(mig->from, NULL);
-        break;
-    default:
-        SPICE_DEBUG("error or unhandled channel event during migration: %d", event);
-        /* go back to main channel to report error */
-        coroutine_yieldto(mig->from, NULL);
-    }
-}
-
-/* main context */
-static gboolean main_migrate_handshake_done(gpointer data)
-{
-    spice_migrate *mig = data;
-    SpiceChannelPrivate  *c = SPICE_CHANNEL(mig->dst_channel)->priv;
-
-    g_return_val_if_fail(c->channel_type == SPICE_CHANNEL_MAIN, FALSE);
-    g_return_val_if_fail(c->state == SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE, FALSE);
-
-    c->state = SPICE_CHANNEL_STATE_MIGRATING;
-    mig->nchannels--;
-    if (mig->nchannels == 0)
-        coroutine_yieldto(mig->from, NULL);
-    return FALSE;
-}
-
-#ifdef __GNUC__
-typedef struct __attribute__ ((__packed__)) OldRedMigrationBegin {
-#else
-typedef struct __declspec(align(1)) OldRedMigrationBegin {
-#endif
-    uint16_t port;
-    uint16_t sport;
-    char host[0];
-} OldRedMigrationBegin;
-
-/* main context */
-static gboolean migrate_connect(gpointer data)
-{
-    spice_migrate *mig = data;
-    SpiceChannelPrivate  *c;
-    int port, sport;
-    const char *host;
-
-    g_return_val_if_fail(mig != NULL, FALSE);
-    g_return_val_if_fail(mig->info != NULL, FALSE);
-    g_return_val_if_fail(mig->nchannels == 0, FALSE);
-    c = SPICE_CHANNEL(mig->src_channel)->priv;
-    g_return_val_if_fail(c != NULL, FALSE);
-    g_return_val_if_fail(mig->session != NULL, FALSE);
-
-    spice_session_set_migration_state(mig->session, SPICE_SESSION_MIGRATION_CONNECTING);
-
-    if ((c->peer_hdr.major_version == 1) &&
-        (c->peer_hdr.minor_version < 1)) {
-        OldRedMigrationBegin *info = (OldRedMigrationBegin *)mig->info;
-        SPICE_DEBUG("migrate_begin old %s %d %d",
-                    info->host, info->port, info->sport);
-        port = info->port;
-        sport = info->sport;
-        host = info->host;
-    } else {
-        SpiceMigrationDstInfo *info = mig->info;
-        SPICE_DEBUG("migrate_begin %d %s %d %d",
-                    info->host_size, info->host_data, info->port, info->sport);
-        port = info->port;
-        sport = info->sport;
-        host = (char*)info->host_data;
-
-        if ((c->peer_hdr.major_version == 1) ||
-            (c->peer_hdr.major_version == 2 && c->peer_hdr.minor_version < 1)) {
-            GByteArray *pubkey = g_byte_array_new();
-
-            g_byte_array_append(pubkey, info->pub_key_data, info->pub_key_size);
-            g_object_set(mig->session,
-                         "pubkey", pubkey,
-                         "verify", SPICE_SESSION_VERIFY_PUBKEY,
-                         NULL);
-            g_byte_array_unref(pubkey);
-        } else if (info->cert_subject_size == 0 ||
-                   strlen((const char*)info->cert_subject_data) == 0) {
-            /* only verify hostname if no cert subject */
-            g_object_set(mig->session, "verify", SPICE_SESSION_VERIFY_HOSTNAME, NULL);
-        } else {
-            gchar *subject = g_alloca(info->cert_subject_size + 1);
-            strncpy(subject, (const char*)info->cert_subject_data, info->cert_subject_size);
-            subject[info->cert_subject_size] = '\0';
-
-            // session data are already copied
-            g_object_set(mig->session,
-                         "cert-subject", subject,
-                         "verify", SPICE_SESSION_VERIFY_SUBJECT,
-                         NULL);
-        }
-    }
-
-    if (g_getenv("SPICE_MIG_HOST"))
-        host = g_getenv("SPICE_MIG_HOST");
-
-    g_object_set(mig->session, "host", host, NULL);
-    spice_session_set_port(mig->session, port, FALSE);
-    spice_session_set_port(mig->session, sport, TRUE);
-    g_signal_connect(mig->session, "channel-new",
-                     G_CALLBACK(migrate_channel_new_cb), mig);
-
-    g_signal_emit(mig->src_channel, signals[SPICE_MIGRATION_STARTED], 0,
-                  mig->session);
-
-    /* the migration process is in 2 steps, first the main channel and
-       then the rest of the channels */
-    migrate_channel_connect(mig, SPICE_CHANNEL_MAIN, 0);
-
-    return FALSE;
-}
-
-/* coroutine context */
-static void main_migrate_connect(SpiceChannel *channel,
-                                 SpiceMigrationDstInfo *dst_info, bool do_seamless,
-                                 uint32_t src_mig_version)
-{
-    SpiceMainChannelPrivate *main_priv = SPICE_MAIN_CHANNEL(channel)->priv;
-    int reply_type = SPICE_MSGC_MAIN_MIGRATE_CONNECT_ERROR;
-    spice_migrate mig = { 0, };
-    SpiceMsgOut *out;
-    SpiceSession *session;
-
-    mig.src_channel = channel;
-    mig.info = dst_info;
-    mig.from = coroutine_self();
-    mig.do_seamless = do_seamless;
-    mig.src_mig_version = src_mig_version;
-
-    CHANNEL_DEBUG(channel, "migrate connect");
-    session = spice_channel_get_session(channel);
-    mig.session = spice_session_new_from_session(session);
-    if (mig.session == NULL)
-        goto end;
-    if (!spice_session_set_migration_session(session, mig.session))
-        goto end;
-
-    main_priv->migrate_data = &mig;
-
-    /* no need to track idle, call is sync for this coroutine */
-    g_idle_add(migrate_connect, &mig);
-
-    /* switch to main loop and wait for connections */
-    coroutine_yield(NULL);
-
-    if (mig.nchannels != 0) {
-        CHANNEL_DEBUG(channel, "migrate failed: some channels failed to connect");
-        spice_session_abort_migration(session);
-    } else {
-        if (mig.do_seamless) {
-            SPICE_DEBUG("migration (seamless): connections all ok");
-            reply_type = SPICE_MSGC_MAIN_MIGRATE_CONNECTED_SEAMLESS;
-        } else {
-            SPICE_DEBUG("migration (semi-seamless): connections all ok");
-            reply_type = SPICE_MSGC_MAIN_MIGRATE_CONNECTED;
-        }
-        spice_session_start_migrating(spice_channel_get_session(channel),
-                                      mig.do_seamless);
-    }
-
-end:
-    CHANNEL_DEBUG(channel, "migrate connect reply %d", reply_type);
-    out = spice_msg_out_new(SPICE_CHANNEL(channel), reply_type);
-    spice_msg_out_send(out);
-}
-
-/* coroutine context */
-static void main_handle_migrate_begin(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceMsgMainMigrationBegin *msg = spice_msg_in_parsed(in);
-
-    main_migrate_connect(channel, &msg->dst_info, false, 0);
-}
-
-/* coroutine context */
-static void main_handle_migrate_begin_seamless(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceMsgMainMigrateBeginSeamless *msg = spice_msg_in_parsed(in);
-
-    main_migrate_connect(channel, &msg->dst_info, true, msg->src_mig_version);
-}
-
-static void main_handle_migrate_dst_seamless_ack(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceChannelPrivate  *c = SPICE_CHANNEL(channel)->priv;
-    SpiceMainChannelPrivate *main_priv = SPICE_MAIN_CHANNEL(channel)->priv;
-
-    g_return_if_fail(c->state == SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE);
-    main_priv->migrate_data->do_seamless = true;
-    g_idle_add(main_migrate_handshake_done, main_priv->migrate_data);
-}
-
-static void main_handle_migrate_dst_seamless_nack(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceChannelPrivate  *c = SPICE_CHANNEL(channel)->priv;
-    SpiceMainChannelPrivate *main_priv = SPICE_MAIN_CHANNEL(channel)->priv;
-
-    g_return_if_fail(c->state == SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE);
-    main_priv->migrate_data->do_seamless = false;
-    g_idle_add(main_migrate_handshake_done, main_priv->migrate_data);
-}
-
-/* main context */
-static gboolean migrate_delayed(gpointer data)
-{
-    SpiceChannel *channel = data;
-    SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
-
-    g_warn_if_fail(c->migrate_delayed_id != 0);
-    c->migrate_delayed_id = 0;
-
-    spice_session_migrate_end(channel->priv->session);
-
-    return FALSE;
-}
-
-/* coroutine context */
-static void main_handle_migrate_end(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
-
-    SPICE_DEBUG("migrate end");
-
-    g_return_if_fail(c->migrate_delayed_id == 0);
-    g_return_if_fail(spice_channel_test_capability(channel, SPICE_MAIN_CAP_SEMI_SEAMLESS_MIGRATE));
-
-    c->migrate_delayed_id = g_idle_add(migrate_delayed, channel);
-}
-
-/* main context */
-static gboolean switch_host_delayed(gpointer data)
-{
-    SpiceChannel *channel = data;
-    SpiceSession *session;
-    SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
-
-    g_warn_if_fail(c->switch_host_delayed_id != 0);
-    c->switch_host_delayed_id = 0;
-
-    session = spice_channel_get_session(channel);
-
-    spice_channel_disconnect(channel, SPICE_CHANNEL_SWITCHING);
-    spice_session_switching_disconnect(session);
-
-    return FALSE;
-}
-
-/* coroutine context */
-static void main_handle_migrate_switch_host(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceMsgMainMigrationSwitchHost *mig = spice_msg_in_parsed(in);
-    SpiceSession *session;
-    char *host = (char *)mig->host_data;
-    char *subject = NULL;
-    SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
-
-    g_return_if_fail(host[mig->host_size - 1] == '\0');
-
-    if (mig->cert_subject_size) {
-        subject = (char *)mig->cert_subject_data;
-        g_return_if_fail(subject[mig->cert_subject_size - 1] == '\0');
-    }
-
-    SPICE_DEBUG("migrate_switch %s %d %d %s",
-                host, mig->port, mig->sport, subject);
-
-    if (c->switch_host_delayed_id != 0) {
-        g_warning("Switching host already in progress, aborting it");
-        g_warn_if_fail(g_source_remove(c->switch_host_delayed_id));
-        c->switch_host_delayed_id = 0;
-    }
-
-    session = spice_channel_get_session(channel);
-    spice_session_set_migration_state(session, SPICE_SESSION_MIGRATION_SWITCHING);
-    g_object_set(session,
-                 "host", host,
-                 "cert-subject", subject,
-                 NULL);
-    spice_session_set_port(session, mig->port, FALSE);
-    spice_session_set_port(session, mig->sport, TRUE);
-
-    c->switch_host_delayed_id = g_idle_add(switch_host_delayed, channel);
-}
-
-/* coroutine context */
-static void main_handle_migrate_cancel(SpiceChannel *channel,
-                                       SpiceMsgIn *in G_GNUC_UNUSED)
-{
-    SpiceSession *session;
-
-    SPICE_DEBUG("migrate_cancel");
-    session = spice_channel_get_session(channel);
-    spice_session_abort_migration(session);
-}
-
-static void channel_set_handlers(SpiceChannelClass *klass)
-{
-    static const spice_msg_handler handlers[] = {
-        [ SPICE_MSG_MAIN_INIT ]                = main_handle_init,
-        [ SPICE_MSG_MAIN_NAME ]                = main_handle_name,
-        [ SPICE_MSG_MAIN_UUID ]                = main_handle_uuid,
-        [ SPICE_MSG_MAIN_CHANNELS_LIST ]       = main_handle_channels_list,
-        [ SPICE_MSG_MAIN_MOUSE_MODE ]          = main_handle_mouse_mode,
-        [ SPICE_MSG_MAIN_MULTI_MEDIA_TIME ]    = main_handle_mm_time,
-
-        [ SPICE_MSG_MAIN_AGENT_CONNECTED ]     = main_handle_agent_connected,
-        [ SPICE_MSG_MAIN_AGENT_DISCONNECTED ]  = main_handle_agent_disconnected,
-        [ SPICE_MSG_MAIN_AGENT_DATA ]          = main_handle_agent_data,
-        [ SPICE_MSG_MAIN_AGENT_TOKEN ]         = main_handle_agent_token,
-
-        [ SPICE_MSG_MAIN_MIGRATE_BEGIN ]       = main_handle_migrate_begin,
-        [ SPICE_MSG_MAIN_MIGRATE_END ]         = main_handle_migrate_end,
-        [ SPICE_MSG_MAIN_MIGRATE_CANCEL ]      = main_handle_migrate_cancel,
-        [ SPICE_MSG_MAIN_MIGRATE_SWITCH_HOST ] = main_handle_migrate_switch_host,
-        [ SPICE_MSG_MAIN_AGENT_CONNECTED_TOKENS ]   = main_handle_agent_connected_tokens,
-        [ SPICE_MSG_MAIN_MIGRATE_BEGIN_SEAMLESS ]   = main_handle_migrate_begin_seamless,
-        [ SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_ACK]  = main_handle_migrate_dst_seamless_ack,
-        [ SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_NACK] = main_handle_migrate_dst_seamless_nack,
-    };
-
-    spice_channel_set_handlers(klass, handlers, G_N_ELEMENTS(handlers));
-}
-
-/* coroutine context */
-static void spice_main_handle_msg(SpiceChannel *channel, SpiceMsgIn *msg)
-{
-    int type = spice_msg_in_type(msg);
-    SpiceChannelClass *parent_class;
-    SpiceChannelPrivate *c = SPICE_CHANNEL(channel)->priv;
-
-    parent_class = SPICE_CHANNEL_CLASS(spice_main_channel_parent_class);
-
-    if (c->state == SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE) {
-        if (type != SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_ACK &&
-            type != SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_NACK) {
-            g_critical("unexpected msg (%d)."
-                       "Only MIGRATE_DST_SEAMLESS_ACK/NACK are allowed", type);
-            return;
-        }
-    }
-
-    parent_class->handle_msg(channel, msg);
-}
-
-/**
- * spice_main_agent_test_capability:
- * @channel:
- * @cap: an agent capability identifier
- *
- * Test capability of a remote agent.
- *
- * Returns: %TRUE if @cap (channel kind capability) is available.
- **/
-gboolean spice_main_agent_test_capability(SpiceMainChannel *channel, guint32 cap)
-{
-    g_return_val_if_fail(SPICE_IS_MAIN_CHANNEL(channel), FALSE);
-
-    return test_agent_cap(channel, cap);
-}
-
-/**
- * spice_main_update_display:
- * @channel:
- * @id: display ID
- * @x: x position
- * @y: y position
- * @width: display width
- * @height: display height
- * @update: if %TRUE, update guest resolution after 1sec.
- *
- * Update the display @id resolution.
- *
- * If @update is %TRUE, the remote configuration will be updated too
- * after 1 second without further changes. You can send when you want
- * without delay the new configuration to the remote with
- * spice_main_send_monitor_config()
- **/
-void spice_main_update_display(SpiceMainChannel *channel, int id,
-                               int x, int y, int width, int height,
-                               gboolean update)
-{
-    SpiceMainChannelPrivate *c;
-
-    g_return_if_fail(channel != NULL);
-    g_return_if_fail(SPICE_IS_MAIN_CHANNEL(channel));
-    g_return_if_fail(x >= 0);
-    g_return_if_fail(y >= 0);
-    g_return_if_fail(width >= 0);
-    g_return_if_fail(height >= 0);
-
-    c = SPICE_MAIN_CHANNEL(channel)->priv;
-
-    g_return_if_fail(id < SPICE_N_ELEMENTS(c->display));
-
-    c->display[id].x      = x;
-    c->display[id].y      = y;
-    c->display[id].width  = width;
-    c->display[id].height = height;
-
-    if (update)
-        update_display_timer(channel, 1);
-}
-
-/**
- * spice_main_set_display:
- * @channel:
- * @id: display ID
- * @x: x position
- * @y: y position
- * @width: display width
- * @height: display height
- *
- * Notify the guest of screen resolution change. The notification is
- * sent 1 second later, if no further changes happen.
- **/
-void spice_main_set_display(SpiceMainChannel *channel, int id,
-                            int x, int y, int width, int height)
-{
-    spice_main_update_display(channel, id, x, y, width, height, TRUE);
-}
-
-/**
- * spice_main_clipboard_grab:
- * @channel:
- * @types: an array of #VD_AGENT_CLIPBOARD types available in the clipboard
- * @ntypes: the number of @types
- *
- * Grab the guest clipboard, with #VD_AGENT_CLIPBOARD @types.
- *
- * Deprecated: 0.6: use spice_main_clipboard_selection_grab() instead.
- **/
-void spice_main_clipboard_grab(SpiceMainChannel *channel, guint32 *types, int ntypes)
-{
-    spice_main_clipboard_selection_grab(channel, VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD, types, ntypes);
-}
-
-/**
- * spice_main_clipboard_selection_grab:
- * @channel:
- * @selection: one of the clipboard #VD_AGENT_CLIPBOARD_SELECTION_*
- * @types: an array of #VD_AGENT_CLIPBOARD types available in the clipboard
- * @ntypes: the number of @types
- *
- * Grab the guest clipboard, with #VD_AGENT_CLIPBOARD @types.
- *
- * Since: 0.6
- **/
-void spice_main_clipboard_selection_grab(SpiceMainChannel *channel, guint selection,
-                                         guint32 *types, int ntypes)
-{
-    g_return_if_fail(channel != NULL);
-    g_return_if_fail(SPICE_IS_MAIN_CHANNEL(channel));
-
-    agent_clipboard_grab(channel, selection, types, ntypes);
-    spice_channel_wakeup(SPICE_CHANNEL(channel), FALSE);
-}
-
-/**
- * spice_main_clipboard_release:
- * @channel:
- *
- * Release the clipboard (for example, when the client loses the
- * clipboard grab): Inform the guest no clipboard data is available.
- *
- * Deprecated: 0.6: use spice_main_clipboard_selection_release() instead.
- **/
-void spice_main_clipboard_release(SpiceMainChannel *channel)
-{
-    spice_main_clipboard_selection_release(channel, VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD);
-}
-
-/**
- * spice_main_clipboard_selection_release:
- * @channel:
- * @selection: one of the clipboard #VD_AGENT_CLIPBOARD_SELECTION_*
- *
- * Release the clipboard (for example, when the client loses the
- * clipboard grab): Inform the guest no clipboard data is available.
- *
- * Since: 0.6
- **/
-void spice_main_clipboard_selection_release(SpiceMainChannel *channel, guint selection)
-{
-    g_return_if_fail(channel != NULL);
-    g_return_if_fail(SPICE_IS_MAIN_CHANNEL(channel));
-
-    SpiceMainChannelPrivate *c = channel->priv;
-
-    if (!c->agent_connected)
-        return;
-
-    agent_clipboard_release(channel, selection);
-    spice_channel_wakeup(SPICE_CHANNEL(channel), FALSE);
-}
-
-/**
- * spice_main_clipboard_notify:
- * @channel:
- * @type: a #VD_AGENT_CLIPBOARD type
- * @data: clipboard data
- * @size: data length in bytes
- *
- * Send the clipboard data to the guest.
- *
- * Deprecated: 0.6: use spice_main_clipboard_selection_notify() instead.
- **/
-void spice_main_clipboard_notify(SpiceMainChannel *channel,
-                                 guint32 type, const guchar *data, size_t size)
-{
-    spice_main_clipboard_selection_notify(channel, VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD,
-                                          type, data, size);
-}
-
-/**
- * spice_main_clipboard_selection_notify:
- * @channel:
- * @selection: one of the clipboard #VD_AGENT_CLIPBOARD_SELECTION_*
- * @type: a #VD_AGENT_CLIPBOARD type
- * @data: clipboard data
- * @size: data length in bytes
- *
- * Send the clipboard data to the guest.
- *
- * Since: 0.6
- **/
-void spice_main_clipboard_selection_notify(SpiceMainChannel *channel, guint selection,
-                                           guint32 type, const guchar *data, size_t size)
-{
-    g_return_if_fail(channel != NULL);
-    g_return_if_fail(SPICE_IS_MAIN_CHANNEL(channel));
-
-    agent_clipboard_notify(channel, selection, type, data, size);
-    spice_channel_wakeup(SPICE_CHANNEL(channel), FALSE);
-}
-
-/**
- * spice_main_clipboard_request:
- * @channel:
- * @type: a #VD_AGENT_CLIPBOARD type
- *
- * Request clipboard data of @type from the guest. The reply is sent
- * through the #SpiceMainChannel::main-clipboard signal.
- *
- * Deprecated: 0.6: use spice_main_clipboard_selection_request() instead.
- **/
-void spice_main_clipboard_request(SpiceMainChannel *channel, guint32 type)
-{
-    spice_main_clipboard_selection_request(channel, VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD, type);
-}
-
-/**
- * spice_main_clipboard_selection_request:
- * @channel:
- * @selection: one of the clipboard #VD_AGENT_CLIPBOARD_SELECTION_*
- * @type: a #VD_AGENT_CLIPBOARD type
- *
- * Request clipboard data of @type from the guest. The reply is sent
- * through the #SpiceMainChannel::main-clipboard-selection signal.
- *
- * Since: 0.6
- **/
-void spice_main_clipboard_selection_request(SpiceMainChannel *channel, guint selection, guint32 type)
-{
-    g_return_if_fail(channel != NULL);
-    g_return_if_fail(SPICE_IS_MAIN_CHANNEL(channel));
-
-    agent_clipboard_request(channel, selection, type);
-    spice_channel_wakeup(SPICE_CHANNEL(channel), FALSE);
-}
-
-/**
- * spice_main_set_display_enabled:
- * @channel: a #SpiceMainChannel
- * @id: display ID (if -1: set all displays)
- * @enabled: wether display @id is enabled
- *
- * When sending monitor configuration to agent guest, don't set
- * display @id, which the agent translates to disabling the display
- * id. Note: this will take effect next time the monitor
- * configuration is sent.
- *
- * Since: 0.6
- **/
-void spice_main_set_display_enabled(SpiceMainChannel *channel, int id, gboolean enabled)
-{
-    g_return_if_fail(channel != NULL);
-    g_return_if_fail(SPICE_IS_MAIN_CHANNEL(channel));
-    g_return_if_fail(id >= -1);
-
-    SpiceMainChannelPrivate *c = channel->priv;
-
-    if (id == -1) {
-        gint i;
-        for (i = 0; i < G_N_ELEMENTS(c->display); i++) {
-            c->display[i].enabled = enabled;
-            c->display[i].enabled_set = TRUE;
-        }
-    } else {
-        g_return_if_fail(id < G_N_ELEMENTS(c->display));
-        if (c->display[id].enabled == enabled)
-            return;
-        c->display[id].enabled = enabled;
-        c->display[id].enabled_set = TRUE;
-    }
-
-    update_display_timer(channel, 1);
-}
-
-static void file_xfer_completed(SpiceFileXferTask *task, GError *error)
-{
-    /* In case of multiple errors we only report the first error */
-    if (task->error)
-        g_clear_error(&error);
-    if (error) {
-        SPICE_DEBUG("File %s xfer failed: %s",
-                    g_file_get_path(task->file), error->message);
-        task->error = error;
-    }
-
-    if (task->pending)
-        return;
-
-    if (!task->file_stream) {
-        file_xfer_close_cb(NULL, NULL, task);
-        return;
-    }
-
-    g_input_stream_close_async(G_INPUT_STREAM(task->file_stream),
-                               G_PRIORITY_DEFAULT,
-                               task->cancellable,
-                               file_xfer_close_cb,
-                               task);
-    task->pending = TRUE;
-}
-
-static void file_xfer_info_async_cb(GObject *obj, GAsyncResult *res, gpointer data)
-{
-    GFileInfo *info;
-    GFile *file = G_FILE(obj);
-    GError *error = NULL;
-    GKeyFile *keyfile = NULL;
-    gchar *basename = NULL;
-    VDAgentFileXferStartMessage msg;
-    gsize /*msg_size*/ data_len;
-    gchar *string;
-    SpiceFileXferTask *task = (SpiceFileXferTask *)data;
-
-    task->pending = FALSE;
-    info = g_file_query_info_finish(file, res, &error);
-    if (error || task->error)
-        goto failed;
-
-    task->file_size =
-        g_file_info_get_attribute_uint64(info, G_FILE_ATTRIBUTE_STANDARD_SIZE);
-    keyfile = g_key_file_new();
-
-    /* File name */
-    basename = g_file_get_basename(file);
-    g_key_file_set_string(keyfile, "vdagent-file-xfer", "name", basename);
-    g_free(basename);
-    /* File size */
-    g_key_file_set_uint64(keyfile, "vdagent-file-xfer", "size", task->file_size);
-
-    /* Save keyfile content to memory. TODO: more file attributions
-       need to be sent to guest */
-    string = g_key_file_to_data(keyfile, &data_len, &error);
-    g_key_file_free(keyfile);
-    if (error)
-        goto failed;
-
-    /* Create file-xfer start message */
-    msg.id = task->id;
-    agent_msg_queue_many(task->channel, VD_AGENT_FILE_XFER_START,
-                         &msg, sizeof(msg),
-                         string, data_len + 1, NULL);
-    g_free(string);
-    spice_channel_wakeup(SPICE_CHANNEL(task->channel), FALSE);
-    return;
-
-failed:
-    file_xfer_completed(task, error);
-}
-
-static void file_xfer_read_async_cb(GObject *obj, GAsyncResult *res, gpointer data)
-{
-    GFile *file = G_FILE(obj);
-    SpiceFileXferTask *task = (SpiceFileXferTask *)data;
-    GError *error = NULL;
-
-    task->pending = FALSE;
-    task->file_stream = g_file_read_finish(file, res, &error);
-    if (error || task->error) {
-        file_xfer_completed(task, error);
-        return;
-    }
-
-    g_file_query_info_async(task->file,
-                            G_FILE_ATTRIBUTE_STANDARD_SIZE,
-                            G_FILE_QUERY_INFO_NONE,
-                            G_PRIORITY_DEFAULT,
-                            task->cancellable,
-                            file_xfer_info_async_cb,
-                            task);
-    task->pending = TRUE;
-}
-
-static void file_xfer_send_start_msg_async(SpiceMainChannel *channel,
-                                           GFile **files,
-                                           GFileCopyFlags flags,
-                                           GCancellable *cancellable,
-                                           GFileProgressCallback progress_callback,
-                                           gpointer progress_callback_data,
-                                           GAsyncReadyCallback callback,
-                                           gpointer user_data)
-{
-    SpiceMainChannelPrivate *c = channel->priv;
-    SpiceFileXferTask *task;
-    static uint32_t xfer_id;    /* Used to identify task id */
-    gint i;
-
-    for (i = 0; files[i] != NULL && !g_cancellable_is_cancelled(cancellable); i++) {
-        task = g_malloc0(sizeof(SpiceFileXferTask));
-        task->id = ++xfer_id;
-        task->channel = g_object_ref(channel);
-        task->file = g_object_ref(files[i]);
-        task->flags = flags;
-        task->cancellable = cancellable;
-        task->progress_callback = progress_callback;
-        task->progress_callback_data = progress_callback_data;
-        task->callback = callback;
-        task->user_data = user_data;
-
-        CHANNEL_DEBUG(task->channel, "Insert a xfer task:%d to task list", task->id);
-        g_hash_table_insert(c->file_xfer_tasks, GUINT_TO_POINTER(task->id), task);
-
-        g_file_read_async(files[i],
-                          G_PRIORITY_DEFAULT,
-                          cancellable,
-                          file_xfer_read_async_cb,
-                          task);
-        task->pending = TRUE;
-    }
-}
-
-/**
- * spice_main_file_copy_async:
- * @sources: #GFile to be transfer
- * @flags: set of #GFileCopyFlags
- * @cancellable: (allow-none): optional #GCancellable object, %NULL to ignore
- * @progress_callback: (allow-none) (scope call): function to callback with
- *     progress information, or %NULL if progress information is not needed
- * @progress_callback_data: (closure): user data to pass to @progress_callback
- * @callback: a #GAsyncReadyCallback to call when the request is satisfied
- * @user_data: the data to pass to callback function
- *
- * Copies the file @sources to guest
- *
- * If @cancellable is not %NULL, then the operation can be cancelled by
- * triggering the cancellable object from another thread. If the operation
- * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned.
- *
- * If @progress_callback is not %NULL, then the operation can be monitored by
- * setting this to a #GFileProgressCallback function. @progress_callback_data
- * will be passed to this function. It is guaranteed that this callback will
- * be called after all data has been transferred with the total number of bytes
- * copied during the operation.
- *
- * When the operation is finished, callback will be called. You can then call
- * spice_main_file_copy_finish() to get the result of the operation.
- *
- **/
-void spice_main_file_copy_async(SpiceMainChannel *channel,
-                                GFile **sources,
-                                GFileCopyFlags flags,
-                                GCancellable *cancellable,
-                                GFileProgressCallback progress_callback,
-                                gpointer progress_callback_data,
-                                GAsyncReadyCallback callback,
-                                gpointer user_data)
-{
-    SpiceMainChannelPrivate *c = channel->priv;
-
-    g_return_if_fail(channel != NULL);
-    g_return_if_fail(SPICE_IS_MAIN_CHANNEL(channel));
-    g_return_if_fail(sources != NULL);
-
-    if (!c->agent_connected) {
-        g_simple_async_report_error_in_idle(G_OBJECT(channel),
-                                            callback,
-                                            user_data,
-                                            SPICE_CLIENT_ERROR,
-                                            SPICE_CLIENT_ERROR_FAILED,
-                                            "The agent is not connected");
-        return;
-    }
-
-    file_xfer_send_start_msg_async(channel,
-                                   sources,
-                                   flags,
-                                   cancellable,
-                                   progress_callback,
-                                   progress_callback_data,
-                                   callback,
-                                   user_data);
-}
-
-/**
- * spice_main_file_copy_finish:
- * @result: a #GAsyncResult.
- * @error: a #GError, or %NULL
- *
- * Finishes copying the file started with
- * spice_main_file_copy_async().
- *
- * Returns: a %TRUE on success, %FALSE on error.
- **/
-gboolean spice_main_file_copy_finish(SpiceMainChannel *channel,
-                                     GAsyncResult *result,
-                                     GError **error)
-{
-    GSimpleAsyncResult *simple;
-
-    g_return_val_if_fail(SPICE_IS_MAIN_CHANNEL(channel), FALSE);
-    g_return_val_if_fail(g_simple_async_result_is_valid(result,
-        G_OBJECT(channel), spice_main_file_copy_async), FALSE);
-
-    simple = (GSimpleAsyncResult *)result;
-
-    if (g_simple_async_result_propagate_error(simple, error)) {
-        return FALSE;
-    }
-
-    return g_simple_async_result_get_op_res_gboolean(simple);
-}
diff --git a/gtk/channel-main.h b/gtk/channel-main.h
deleted file mode 100644
index 3e4fc42..0000000
--- a/gtk/channel-main.h
+++ /dev/null
@@ -1,109 +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_CLIENT_MAIN_CHANNEL_H__
-#define __SPICE_CLIENT_MAIN_CHANNEL_H__
-
-#include "spice-client.h"
-
-G_BEGIN_DECLS
-
-#define SPICE_TYPE_MAIN_CHANNEL            (spice_main_channel_get_type())
-#define SPICE_MAIN_CHANNEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), SPICE_TYPE_MAIN_CHANNEL, SpiceMainChannel))
-#define SPICE_MAIN_CHANNEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), SPICE_TYPE_MAIN_CHANNEL, SpiceMainChannelClass))
-#define SPICE_IS_MAIN_CHANNEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), SPICE_TYPE_MAIN_CHANNEL))
-#define SPICE_IS_MAIN_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SPICE_TYPE_MAIN_CHANNEL))
-#define SPICE_MAIN_CHANNEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), SPICE_TYPE_MAIN_CHANNEL, SpiceMainChannelClass))
-
-typedef struct _SpiceMainChannel SpiceMainChannel;
-typedef struct _SpiceMainChannelClass SpiceMainChannelClass;
-typedef struct _SpiceMainChannelPrivate SpiceMainChannelPrivate;
-
-/**
- * SpiceMainChannel:
- *
- * The #SpiceMainChannel struct is opaque and should not be accessed directly.
- */
-struct _SpiceMainChannel {
-    SpiceChannel parent;
-
-    /*< private >*/
-    SpiceMainChannelPrivate *priv;
-    /* Do not add fields to this struct */
-};
-
-/**
- * SpiceMainChannelClass:
- * @parent_class: Parent class.
- * @mouse_update: Signal class handler for the #SpiceMainChannel::mouse-update signal.
- * @agent_update: Signal class handler for the #SpiceMainChannel::agent-update signal.
- *
- * Class structure for #SpiceMainChannel.
- */
-struct _SpiceMainChannelClass {
-    SpiceChannelClass parent_class;
-
-    /* signals */
-    void (*mouse_update)(SpiceChannel *channel);
-    void (*agent_update)(SpiceChannel *channel);
-
-    /*< private >*/
-    /* Do not add fields to this struct */
-};
-
-GType spice_main_channel_get_type(void);
-
-void spice_main_set_display(SpiceMainChannel *channel, int id,
-                            int x, int y, int width, int height);
-void spice_main_update_display(SpiceMainChannel *channel, int id,
-                               int x, int y, int width, int height, gboolean update);
-void spice_main_set_display_enabled(SpiceMainChannel *channel, int id, gboolean enabled);
-gboolean spice_main_send_monitor_config(SpiceMainChannel *channel);
-
-void spice_main_clipboard_selection_grab(SpiceMainChannel *channel, guint selection, guint32 *types, int ntypes);
-void spice_main_clipboard_selection_release(SpiceMainChannel *channel, guint selection);
-void spice_main_clipboard_selection_notify(SpiceMainChannel *channel, guint selection, guint32 type, const guchar *data, size_t size);
-void spice_main_clipboard_selection_request(SpiceMainChannel *channel, guint selection, guint32 type);
-
-gboolean spice_main_agent_test_capability(SpiceMainChannel *channel, guint32 cap);
-void spice_main_file_copy_async(SpiceMainChannel *channel,
-                                GFile **sources,
-                                GFileCopyFlags flags,
-                                GCancellable *cancellable,
-                                GFileProgressCallback progress_callback,
-                                gpointer progress_callback_data,
-                                GAsyncReadyCallback callback,
-                                gpointer user_data);
-
-gboolean spice_main_file_copy_finish(SpiceMainChannel *channel,
-                                     GAsyncResult *result,
-                                     GError **error);
-
-#ifndef SPICE_DISABLE_DEPRECATED
-SPICE_DEPRECATED_FOR(spice_main_clipboard_selection_grab)
-void spice_main_clipboard_grab(SpiceMainChannel *channel, guint32 *types, int ntypes);
-SPICE_DEPRECATED_FOR(spice_main_clipboard_selection_release)
-void spice_main_clipboard_release(SpiceMainChannel *channel);
-SPICE_DEPRECATED_FOR(spice_main_clipboard_selection_notify)
-void spice_main_clipboard_notify(SpiceMainChannel *channel, guint32 type, const guchar *data, size_t size);
-SPICE_DEPRECATED_FOR(spice_main_clipboard_selection_request)
-void spice_main_clipboard_request(SpiceMainChannel *channel, guint32 type);
-#endif
-
-G_END_DECLS
-
-#endif /* __SPICE_CLIENT_MAIN_CHANNEL_H__ */
diff --git a/gtk/channel-playback-priv.h b/gtk/channel-playback-priv.h
deleted file mode 100644
index aa33d2c..0000000
--- a/gtk/channel-playback-priv.h
+++ /dev/null
@@ -1,24 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2013 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_CLIENT_PLAYBACK_CHANNEL_PRIV_H__
-#define __SPICE_CLIENT_PLAYBACK_CHANNEL_PRIV_H__
-
-gboolean spice_playback_channel_is_active(SpicePlaybackChannel *channel);
-guint32 spice_playback_channel_get_latency(SpicePlaybackChannel *channel);
-void spice_playback_channel_sync_latency(SpicePlaybackChannel *channel);
-#endif
diff --git a/gtk/channel-playback.c b/gtk/channel-playback.c
deleted file mode 100644
index d8a181e..0000000
--- a/gtk/channel-playback.c
+++ /dev/null
@@ -1,496 +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-channel-priv.h"
-#include "spice-session-priv.h"
-
-#include "spice-marshal.h"
-
-#include "common/snd_codec.h"
-
-/**
- * SECTION:channel-playback
- * @short_description: audio stream for playback
- * @title: Playback Channel
- * @section_id:
- * @see_also: #SpiceChannel, and #SpiceAudio
- * @stability: Stable
- * @include: channel-playback.h
- *
- * #SpicePlaybackChannel class handles an audio playback stream. The
- * audio data is received via #SpicePlaybackChannel::playback-data
- * signal, and is controlled by the guest with
- * #SpicePlaybackChannel::playback-stop and
- * #SpicePlaybackChannel::playback-start signal events.
- *
- * Note: You may be interested to let the #SpiceAudio class play and
- * record audio channels for your application.
- */
-
-#define SPICE_PLAYBACK_CHANNEL_GET_PRIVATE(obj)                                  \
-    (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_PLAYBACK_CHANNEL, SpicePlaybackChannelPrivate))
-
-struct _SpicePlaybackChannelPrivate {
-    int                         mode;
-    SndCodec                    codec;
-    guint32                     frame_count;
-    guint32                     last_time;
-    guint8                      nchannels;
-    guint16                     *volume;
-    guint8                      mute;
-    gboolean                    is_active;
-    guint32                     latency;
-    guint32                     min_latency;
-};
-
-G_DEFINE_TYPE(SpicePlaybackChannel, spice_playback_channel, SPICE_TYPE_CHANNEL)
-
-/* Properties */
-enum {
-    PROP_0,
-    PROP_NCHANNELS,
-    PROP_VOLUME,
-    PROP_MUTE,
-    PROP_MIN_LATENCY,
-};
-
-/* Signals */
-enum {
-    SPICE_PLAYBACK_START,
-    SPICE_PLAYBACK_DATA,
-    SPICE_PLAYBACK_STOP,
-    SPICE_PLAYBACK_GET_DELAY,
-
-    SPICE_PLAYBACK_LAST_SIGNAL,
-};
-
-static guint signals[SPICE_PLAYBACK_LAST_SIGNAL];
-static void channel_set_handlers(SpiceChannelClass *klass);
-
-/* ------------------------------------------------------------------ */
-
-#define SPICE_PLAYBACK_DEFAULT_LATENCY_MS 200
-
-static void spice_playback_channel_reset_capabilities(SpiceChannel *channel)
-{
-    if (!g_getenv("SPICE_DISABLE_CELT"))
-        if (snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_CELT_0_5_1, SND_CODEC_ANY_FREQUENCY))
-            spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_PLAYBACK_CAP_CELT_0_5_1);
-    if (!g_getenv("SPICE_DISABLE_OPUS"))
-        if (snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_OPUS, SND_CODEC_ANY_FREQUENCY))
-            spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_PLAYBACK_CAP_OPUS);
-    spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_PLAYBACK_CAP_VOLUME);
-    spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_PLAYBACK_CAP_LATENCY);
-}
-
-static void spice_playback_channel_init(SpicePlaybackChannel *channel)
-{
-    channel->priv = SPICE_PLAYBACK_CHANNEL_GET_PRIVATE(channel);
-
-    spice_playback_channel_reset_capabilities(SPICE_CHANNEL(channel));
-}
-
-static void spice_playback_channel_finalize(GObject *obj)
-{
-    SpicePlaybackChannelPrivate *c = SPICE_PLAYBACK_CHANNEL(obj)->priv;
-
-    snd_codec_destroy(&c->codec);
-
-    g_free(c->volume);
-    c->volume = NULL;
-
-    if (G_OBJECT_CLASS(spice_playback_channel_parent_class)->finalize)
-        G_OBJECT_CLASS(spice_playback_channel_parent_class)->finalize(obj);
-}
-
-static void spice_playback_channel_get_property(GObject    *gobject,
-                                                guint       prop_id,
-                                                GValue     *value,
-                                                GParamSpec *pspec)
-{
-    SpicePlaybackChannel *channel = SPICE_PLAYBACK_CHANNEL(gobject);
-    SpicePlaybackChannelPrivate *c = channel->priv;
-
-    switch (prop_id) {
-    case PROP_VOLUME:
-        g_value_set_pointer(value, c->volume);
-        break;
-    case PROP_NCHANNELS:
-        g_value_set_uint(value, c->nchannels);
-        break;
-    case PROP_MUTE:
-        g_value_set_boolean(value, c->mute);
-        break;
-    case PROP_MIN_LATENCY:
-        g_value_set_uint(value, c->min_latency);
-        break;
-    default:
-        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
-        break;
-    }
-}
-
-static void spice_playback_channel_set_property(GObject      *gobject,
-                                                guint         prop_id,
-                                                const GValue *value,
-                                                GParamSpec   *pspec)
-{
-    switch (prop_id) {
-    case PROP_VOLUME:
-        /* TODO: request guest volume change */
-        break;
-    case PROP_MUTE:
-        /* TODO: request guest mute change */
-        break;
-    default:
-        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
-        break;
-    }
-}
-
-/* main or coroutine context */
-static void spice_playback_channel_reset(SpiceChannel *channel, gboolean migrating)
-{
-    SpicePlaybackChannelPrivate *c = SPICE_PLAYBACK_CHANNEL(channel)->priv;
-
-    snd_codec_destroy(&c->codec);
-    g_coroutine_signal_emit(channel, signals[SPICE_PLAYBACK_STOP], 0);
-    c->is_active = FALSE;
-
-    SPICE_CHANNEL_CLASS(spice_playback_channel_parent_class)->channel_reset(channel, migrating);
-}
-
-static void spice_playback_channel_class_init(SpicePlaybackChannelClass *klass)
-{
-    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
-    SpiceChannelClass *channel_class = SPICE_CHANNEL_CLASS(klass);
-
-    gobject_class->finalize     = spice_playback_channel_finalize;
-    gobject_class->get_property = spice_playback_channel_get_property;
-    gobject_class->set_property = spice_playback_channel_set_property;
-
-    channel_class->channel_reset = spice_playback_channel_reset;
-    channel_class->channel_reset_capabilities = spice_playback_channel_reset_capabilities;
-
-    g_object_class_install_property
-        (gobject_class, PROP_NCHANNELS,
-         g_param_spec_uint("nchannels",
-                           "Number of Channels",
-                           "Number of Channels",
-                           0, G_MAXUINT8, 2,
-                           G_PARAM_READWRITE |
-                           G_PARAM_STATIC_STRINGS));
-
-    g_object_class_install_property
-        (gobject_class, PROP_VOLUME,
-         g_param_spec_pointer("volume",
-                              "Playback volume",
-                              "Playback volume",
-                              G_PARAM_READWRITE |
-                              G_PARAM_STATIC_STRINGS));
-
-    g_object_class_install_property
-        (gobject_class, PROP_MUTE,
-         g_param_spec_boolean("mute",
-                              "Mute",
-                              "Mute",
-                              FALSE,
-                              G_PARAM_READWRITE |
-                              G_PARAM_STATIC_STRINGS));
-    g_object_class_install_property
-        (gobject_class, PROP_MIN_LATENCY,
-         g_param_spec_uint("min-latency",
-                           "Playback min buffer size (ms)",
-                           "Playback min buffer size (ms)",
-                           0, G_MAXUINT32, SPICE_PLAYBACK_DEFAULT_LATENCY_MS,
-                           G_PARAM_READWRITE |
-                           G_PARAM_STATIC_STRINGS));
-    /**
-     * SpicePlaybackChannel::playback-start:
-     * @channel: the #SpicePlaybackChannel that emitted the signal
-     * @format: a #SPICE_AUDIO_FMT
-     * @channels: number of channels
-     * @rate: audio rate
-     * @latency: minimum playback latency in ms
-     *
-     * Notify when the playback should start, and provide audio format
-     * characteristics.
-     **/
-    signals[SPICE_PLAYBACK_START] =
-        g_signal_new("playback-start",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_FIRST,
-                     G_STRUCT_OFFSET(SpicePlaybackChannelClass, playback_start),
-                     NULL, NULL,
-                     g_cclosure_user_marshal_VOID__INT_INT_INT,
-                     G_TYPE_NONE,
-                     3,
-                     G_TYPE_INT, G_TYPE_INT, G_TYPE_INT);
-
-    /**
-     * SpicePlaybackChannel::playback-data:
-     * @channel: the #SpicePlaybackChannel that emitted the signal
-     * @data: pointer to audio data
-     * @data_size: size in byte of @data
-     *
-     * Provide audio data to be played.
-     **/
-    signals[SPICE_PLAYBACK_DATA] =
-        g_signal_new("playback-data",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_FIRST,
-                     G_STRUCT_OFFSET(SpicePlaybackChannelClass, playback_data),
-                     NULL, NULL,
-                     g_cclosure_user_marshal_VOID__POINTER_INT,
-                     G_TYPE_NONE,
-                     2,
-                     G_TYPE_POINTER, G_TYPE_INT);
-
-    /**
-     * SpicePlaybackChannel::playback-stop:
-     * @channel: the #SpicePlaybackChannel that emitted the signal
-     *
-     * Notify when the playback should stop.
-     **/
-    signals[SPICE_PLAYBACK_STOP] =
-        g_signal_new("playback-stop",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_FIRST,
-                     G_STRUCT_OFFSET(SpicePlaybackChannelClass, playback_stop),
-                     NULL, NULL,
-                     g_cclosure_marshal_VOID__VOID,
-                     G_TYPE_NONE,
-                     0);
-
-    /**
-     * SpicePlaybackChannel::playback-get-delay:
-     * @channel: the #SpicePlaybackChannel that emitted the signal
-     *
-     * Notify when the current playback delay is requested
-     **/
-    signals[SPICE_PLAYBACK_GET_DELAY] =
-        g_signal_new("playback-get-delay",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_FIRST,
-                     0,
-                     NULL, NULL,
-                     g_cclosure_marshal_VOID__VOID,
-                     G_TYPE_NONE,
-                     0);
-
-    g_type_class_add_private(klass, sizeof(SpicePlaybackChannelPrivate));
-    channel_set_handlers(SPICE_CHANNEL_CLASS(klass));
-}
-
-/* ------------------------------------------------------------------ */
-
-/* coroutine context */
-static void playback_handle_data(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpicePlaybackChannelPrivate *c = SPICE_PLAYBACK_CHANNEL(channel)->priv;
-    SpiceMsgPlaybackPacket *packet = spice_msg_in_parsed(in);
-
-#ifdef DEBUG
-    CHANNEL_DEBUG(channel, "%s: time %d data %p size %d", __FUNCTION__,
-                  packet->time, packet->data, packet->data_size);
-#endif
-
-    if (c->last_time > packet->time)
-        g_warn_if_reached();
-
-    c->last_time = packet->time;
-
-    uint8_t *data = packet->data;
-    int n = packet->data_size;
-    uint8_t pcm[SND_CODEC_MAX_FRAME_SIZE * 2 * 2];
-
-    if (c->mode != SPICE_AUDIO_DATA_MODE_RAW) {
-        n = sizeof(pcm);
-        data = pcm;
-
-        if (snd_codec_decode(c->codec, packet->data, packet->data_size,
-                    pcm, &n) != SND_CODEC_OK) {
-            g_warning("snd_codec_decode() error");
-            return;
-        }
-    }
-
-    g_coroutine_signal_emit(channel, signals[SPICE_PLAYBACK_DATA], 0, data, n);
-
-    if ((c->frame_count++ % 100) == 0) {
-        g_coroutine_signal_emit(channel, signals[SPICE_PLAYBACK_GET_DELAY], 0);
-    }
-}
-
-/* coroutine context */
-static void playback_handle_mode(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpicePlaybackChannelPrivate *c = SPICE_PLAYBACK_CHANNEL(channel)->priv;
-    SpiceMsgPlaybackMode *mode = spice_msg_in_parsed(in);
-
-    CHANNEL_DEBUG(channel, "%s: time %d mode %d data %p size %d", __FUNCTION__,
-                  mode->time, mode->mode, mode->data, mode->data_size);
-
-    c->mode = mode->mode;
-    switch (c->mode) {
-    case SPICE_AUDIO_DATA_MODE_RAW:
-    case SPICE_AUDIO_DATA_MODE_CELT_0_5_1:
-    case SPICE_AUDIO_DATA_MODE_OPUS:
-        break;
-    default:
-        g_warning("%s: unhandled mode", __FUNCTION__);
-        break;
-    }
-}
-
-/* coroutine context */
-static void playback_handle_start(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpicePlaybackChannelPrivate *c = SPICE_PLAYBACK_CHANNEL(channel)->priv;
-    SpiceMsgPlaybackStart *start = spice_msg_in_parsed(in);
-
-    CHANNEL_DEBUG(channel, "%s: fmt %d channels %d freq %d time %d", __FUNCTION__,
-                  start->format, start->channels, start->frequency, start->time);
-
-    c->frame_count = 0;
-    c->last_time = start->time;
-    c->is_active = TRUE;
-    c->min_latency = SPICE_PLAYBACK_DEFAULT_LATENCY_MS;
-    snd_codec_destroy(&c->codec);
-
-    if (c->mode != SPICE_AUDIO_DATA_MODE_RAW) {
-        if (snd_codec_create(&c->codec, c->mode, start->frequency, SND_CODEC_DECODE) != SND_CODEC_OK) {
-            g_warning("create decoder failed");
-            return;
-        }
-    }
-    g_coroutine_signal_emit(channel, signals[SPICE_PLAYBACK_START], 0,
-                            start->format, start->channels, start->frequency);
-}
-
-/* coroutine context */
-static void playback_handle_stop(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpicePlaybackChannelPrivate *c = SPICE_PLAYBACK_CHANNEL(channel)->priv;
-
-    g_coroutine_signal_emit(channel, signals[SPICE_PLAYBACK_STOP], 0);
-    c->is_active = FALSE;
-}
-
-/* coroutine context */
-static void playback_handle_set_volume(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpicePlaybackChannelPrivate *c = SPICE_PLAYBACK_CHANNEL(channel)->priv;
-    SpiceMsgAudioVolume *vol = spice_msg_in_parsed(in);
-
-    if (vol->nchannels == 0) {
-        g_warning("spice-server send audio-volume-msg with 0 channels");
-        return;
-    }
-
-    g_free(c->volume);
-    c->nchannels = vol->nchannels;
-    c->volume = g_new(guint16, c->nchannels);
-    memcpy(c->volume, vol->volume, sizeof(guint16) * c->nchannels);
-    g_coroutine_object_notify(G_OBJECT(channel), "volume");
-}
-
-/* coroutine context */
-static void playback_handle_set_mute(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpicePlaybackChannelPrivate *c = SPICE_PLAYBACK_CHANNEL(channel)->priv;
-    SpiceMsgAudioMute *m = spice_msg_in_parsed(in);
-
-    c->mute = m->mute;
-    g_coroutine_object_notify(G_OBJECT(channel), "mute");
-}
-
-/* coroutine context */
-static void playback_handle_set_latency(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpicePlaybackChannelPrivate *c = SPICE_PLAYBACK_CHANNEL(channel)->priv;
-    SpiceMsgPlaybackLatency *msg = spice_msg_in_parsed(in);
-
-    c->min_latency = msg->latency_ms;
-    SPICE_DEBUG("%s: notify latency update %u", __FUNCTION__, c->min_latency);
-    g_coroutine_object_notify(G_OBJECT(channel), "min-latency");
-}
-
-static void channel_set_handlers(SpiceChannelClass *klass)
-{
-    static const spice_msg_handler handlers[] = {
-        [ SPICE_MSG_PLAYBACK_DATA ]            = playback_handle_data,
-        [ SPICE_MSG_PLAYBACK_MODE ]            = playback_handle_mode,
-        [ SPICE_MSG_PLAYBACK_START ]           = playback_handle_start,
-        [ SPICE_MSG_PLAYBACK_STOP ]            = playback_handle_stop,
-        [ SPICE_MSG_PLAYBACK_VOLUME ]          = playback_handle_set_volume,
-        [ SPICE_MSG_PLAYBACK_MUTE ]            = playback_handle_set_mute,
-        [ SPICE_MSG_PLAYBACK_LATENCY ]         = playback_handle_set_latency,
-    };
-
-    spice_channel_set_handlers(klass, handlers, G_N_ELEMENTS(handlers));
-}
-
-void spice_playback_channel_set_delay(SpicePlaybackChannel *channel, guint32 delay_ms)
-{
-    SpicePlaybackChannelPrivate *c;
-    SpiceSession *session;
-
-    g_return_if_fail(SPICE_IS_PLAYBACK_CHANNEL(channel));
-
-    CHANNEL_DEBUG(channel, "playback set_delay %u ms", delay_ms);
-
-    c = channel->priv;
-    c->latency = delay_ms;
-
-    session = spice_channel_get_session(SPICE_CHANNEL(channel));
-    if (session) {
-        spice_session_set_mm_time(session, c->last_time - delay_ms);
-    } else {
-        CHANNEL_DEBUG(channel, "channel detached from session, mm time skipped");
-    }
-}
-
-G_GNUC_INTERNAL
-gboolean spice_playback_channel_is_active(SpicePlaybackChannel *channel)
-{
-    g_return_val_if_fail(SPICE_IS_PLAYBACK_CHANNEL(channel), FALSE);
-    return channel->priv->is_active;
-}
-
-G_GNUC_INTERNAL
-guint32 spice_playback_channel_get_latency(SpicePlaybackChannel *channel)
-{
-    g_return_val_if_fail(SPICE_IS_PLAYBACK_CHANNEL(channel), 0);
-    if (!channel->priv->is_active) {
-        return 0;
-    }
-    return channel->priv->latency;
-}
-
-G_GNUC_INTERNAL
-void spice_playback_channel_sync_latency(SpicePlaybackChannel *channel)
-{
-    g_return_if_fail(SPICE_IS_PLAYBACK_CHANNEL(channel));
-    g_return_if_fail(channel->priv->is_active);
-    SPICE_DEBUG("%s: notify latency update %u", __FUNCTION__, channel->priv->min_latency);
-    g_coroutine_object_notify(G_OBJECT(SPICE_CHANNEL(channel)), "min-latency");
-}
diff --git a/gtk/channel-playback.h b/gtk/channel-playback.h
deleted file mode 100644
index 9cf68cf..0000000
--- a/gtk/channel-playback.h
+++ /dev/null
@@ -1,76 +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_CLIENT_PLAYBACK_CHANNEL_H__
-#define __SPICE_CLIENT_PLAYBACK_CHANNEL_H__
-
-#include "spice-client.h"
-
-G_BEGIN_DECLS
-
-#define SPICE_TYPE_PLAYBACK_CHANNEL            (spice_playback_channel_get_type())
-#define SPICE_PLAYBACK_CHANNEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), SPICE_TYPE_PLAYBACK_CHANNEL, SpicePlaybackChannel))
-#define SPICE_PLAYBACK_CHANNEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), SPICE_TYPE_PLAYBACK_CHANNEL, SpicePlaybackChannelClass))
-#define SPICE_IS_PLAYBACK_CHANNEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), SPICE_TYPE_PLAYBACK_CHANNEL))
-#define SPICE_IS_PLAYBACK_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SPICE_TYPE_PLAYBACK_CHANNEL))
-#define SPICE_PLAYBACK_CHANNEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), SPICE_TYPE_PLAYBACK_CHANNEL, SpicePlaybackChannelClass))
-
-typedef struct _SpicePlaybackChannel SpicePlaybackChannel;
-typedef struct _SpicePlaybackChannelClass SpicePlaybackChannelClass;
-typedef struct _SpicePlaybackChannelPrivate SpicePlaybackChannelPrivate;
-
-/**
- * SpicePlaybackChannel:
- *
- * The #SpicePlaybackChannel struct is opaque and should not be accessed directly.
- */
-struct _SpicePlaybackChannel {
-    SpiceChannel parent;
-
-    /*< private >*/
-    SpicePlaybackChannelPrivate *priv;
-    /* Do not add fields to this struct */
-};
-
-/**
- * SpicePlaybackChannelClass:
- * @parent_class: Parent class.
- * @playback_start: Signal class handler for the #SpicePlaybackChannel::playback-start signal.
- * @playback_data: Signal class handler for the #SpicePlaybackChannel::playback-data signal.
- * @playback_stop: Signal class handler for the #SpicePlaybackChannel::playback-stop signal.
- *
- * Class structure for #SpicePlaybackChannel.
- */
-struct _SpicePlaybackChannelClass {
-    SpiceChannelClass parent_class;
-
-    /* signals */
-    void (*playback_start)(SpicePlaybackChannel *channel,
-                           gint format, gint channels, gint freq);
-    void (*playback_data)(SpicePlaybackChannel *channel, gpointer *data, gint size);
-    void (*playback_stop)(SpicePlaybackChannel *channel);
-
-    /*< private >*/
-    /* Do not add fields to this struct */
-};
-
-GType           spice_playback_channel_get_type(void);
-void            spice_playback_channel_set_delay(SpicePlaybackChannel *channel, guint32 delay_ms);
-
-G_END_DECLS
-
-#endif /* __SPICE_CLIENT_PLAYBACK_CHANNEL_H__ */
diff --git a/gtk/channel-port.c b/gtk/channel-port.c
deleted file mode 100644
index f0b6d1e..0000000
--- a/gtk/channel-port.c
+++ /dev/null
@@ -1,361 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2012 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-channel-priv.h"
-#include "spice-marshal.h"
-#include "glib-compat.h"
-
-/**
- * SECTION:channel-port
- * @short_description: private communication channel
- * @title: Port Channel
- * @section_id:
- * @see_also: #SpiceChannel
- * @stability: Stable
- * @include: channel-port.h
- *
- * A Spice port channel carry arbitrary data between the Spice client
- * and the Spice server. It may be used to provide additional
- * services on top of a Spice connection. For example, a channel can
- * be associated with the qemu monitor for the client to interact
- * with it, just like any qemu chardev. Or it may be used with
- * various protocols, such as the Spice Controller.
- *
- * A port kind is identified simply by a fqdn, such as
- * org.qemu.monitor, org.spice.spicy.test or org.ovirt.controller...
- *
- * Once connected and initialized, the client may read the name of the
- * port via SpicePortChannel:port-name.
-
- * When the other end of the port is ready,
- * SpicePortChannel:port-opened is set to %TRUE and you can start
- * receiving data via the signal SpicePortChannel::port-data, or
- * sending data via spice_port_write_async().
- *
- * Since: 0.15
- */
-
-#define SPICE_PORT_CHANNEL_GET_PRIVATE(obj)                                  \
-    (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_PORT_CHANNEL, SpicePortChannelPrivate))
-
-struct _SpicePortChannelPrivate {
-    gchar *name;
-    gboolean opened;
-};
-
-G_DEFINE_TYPE(SpicePortChannel, spice_port_channel, SPICE_TYPE_CHANNEL)
-
-/* Properties */
-enum {
-    PROP_0,
-    PROP_PORT_NAME,
-    PROP_PORT_OPENED,
-};
-
-/* Signals */
-enum {
-    SPICE_PORT_DATA,
-    SPICE_PORT_EVENT,
-    LAST_SIGNAL,
-};
-
-static guint signals[LAST_SIGNAL];
-static void channel_set_handlers(SpiceChannelClass *klass);
-
-static void spice_port_channel_init(SpicePortChannel *channel)
-{
-    channel->priv = SPICE_PORT_CHANNEL_GET_PRIVATE(channel);
-}
-
-static void spice_port_get_property(GObject    *object,
-                                      guint       prop_id,
-                                      GValue     *value,
-                                      GParamSpec *pspec)
-{
-    SpicePortChannelPrivate *c = SPICE_PORT_CHANNEL(object)->priv;
-
-    switch (prop_id) {
-    case PROP_PORT_NAME:
-        g_value_set_string(value, c->name);
-        break;
-    case PROP_PORT_OPENED:
-        g_value_set_boolean(value, c->opened);
-        break;
-    default:
-        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
-        break;
-    }
-}
-
-static void spice_port_channel_finalize(GObject *object)
-{
-    SpicePortChannelPrivate *c = SPICE_PORT_CHANNEL(object)->priv;
-
-    g_free(c->name);
-
-    if (G_OBJECT_CLASS(spice_port_channel_parent_class)->finalize)
-        G_OBJECT_CLASS(spice_port_channel_parent_class)->finalize(object);
-}
-
-static void spice_port_channel_reset(SpiceChannel *channel, gboolean migrating)
-{
-    SpicePortChannelPrivate *c = SPICE_PORT_CHANNEL(channel)->priv;
-
-    g_clear_pointer(&c->name, g_free);
-    c->opened = FALSE;
-
-    SPICE_CHANNEL_CLASS(spice_port_channel_parent_class)->channel_reset(channel, migrating);
-}
-
-static void spice_port_channel_class_init(SpicePortChannelClass *klass)
-{
-    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
-    SpiceChannelClass *channel_class = SPICE_CHANNEL_CLASS(klass);
-
-    gobject_class->finalize     = spice_port_channel_finalize;
-    gobject_class->get_property = spice_port_get_property;
-    channel_class->channel_reset = spice_port_channel_reset;
-
-    g_object_class_install_property
-        (gobject_class, PROP_PORT_NAME,
-         g_param_spec_string("port-name",
-                             "Port name",
-                             "Port name",
-                             NULL,
-                             G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
-
-    g_object_class_install_property
-        (gobject_class, PROP_PORT_OPENED,
-         g_param_spec_boolean("port-opened",
-                              "Port opened",
-                              "Port opened",
-                              FALSE,
-                              G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpicePort::port-data:
-     * @channel: the channel that emitted the signal
-     * @data: the data received
-     * @size: number of bytes read
-     *
-     * The #SpicePortChannel::port-data signal is emitted when new
-     * port data is received.
-     * Since: 0.15
-     **/
-    signals[SPICE_PORT_DATA] =
-        g_signal_new("port-data",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_LAST,
-                     0,
-                     NULL, NULL,
-                     g_cclosure_user_marshal_VOID__POINTER_INT,
-                     G_TYPE_NONE,
-                     2,
-                     G_TYPE_POINTER, G_TYPE_INT);
-
-
-    /**
-     * SpicePort::port-event:
-     * @channel: the channel that emitted the signal
-     * @event: the event received
-     * @size: number of bytes read
-     *
-     * The #SpicePortChannel::port-event signal is emitted when new
-     * port event is received.
-     * Since: 0.15
-     **/
-    signals[SPICE_PORT_EVENT] =
-        g_signal_new("port-event",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_LAST,
-                     0,
-                     NULL, NULL,
-                     g_cclosure_marshal_VOID__INT,
-                     G_TYPE_NONE,
-                     1,
-                     G_TYPE_INT);
-
-    g_type_class_add_private(klass, sizeof(SpicePortChannelPrivate));
-    channel_set_handlers(SPICE_CHANNEL_CLASS(klass));
-}
-
-
-/* coroutine context */
-static void port_set_opened(SpicePortChannel *self, gboolean opened)
-{
-    SpicePortChannelPrivate *c = self->priv;
-
-    if (c->opened == opened)
-        return;
-
-    c->opened = opened;
-    g_coroutine_object_notify(G_OBJECT(self), "port-opened");
-}
-
-/* coroutine context */
-static void port_handle_init(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpicePortChannel *self = SPICE_PORT_CHANNEL(channel);
-    SpicePortChannelPrivate *c = self->priv;
-    SpiceMsgPortInit *init = spice_msg_in_parsed(in);
-
-    CHANNEL_DEBUG(channel, "init: %s %d", init->name, init->opened);
-    g_return_if_fail(init->name != NULL && *init->name != '\0');
-    g_return_if_fail(c->name == NULL);
-
-    c->name = g_strdup((gchar*)init->name);
-
-    port_set_opened(self, init->opened);
-    if (init->opened)
-        g_coroutine_signal_emit(channel, signals[SPICE_PORT_EVENT], 0, SPICE_PORT_EVENT_OPENED);
-
-    g_coroutine_object_notify(G_OBJECT(channel), "port-name");
-}
-
-/* coroutine context */
-static void port_handle_event(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpicePortChannel *self = SPICE_PORT_CHANNEL(channel);
-    SpiceMsgPortEvent *event = spice_msg_in_parsed(in);
-
-    CHANNEL_DEBUG(channel, "port event: %d", event->event);
-    switch (event->event) {
-    case SPICE_PORT_EVENT_OPENED:
-        port_set_opened(self, true);
-        break;
-    case SPICE_PORT_EVENT_CLOSED:
-        port_set_opened(self, false);
-        break;
-    }
-
-    g_coroutine_signal_emit(channel, signals[SPICE_PORT_EVENT], 0, event->event);
-}
-
-/* coroutine context */
-static void port_handle_msg(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpicePortChannel *self = SPICE_PORT_CHANNEL(channel);
-    int size;
-    uint8_t *buf;
-
-    buf = spice_msg_in_raw(in, &size);
-    CHANNEL_DEBUG(channel, "port %p got %d %p", channel, size, buf);
-    port_set_opened(self, true);
-    g_coroutine_signal_emit(channel, signals[SPICE_PORT_DATA], 0, buf, size);
-}
-
-/**
- * spice_port_write_async:
- * @port: A #SpicePortChannel
- * @buffer: (array length=count) (element-type guint8): the buffer
- * containing the data to write
- * @count: the number of bytes to write
- * @cancellable: (allow-none): optional GCancellable object, NULL to ignore
- * @callback: (scope async): callback to call when the request is satisfied
- * @user_data: (closure): the data to pass to callback function
- *
- * Request an asynchronous write of count bytes from @buffer into the
- * @port. When the operation is finished @callback will be called. You
- * can then call spice_port_write_finish() to get the result of
- * the operation.
- *
- * Since: 0.15
- **/
-void spice_port_write_async(SpicePortChannel *self,
-                            const void *buffer, gsize count,
-                            GCancellable *cancellable,
-                            GAsyncReadyCallback callback,
-                            gpointer user_data)
-{
-    SpicePortChannelPrivate *c;
-
-    g_return_if_fail(SPICE_IS_PORT_CHANNEL(self));
-    g_return_if_fail(buffer != NULL);
-    c = self->priv;
-
-    if (!c->opened) {
-        g_simple_async_report_error_in_idle(G_OBJECT(self), callback, user_data,
-            SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
-            "The port is not opened");
-        return;
-    }
-
-    spice_vmc_write_async(SPICE_CHANNEL(self), buffer, count,
-                          cancellable, callback, user_data);
-}
-
-/**
- * spice_port_write_finish:
- * @port: a #SpicePortChannel
- * @result: a #GAsyncResult
- * @error: a #GError location to store the error occurring, or %NULL
- * to ignore
- *
- * Finishes a port write operation.
- *
- * Returns: a #gssize containing the number of bytes written to the stream.
- * Since: 0.15
- **/
-gssize spice_port_write_finish(SpicePortChannel *self,
-                               GAsyncResult *result, GError **error)
-{
-    g_return_val_if_fail(SPICE_IS_PORT_CHANNEL(self), -1);
-
-    return spice_vmc_write_finish(SPICE_CHANNEL(self), result, error);
-}
-
-/**
- * spice_port_event:
- * @port: a #SpicePortChannel
- * @event: a SPICE_PORT_EVENT value
- *
- * Send an event to the port.
- *
- * Note: The values SPICE_PORT_EVENT_CLOSED and
- * SPICE_PORT_EVENT_OPENED are managed by the channel connection
- * state.
- *
- * Since: 0.15
- **/
-void spice_port_event(SpicePortChannel *self, guint8 event)
-{
-    SpiceMsgcPortEvent e;
-    SpiceMsgOut *msg;
-
-    g_return_if_fail(SPICE_IS_PORT_CHANNEL(self));
-    g_return_if_fail(event > SPICE_PORT_EVENT_CLOSED);
-
-    msg = spice_msg_out_new(SPICE_CHANNEL(self), SPICE_MSGC_PORT_EVENT);
-    e.event = event;
-    msg->marshallers->msgc_port_event(msg->marshaller, &e);
-    spice_msg_out_send(msg);
-}
-
-static void channel_set_handlers(SpiceChannelClass *klass)
-{
-    static const spice_msg_handler handlers[] = {
-        [ SPICE_MSG_PORT_INIT ]              = port_handle_init,
-        [ SPICE_MSG_PORT_EVENT ]             = port_handle_event,
-        [ SPICE_MSG_SPICEVMC_DATA ]          = port_handle_msg,
-    };
-
-    spice_channel_set_handlers(klass, handlers, G_N_ELEMENTS(handlers));
-}
diff --git a/gtk/channel-port.h b/gtk/channel-port.h
deleted file mode 100644
index 08c15dc..0000000
--- a/gtk/channel-port.h
+++ /dev/null
@@ -1,76 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2012 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_CLIENT_PORT_CHANNEL_H__
-#define __SPICE_CLIENT_PORT_CHANNEL_H__
-
-#include <gio/gio.h>
-#include "spice-channel.h"
-
-G_BEGIN_DECLS
-
-#define SPICE_TYPE_PORT_CHANNEL            (spice_port_channel_get_type())
-#define SPICE_PORT_CHANNEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), SPICE_TYPE_PORT_CHANNEL, SpicePortChannel))
-#define SPICE_PORT_CHANNEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), SPICE_TYPE_PORT_CHANNEL, SpicePortChannelClass))
-#define SPICE_IS_PORT_CHANNEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), SPICE_TYPE_PORT_CHANNEL))
-#define SPICE_IS_PORT_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SPICE_TYPE_PORT_CHANNEL))
-#define SPICE_PORT_CHANNEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), SPICE_TYPE_PORT_CHANNEL, SpicePortChannelClass))
-
-typedef struct _SpicePortChannel SpicePortChannel;
-typedef struct _SpicePortChannelClass SpicePortChannelClass;
-typedef struct _SpicePortChannelPrivate SpicePortChannelPrivate;
-
-/**
- * SpicePortChannel:
- *
- * The #SpicePortChannel struct is opaque and should not be accessed directly.
- */
-struct _SpicePortChannel {
-    SpiceChannel parent;
-
-    /*< private >*/
-    SpicePortChannelPrivate *priv;
-    /* Do not add fields to this struct */
-};
-
-/**
- * SpicePortChannelClass:
- * @parent_class: Parent class.
- *
- * Class structure for #SpicePortChannel.
- */
-struct _SpicePortChannelClass {
-    SpiceChannelClass parent_class;
-
-    /*< private >*/
-    /* Do not add fields to this struct */
-};
-
-GType spice_port_channel_get_type(void);
-
-void spice_port_write_async(SpicePortChannel *port,
-                            const void *buffer, gsize count,
-                            GCancellable *cancellable,
-                            GAsyncReadyCallback callback,
-                            gpointer user_data);
-gssize spice_port_write_finish(SpicePortChannel *port,
-                               GAsyncResult *result, GError **error);
-void spice_port_event(SpicePortChannel *port, guint8 event);
-
-G_END_DECLS
-
-#endif /* __SPICE_CLIENT_PORT_CHANNEL_H__ */
diff --git a/gtk/channel-record.c b/gtk/channel-record.c
deleted file mode 100644
index d07d84e..0000000
--- a/gtk/channel-record.c
+++ /dev/null
@@ -1,482 +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-channel-priv.h"
-
-#include "spice-marshal.h"
-#include "spice-session-priv.h"
-
-#include "common/snd_codec.h"
-
-/**
- * SECTION:channel-record
- * @short_description: audio stream for recording
- * @title: Record Channel
- * @section_id:
- * @see_also: #SpiceChannel, and #SpiceAudio
- * @stability: Stable
- * @include: channel-record.h
- *
- * #SpiceRecordChannel class handles an audio recording stream. The
- * audio stream should start when #SpiceRecordChannel::record-start is
- * emitted and should be stopped when #SpiceRecordChannel::record-stop
- * is received.
- *
- * The audio is sent to the guest by calling spice_record_send_data()
- * with the recorded PCM data.
- *
- * Note: You may be interested to let the #SpiceAudio class play and
- * record audio channels for your application.
- */
-
-#define SPICE_RECORD_CHANNEL_GET_PRIVATE(obj)                                  \
-    (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_RECORD_CHANNEL, SpiceRecordChannelPrivate))
-
-struct _SpiceRecordChannelPrivate {
-    int                         mode;
-    gboolean                    started;
-    SndCodec                    codec;
-    gsize                       frame_bytes;
-    guint8                      *last_frame;
-    gsize                       last_frame_current;
-    guint8                      nchannels;
-    guint16                     *volume;
-    guint8                      mute;
-};
-
-G_DEFINE_TYPE(SpiceRecordChannel, spice_record_channel, SPICE_TYPE_CHANNEL)
-
-/* Properties */
-enum {
-    PROP_0,
-    PROP_NCHANNELS,
-    PROP_VOLUME,
-    PROP_MUTE,
-};
-
-/* Signals */
-enum {
-    SPICE_RECORD_START,
-    SPICE_RECORD_STOP,
-
-    SPICE_RECORD_LAST_SIGNAL,
-};
-
-static guint signals[SPICE_RECORD_LAST_SIGNAL];
-
-static void channel_set_handlers(SpiceChannelClass *klass);
-
-/* ------------------------------------------------------------------ */
-
-static void spice_record_channel_reset_capabilities(SpiceChannel *channel)
-{
-    if (!g_getenv("SPICE_DISABLE_CELT"))
-        if (snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_CELT_0_5_1, SND_CODEC_ANY_FREQUENCY))
-            spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_RECORD_CAP_CELT_0_5_1);
-    if (!g_getenv("SPICE_DISABLE_OPUS"))
-        if (snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_OPUS, SND_CODEC_ANY_FREQUENCY))
-            spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_RECORD_CAP_OPUS);
-    spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_RECORD_CAP_VOLUME);
-}
-
-static void spice_record_channel_init(SpiceRecordChannel *channel)
-{
-    channel->priv = SPICE_RECORD_CHANNEL_GET_PRIVATE(channel);
-
-    spice_record_channel_reset_capabilities(SPICE_CHANNEL(channel));
-}
-
-static void spice_record_channel_finalize(GObject *obj)
-{
-    SpiceRecordChannelPrivate *c = SPICE_RECORD_CHANNEL(obj)->priv;
-
-    g_free(c->last_frame);
-    c->last_frame = NULL;
-
-    snd_codec_destroy(&c->codec);
-
-    g_free(c->volume);
-    c->volume = NULL;
-
-    if (G_OBJECT_CLASS(spice_record_channel_parent_class)->finalize)
-        G_OBJECT_CLASS(spice_record_channel_parent_class)->finalize(obj);
-}
-
-static void spice_record_channel_get_property(GObject    *gobject,
-                                              guint       prop_id,
-                                              GValue     *value,
-                                              GParamSpec *pspec)
-{
-    SpiceRecordChannel *channel = SPICE_RECORD_CHANNEL(gobject);
-    SpiceRecordChannelPrivate *c = channel->priv;
-
-    switch (prop_id) {
-    case PROP_VOLUME:
-        g_value_set_pointer(value, c->volume);
-        break;
-    case PROP_NCHANNELS:
-        g_value_set_uint(value, c->nchannels);
-        break;
-    case PROP_MUTE:
-        g_value_set_boolean(value, c->mute);
-        break;
-    default:
-        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
-        break;
-    }
-}
-
-static void spice_record_channel_set_property(GObject      *gobject,
-                                              guint         prop_id,
-                                              const GValue *value,
-                                              GParamSpec   *pspec)
-{
-    switch (prop_id) {
-    case PROP_VOLUME:
-        /* TODO: request guest volume change */
-        break;
-    case PROP_MUTE:
-        /* TODO: request guest mute change */
-        break;
-    default:
-        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
-        break;
-    }
-}
-
-static void channel_reset(SpiceChannel *channel, gboolean migrating)
-{
-    SpiceRecordChannelPrivate *c = SPICE_RECORD_CHANNEL(channel)->priv;
-
-    g_free(c->last_frame);
-    c->last_frame = NULL;
-
-    g_coroutine_signal_emit(channel, signals[SPICE_RECORD_STOP], 0);
-    c->started = FALSE;
-
-    snd_codec_destroy(&c->codec);
-
-    SPICE_CHANNEL_CLASS(spice_record_channel_parent_class)->channel_reset(channel, migrating);
-}
-
-static void spice_record_channel_class_init(SpiceRecordChannelClass *klass)
-{
-    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
-    SpiceChannelClass *channel_class = SPICE_CHANNEL_CLASS(klass);
-
-    gobject_class->finalize     = spice_record_channel_finalize;
-    gobject_class->get_property = spice_record_channel_get_property;
-    gobject_class->set_property = spice_record_channel_set_property;
-    channel_class->channel_reset = channel_reset;
-    channel_class->channel_reset_capabilities = spice_record_channel_reset_capabilities;
-
-    g_object_class_install_property
-        (gobject_class, PROP_NCHANNELS,
-         g_param_spec_uint("nchannels",
-                           "Number of Channels",
-                           "Number of Channels",
-                           0, G_MAXUINT8, 2,
-                           G_PARAM_READWRITE |
-                           G_PARAM_STATIC_STRINGS));
-
-    g_object_class_install_property
-        (gobject_class, PROP_VOLUME,
-         g_param_spec_pointer("volume",
-                              "Playback volume",
-                              "",
-                              G_PARAM_READWRITE |
-                              G_PARAM_STATIC_STRINGS));
-
-    g_object_class_install_property
-        (gobject_class, PROP_MUTE,
-         g_param_spec_boolean("mute",
-                              "Mute",
-                              "Mute",
-                              FALSE,
-                              G_PARAM_READWRITE |
-                              G_PARAM_STATIC_STRINGS));
-    /**
-     * SpiceRecordChannel::record-start:
-     * @channel: the #SpiceRecordChannel that emitted the signal
-     * @format: a #SPICE_AUDIO_FMT
-     * @channels: number of channels
-     * @rate: audio rate
-     *
-     * Notify when the recording should start, and provide audio format
-     * characteristics.
-     **/
-    signals[SPICE_RECORD_START] =
-        g_signal_new("record-start",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_FIRST,
-                     G_STRUCT_OFFSET(SpiceRecordChannelClass, record_start),
-                     NULL, NULL,
-                     g_cclosure_user_marshal_VOID__INT_INT_INT,
-                     G_TYPE_NONE,
-                     3,
-                     G_TYPE_INT, G_TYPE_INT, G_TYPE_INT);
-
-    /**
-     * SpiceRecordChannel::record-stop:
-     * @channel: the #SpiceRecordChannel that emitted the signal
-     *
-     * Notify when the recording should stop.
-     **/
-    signals[SPICE_RECORD_STOP] =
-        g_signal_new("record-stop",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_FIRST,
-                     G_STRUCT_OFFSET(SpiceRecordChannelClass, record_stop),
-                     NULL, NULL,
-                     g_cclosure_marshal_VOID__VOID,
-                     G_TYPE_NONE,
-                     0);
-
-    g_type_class_add_private(klass, sizeof(SpiceRecordChannelPrivate));
-    channel_set_handlers(SPICE_CHANNEL_CLASS(klass));
-}
-
-/* main context */
-static void spice_record_mode(SpiceRecordChannel *channel, uint32_t time,
-                              uint32_t mode, uint8_t *data, uint32_t data_size)
-{
-    SpiceMsgcRecordMode m = {0, };
-    SpiceMsgOut *msg;
-
-    g_return_if_fail(channel != NULL);
-    if (spice_channel_get_read_only(SPICE_CHANNEL(channel)))
-        return;
-
-    m.mode = mode;
-    m.time = time;
-    m.data = data;
-    m.data_size = data_size;
-
-    msg = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_RECORD_MODE);
-    msg->marshallers->msgc_record_mode(msg->marshaller, &m);
-    spice_msg_out_send(msg);
-}
-
-static int spice_record_desired_mode(SpiceChannel *channel, int frequency)
-{
-    if (!g_getenv("SPICE_DISABLE_OPUS") &&
-        snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_OPUS, frequency) &&
-        spice_channel_test_capability(channel, SPICE_RECORD_CAP_OPUS)) {
-        return SPICE_AUDIO_DATA_MODE_OPUS;
-    } else if (!g_getenv("SPICE_DISABLE_CELT") &&
-        snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_CELT_0_5_1, frequency) &&
-        spice_channel_test_capability(channel, SPICE_RECORD_CAP_CELT_0_5_1)) {
-        return SPICE_AUDIO_DATA_MODE_CELT_0_5_1;
-    } else {
-        return SPICE_AUDIO_DATA_MODE_RAW;
-    }
-}
-
-/* main context */
-static void spice_record_start_mark(SpiceRecordChannel *channel, uint32_t time)
-{
-    SpiceMsgcRecordStartMark m = {0, };
-    SpiceMsgOut *msg;
-
-    g_return_if_fail(channel != NULL);
-    if (spice_channel_get_read_only(SPICE_CHANNEL(channel)))
-        return;
-
-    m.time = time;
-
-    msg = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_RECORD_START_MARK);
-    msg->marshallers->msgc_record_start_mark(msg->marshaller, &m);
-    spice_msg_out_send(msg);
-}
-
-/**
- * spice_record_send_data:
- * @channel:
- * @data: PCM data
- * @bytes: size of @data
- * @time: stream timestamp
- *
- * Send recorded PCM data to the guest.
- **/
-void spice_record_send_data(SpiceRecordChannel *channel, gpointer data,
-                            gsize bytes, uint32_t time)
-{
-    SpiceRecordChannelPrivate *rc;
-    SpiceMsgcRecordPacket p = {0, };
-
-    g_return_if_fail(SPICE_IS_RECORD_CHANNEL(channel));
-    rc = channel->priv;
-    if (rc->last_frame == NULL) {
-        CHANNEL_DEBUG(channel, "recording didn't start or was reset");
-        return;
-    }
-
-    g_return_if_fail(spice_channel_get_read_only(SPICE_CHANNEL(channel)) == FALSE);
-
-    uint8_t *encode_buf = NULL;
-
-    if (!rc->started) {
-        spice_record_mode(channel, time, rc->mode, NULL, 0);
-        spice_record_start_mark(channel, time);
-        rc->started = TRUE;
-    }
-
-    if (rc->mode != SPICE_AUDIO_DATA_MODE_RAW)
-        encode_buf = g_alloca(SND_CODEC_MAX_COMPRESSED_BYTES);
-
-    p.time = time;
-
-    while (bytes > 0) {
-        gsize n;
-        int frame_size;
-        SpiceMsgOut *msg;
-        uint8_t *frame;
-
-        if (rc->last_frame_current > 0) {
-            /* complete previous frame */
-            n = MIN(bytes, rc->frame_bytes - rc->last_frame_current);
-            memcpy(rc->last_frame + rc->last_frame_current, data, n);
-            rc->last_frame_current += n;
-            if (rc->last_frame_current < rc->frame_bytes)
-                /* if the frame is still incomplete, return */
-                break;
-            frame = rc->last_frame;
-            frame_size = rc->frame_bytes;
-        } else {
-            n = MIN(bytes, rc->frame_bytes);
-            frame_size = n;
-            frame = data;
-        }
-
-        if (rc->last_frame_current == 0 &&
-            n < rc->frame_bytes) {
-            /* start a new frame */
-            memcpy(rc->last_frame, data, n);
-            rc->last_frame_current = n;
-            break;
-        }
-
-        if (rc->mode != SPICE_AUDIO_DATA_MODE_RAW) {
-            int len = SND_CODEC_MAX_COMPRESSED_BYTES;
-            if (snd_codec_encode(rc->codec, frame, frame_size, encode_buf, &len) != SND_CODEC_OK) {
-                g_warning("encode failed");
-                return;
-            }
-            frame = encode_buf;
-            frame_size = len;
-        }
-
-        msg = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_RECORD_DATA);
-        msg->marshallers->msgc_record_data(msg->marshaller, &p);
-        spice_marshaller_add(msg->marshaller, frame, frame_size);
-        spice_msg_out_send(msg);
-
-        if (rc->last_frame_current == rc->frame_bytes)
-            rc->last_frame_current = 0;
-
-        bytes -= n;
-        data = (guint8*)data + n;
-    }
-}
-
-/* ------------------------------------------------------------------ */
-
-/* coroutine context */
-static void record_handle_start(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceRecordChannelPrivate *c = SPICE_RECORD_CHANNEL(channel)->priv;
-    SpiceMsgRecordStart *start = spice_msg_in_parsed(in);
-    int frame_size = SND_CODEC_MAX_FRAME_SIZE;
-
-    c->mode = spice_record_desired_mode(channel, start->frequency);
-
-    CHANNEL_DEBUG(channel, "%s: fmt %d channels %d freq %d", __FUNCTION__,
-                  start->format, start->channels, start->frequency);
-
-    g_return_if_fail(start->format == SPICE_AUDIO_FMT_S16);
-
-    snd_codec_destroy(&c->codec);
-
-    if (c->mode != SPICE_AUDIO_DATA_MODE_RAW) {
-        if (snd_codec_create(&c->codec, c->mode, start->frequency, SND_CODEC_ENCODE) != SND_CODEC_OK) {
-            g_warning("Failed to create encoder");
-            return;
-        }
-        frame_size = snd_codec_frame_size(c->codec);
-    }
-
-    g_free(c->last_frame);
-    c->frame_bytes = frame_size * 16 * start->channels / 8;
-    c->last_frame = g_malloc0(c->frame_bytes);
-    c->last_frame_current = 0;
-
-    g_coroutine_signal_emit(channel, signals[SPICE_RECORD_START], 0,
-                            start->format, start->channels, start->frequency);
-}
-
-/* coroutine context */
-static void record_handle_stop(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceRecordChannelPrivate *rc = SPICE_RECORD_CHANNEL(channel)->priv;
-
-    g_coroutine_signal_emit(channel, signals[SPICE_RECORD_STOP], 0);
-    rc->started = FALSE;
-}
-
-/* coroutine context */
-static void record_handle_set_volume(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceRecordChannelPrivate *c = SPICE_RECORD_CHANNEL(channel)->priv;
-    SpiceMsgAudioVolume *vol = spice_msg_in_parsed(in);
-
-    if (vol->nchannels == 0) {
-        g_warning("spice-server send audio-volume-msg with 0 channels");
-        return;
-    }
-
-    g_free(c->volume);
-    c->nchannels = vol->nchannels;
-    c->volume = g_new(guint16, c->nchannels);
-    memcpy(c->volume, vol->volume, sizeof(guint16) * c->nchannels);
-    g_coroutine_object_notify(G_OBJECT(channel), "volume");
-}
-
-/* coroutine context */
-static void record_handle_set_mute(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceRecordChannelPrivate *c = SPICE_RECORD_CHANNEL(channel)->priv;
-    SpiceMsgAudioMute *m = spice_msg_in_parsed(in);
-
-    c->mute = m->mute;
-    g_coroutine_object_notify(G_OBJECT(channel), "mute");
-}
-
-static void channel_set_handlers(SpiceChannelClass *klass)
-{
-    static const spice_msg_handler handlers[] = {
-        [ SPICE_MSG_RECORD_START ]             = record_handle_start,
-        [ SPICE_MSG_RECORD_STOP ]              = record_handle_stop,
-        [ SPICE_MSG_RECORD_VOLUME ]            = record_handle_set_volume,
-        [ SPICE_MSG_RECORD_MUTE ]              = record_handle_set_mute,
-    };
-
-    spice_channel_set_handlers(klass, handlers, G_N_ELEMENTS(handlers));
-}
diff --git a/gtk/channel-record.h b/gtk/channel-record.h
deleted file mode 100644
index 20a9ad3..0000000
--- a/gtk/channel-record.h
+++ /dev/null
@@ -1,77 +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_CLIENT_RECORD_CHANNEL_H__
-#define __SPICE_CLIENT_RECORD_CHANNEL_H__
-
-#include "spice-client.h"
-
-G_BEGIN_DECLS
-
-#define SPICE_TYPE_RECORD_CHANNEL            (spice_record_channel_get_type())
-#define SPICE_RECORD_CHANNEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), SPICE_TYPE_RECORD_CHANNEL, SpiceRecordChannel))
-#define SPICE_RECORD_CHANNEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), SPICE_TYPE_RECORD_CHANNEL, SpiceRecordChannelClass))
-#define SPICE_IS_RECORD_CHANNEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), SPICE_TYPE_RECORD_CHANNEL))
-#define SPICE_IS_RECORD_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SPICE_TYPE_RECORD_CHANNEL))
-#define SPICE_RECORD_CHANNEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), SPICE_TYPE_RECORD_CHANNEL, SpiceRecordChannelClass))
-
-typedef struct _SpiceRecordChannel SpiceRecordChannel;
-typedef struct _SpiceRecordChannelClass SpiceRecordChannelClass;
-typedef struct _SpiceRecordChannelPrivate SpiceRecordChannelPrivate;
-
-/**
- * SpiceRecordChannel:
- *
- * The #SpiceRecordChannel struct is opaque and should not be accessed directly.
- */
-struct _SpiceRecordChannel {
-    SpiceChannel parent;
-
-    /*< private >*/
-    SpiceRecordChannelPrivate *priv;
-    /* Do not add fields to this struct */
-};
-
-/**
- * SpiceRecordChannelClass:
- * @parent_class: Parent class.
- * @record_start: Signal class handler for the #SpiceRecordChannel::record-start signal.
- * @record_stop: Signal class handler for the #SpiceRecordChannel::record-stop signal.
- * @record_data: Unused (deprecated).
- *
- * Class structure for #SpiceRecordChannel.
- */
-struct _SpiceRecordChannelClass {
-    SpiceChannelClass parent_class;
-
-    /* signals */
-    void (*record_start)(SpiceRecordChannel *channel,
-                         gint format, gint channels, gint freq);
-    void (*record_data)(SpiceRecordChannel *channel, gpointer *data, gint size);
-    void (*record_stop)(SpiceRecordChannel *channel);
-
-    /*< private >*/
-    /* Do not add fields to this struct */
-};
-
-GType	        spice_record_channel_get_type(void);
-void            spice_record_send_data(SpiceRecordChannel *channel, gpointer data,
-                                       gsize bytes, guint32 time);
-
-G_END_DECLS
-
-#endif /* __SPICE_CLIENT_RECORD_CHANNEL_H__ */
diff --git a/gtk/channel-smartcard.c b/gtk/channel-smartcard.c
deleted file mode 100644
index d91c9a0..0000000
--- a/gtk/channel-smartcard.c
+++ /dev/null
@@ -1,587 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 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"
-
-#ifdef USE_SMARTCARD
-#include <vreader.h>
-#endif
-
-#include "spice-client.h"
-#include "spice-common.h"
-
-#include "spice-channel-priv.h"
-#include "smartcard-manager.h"
-#include "smartcard-manager-priv.h"
-#include "spice-session-priv.h"
-
-/**
- * SECTION:channel-smartcard
- * @short_description: smartcard authentication
- * @title: Smartcard Channel
- * @section_id:
- * @see_also: #SpiceSmartcardManager, #SpiceSession
- * @stability: API Stable (channel in development)
- * @include: channel-smartcard.h
- *
- * The Spice protocol defines a set of messages to forward smartcard
- * information from the Spice client to the VM. This channel handles
- * these messages. While it's mainly focus on smartcard readers and
- * smartcards, it's also possible to use it with a software smartcard
- * (ie a set of 3 certificates from the client machine).
- * This class doesn't provide useful methods, see #SpiceSession properties
- * for a way to enable/disable this channel, and #SpiceSmartcardManager
- * if you want to detect smartcard reader hotplug/unplug, and smartcard
- * insertion/removal.
- */
-
-#define SPICE_SMARTCARD_CHANNEL_GET_PRIVATE(obj)                                  \
-    (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_SMARTCARD_CHANNEL, SpiceSmartcardChannelPrivate))
-
-struct _SpiceSmartcardChannelMessage {
-#ifdef USE_SMARTCARD
-    VSCMsgType message_type;
-#endif
-    SpiceMsgOut *message;
-};
-typedef struct _SpiceSmartcardChannelMessage SpiceSmartcardChannelMessage;
-
-
-struct _SpiceSmartcardChannelPrivate {
-    /* track readers that have been added but for which we didn't receive
-     * an ack from the spice server yet. We rely on the fact that the
-     * readers in this list are ordered by the time we sent the request to
-     * the server. When we get an ack from the server for a reader addition,
-     * we can pop the 1st entry to get the reader the ack corresponds to. */
-    GList *pending_reader_additions;
-
-    /* used to removals of readers that were not ack'ed yet by the spice
-     * server */
-    GHashTable *pending_reader_removals;
-
-    /* used to track card insertions on readers that were not ack'ed yet
-     * by the spice server */
-    GHashTable *pending_card_insertions;
-
-    /* next commands to be sent to the spice server. This is needed since
-     * we have to wait for a command answer before sending the next one
-     */
-    GQueue *message_queue;
-
-    /* message that is currently being processed by the spice server (ie last
-     * message that was sent to the server)
-     */
-    SpiceSmartcardChannelMessage *in_flight_message;
-};
-
-G_DEFINE_TYPE(SpiceSmartcardChannel, spice_smartcard_channel, SPICE_TYPE_CHANNEL)
-
-enum {
-
-    SPICE_SMARTCARD_LAST_SIGNAL,
-};
-
-static void spice_smartcard_channel_up(SpiceChannel *channel);
-static void handle_smartcard_msg(SpiceChannel *channel, SpiceMsgIn *in);
-static void smartcard_message_free(SpiceSmartcardChannelMessage *message);
-
-/* ------------------------------------------------------------------ */
-#ifdef USE_SMARTCARD
-static void reader_added_cb(SpiceSmartcardManager *manager, VReader *reader,
-                            gpointer user_data);
-static void reader_removed_cb(SpiceSmartcardManager *manager, VReader *reader,
-                              gpointer user_data);
-static void card_inserted_cb(SpiceSmartcardManager *manager, VReader *reader,
-                             gpointer user_data);
-static void card_removed_cb(SpiceSmartcardManager *manager, VReader *reader,
-                            gpointer user_data);
-#endif
-
-static void spice_smartcard_channel_init(SpiceSmartcardChannel *channel)
-{
-    SpiceSmartcardChannelPrivate *priv;
-
-    channel->priv = SPICE_SMARTCARD_CHANNEL_GET_PRIVATE(channel);
-    priv = channel->priv;
-    priv->message_queue = g_queue_new();
-
-#ifdef USE_SMARTCARD
-    priv->pending_card_insertions =
-        g_hash_table_new_full(g_direct_hash, g_direct_equal,
-                              (GDestroyNotify)vreader_free, NULL);
-    priv->pending_reader_removals =
-         g_hash_table_new_full(g_direct_hash, g_direct_equal,
-                               (GDestroyNotify)vreader_free, NULL);
-#endif
-}
-
-static void spice_smartcard_channel_constructed(GObject *object)
-{
-    SpiceSession *s = spice_channel_get_session(SPICE_CHANNEL(object));
-
-    g_return_if_fail(s != NULL);
-
-#ifdef USE_SMARTCARD
-    if (!spice_session_is_for_migration(s)) {
-        SpiceSmartcardChannel *channel = SPICE_SMARTCARD_CHANNEL(object);
-        SpiceSmartcardManager *manager = spice_smartcard_manager_get();
-
-        spice_g_signal_connect_object(G_OBJECT(manager), "reader-added",
-                                      (GCallback)reader_added_cb, channel, 0);
-        spice_g_signal_connect_object(G_OBJECT(manager), "reader-removed",
-                                      (GCallback)reader_removed_cb, channel, 0);
-        spice_g_signal_connect_object(G_OBJECT(manager), "card-inserted",
-                                      (GCallback)card_inserted_cb, channel, 0);
-        spice_g_signal_connect_object(G_OBJECT(manager), "card-removed",
-                                      (GCallback)card_removed_cb, channel, 0);
-    }
-#endif
-
-    if (G_OBJECT_CLASS(spice_smartcard_channel_parent_class)->constructed)
-        G_OBJECT_CLASS(spice_smartcard_channel_parent_class)->constructed(object);
-
-}
-
-static void spice_smartcard_channel_finalize(GObject *obj)
-{
-    SpiceSmartcardChannel *channel = SPICE_SMARTCARD_CHANNEL(obj);
-    SpiceSmartcardChannelPrivate *c = channel->priv;
-
-    if (c->pending_card_insertions != NULL) {
-        g_hash_table_destroy(c->pending_card_insertions);
-        c->pending_card_insertions = NULL;
-    }
-    if (c->pending_reader_removals != NULL) {
-        g_hash_table_destroy(c->pending_reader_removals);
-        c->pending_reader_removals = NULL;
-    }
-    if (c->message_queue != NULL) {
-        g_queue_foreach(c->message_queue, (GFunc)smartcard_message_free, NULL);
-        g_queue_free(c->message_queue);
-        c->message_queue = NULL;
-    }
-    if (c->in_flight_message != NULL) {
-        smartcard_message_free(c->in_flight_message);
-        c->in_flight_message = NULL;
-    }
-
-    g_list_free(c->pending_reader_additions);
-    c->pending_reader_additions = NULL;
-
-    if (G_OBJECT_CLASS(spice_smartcard_channel_parent_class)->finalize)
-        G_OBJECT_CLASS(spice_smartcard_channel_parent_class)->finalize(obj);
-}
-
-static void spice_smartcard_channel_reset(SpiceChannel *channel, gboolean migrating)
-{
-    SpiceSmartcardChannel *smartcard_channel = SPICE_SMARTCARD_CHANNEL(channel);
-    SpiceSmartcardChannelPrivate *c = smartcard_channel->priv;
-
-    g_hash_table_remove_all(c->pending_card_insertions);
-    g_hash_table_remove_all(c->pending_reader_removals);
-
-    if (c->message_queue != NULL) {
-        g_queue_foreach(c->message_queue, (GFunc)smartcard_message_free, NULL);
-        g_queue_clear(c->message_queue);
-    }
-
-    if (c->in_flight_message != NULL) {
-        smartcard_message_free(c->in_flight_message);
-        c->in_flight_message = NULL;
-    }
-
-    g_list_free(c->pending_reader_additions);
-    c->pending_reader_additions = NULL;
-
-    SPICE_CHANNEL_CLASS(spice_smartcard_channel_parent_class)->channel_reset(channel, migrating);
-}
-
-static void channel_set_handlers(SpiceChannelClass *klass)
-{
-    static const spice_msg_handler handlers[] = {
-        [ SPICE_MSG_SMARTCARD_DATA ] = handle_smartcard_msg,
-    };
-    spice_channel_set_handlers(klass, handlers, G_N_ELEMENTS(handlers));
-}
-
-static void spice_smartcard_channel_class_init(SpiceSmartcardChannelClass *klass)
-{
-    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
-    SpiceChannelClass *channel_class = SPICE_CHANNEL_CLASS(klass);
-
-    gobject_class->finalize     = spice_smartcard_channel_finalize;
-    gobject_class->constructed  = spice_smartcard_channel_constructed;
-
-    channel_class->channel_up   = spice_smartcard_channel_up;
-    channel_class->channel_reset = spice_smartcard_channel_reset;
-
-    g_type_class_add_private(klass, sizeof(SpiceSmartcardChannelPrivate));
-    channel_set_handlers(SPICE_CHANNEL_CLASS(klass));
-}
-
-/* ------------------------------------------------------------------ */
-/* private api                                                        */
-
-static void
-smartcard_message_free(SpiceSmartcardChannelMessage *message)
-{
-    if (message->message)
-        spice_msg_out_unref(message->message);
-    g_slice_free(SpiceSmartcardChannelMessage, message);
-}
-
-#if USE_SMARTCARD
-static gboolean is_attached_to_server(VReader *reader)
-{
-    return (vreader_get_id(reader) != (vreader_id_t)-1);
-}
-
-static gboolean
-spice_channel_has_pending_card_insertion(SpiceSmartcardChannel *channel,
-                                         VReader *reader)
-{
-    return (g_hash_table_lookup(channel->priv->pending_card_insertions, reader) != NULL);
-}
-
-static void
-spice_channel_queue_card_insertion(SpiceSmartcardChannel *channel,
-                                   VReader *reader)
-{
-    vreader_reference(reader);
-    g_hash_table_insert(channel->priv->pending_card_insertions,
-                        reader, reader);
-}
-
-static void
-spice_channel_drop_pending_card_insertion(SpiceSmartcardChannel *channel,
-                                          VReader *reader)
-{
-    g_hash_table_remove(channel->priv->pending_card_insertions, reader);
-}
-
-static gboolean
-spice_channel_has_pending_reader_removal(SpiceSmartcardChannel *channel,
-                                         VReader *reader)
-{
-    return (g_hash_table_lookup(channel->priv->pending_reader_removals, reader) != NULL);
-}
-
-static void
-spice_channel_queue_reader_removal(SpiceSmartcardChannel *channel,
-                                   VReader *reader)
-{
-    vreader_reference(reader);
-    g_hash_table_insert(channel->priv->pending_reader_removals,
-                        reader, reader);
-}
-
-static void
-spice_channel_drop_pending_reader_removal(SpiceSmartcardChannel *channel,
-                                          VReader *reader)
-{
-    g_hash_table_remove(channel->priv->pending_reader_removals, reader);
-}
-
-static SpiceSmartcardChannelMessage *
-smartcard_message_new(VSCMsgType msg_type, SpiceMsgOut *msg_out)
-{
-    SpiceSmartcardChannelMessage *message;
-
-    message = g_slice_new0(SpiceSmartcardChannelMessage);
-    message->message = msg_out;
-    message->message_type = msg_type;
-
-    return message;
-}
-
-/* Indicates that handling of the message that is currently in flight has
- * been completed. If needed, sends the next queued command to the server. */
-static void
-smartcard_message_complete_in_flight(SpiceSmartcardChannel *channel)
-{
-    g_return_if_fail(channel->priv->in_flight_message != NULL);
-
-    smartcard_message_free(channel->priv->in_flight_message);
-    channel->priv->in_flight_message = g_queue_pop_head(channel->priv->message_queue);
-    if (channel->priv->in_flight_message != NULL) {
-        spice_msg_out_send(channel->priv->in_flight_message->message);
-        channel->priv->in_flight_message->message = NULL;
-    }
-}
-
-static void smartcard_message_send(SpiceSmartcardChannel *channel,
-                                   VSCMsgType msg_type,
-                                   SpiceMsgOut *msg_out, gboolean queue)
-{
-    SpiceSmartcardChannelMessage *message;
-
-    if (spice_channel_get_read_only(SPICE_CHANNEL(channel)))
-        return;
-
-    CHANNEL_DEBUG(channel, "send message %d, %s",
-                  msg_type, queue ? "queued" : "now");
-    if (!queue) {
-        spice_msg_out_send(msg_out);
-        return;
-    }
-
-    message = smartcard_message_new(msg_type, msg_out);
-    if (channel->priv->in_flight_message == NULL) {
-        g_return_if_fail(g_queue_is_empty(channel->priv->message_queue));
-        channel->priv->in_flight_message = message;
-        spice_msg_out_send(channel->priv->in_flight_message->message);
-        channel->priv->in_flight_message->message = NULL;
-    } else {
-        g_queue_push_tail(channel->priv->message_queue, message);
-    }
-}
-
-static void
-send_msg_generic_with_data(SpiceSmartcardChannel *channel, VReader *reader,
-                           VSCMsgType msg_type,
-                           const uint8_t *data, gsize data_len,
-                           gboolean serialize_msg)
-{
-    SpiceMsgOut *msg_out;
-    VSCMsgHeader header = {
-        .type = msg_type,
-        .length = data_len
-    };
-
-    if(vreader_get_id(reader) == -1)
-        header.reader_id = VSCARD_UNDEFINED_READER_ID;
-    else
-        header.reader_id = vreader_get_id(reader);
-
-    msg_out = spice_msg_out_new(SPICE_CHANNEL(channel),
-                                SPICE_MSGC_SMARTCARD_DATA);
-    msg_out->marshallers->msgc_smartcard_header(msg_out->marshaller, &header);
-    if ((data != NULL) && (data_len != 0)) {
-        spice_marshaller_add(msg_out->marshaller, data, data_len);
-    }
-
-    smartcard_message_send(channel, msg_type, msg_out, serialize_msg);
-}
-
-static void send_msg_generic(SpiceSmartcardChannel *channel, VReader *reader,
-                             VSCMsgType msg_type)
-{
-    send_msg_generic_with_data(channel, reader, msg_type, NULL, 0, TRUE);
-}
-
-static void send_msg_atr(SpiceSmartcardChannel *channel, VReader *reader)
-{
-#define MAX_ATR_LEN 40 //this should be defined in libcacard
-    uint8_t atr[MAX_ATR_LEN];
-    int atr_len = MAX_ATR_LEN;
-
-    g_return_if_fail(vreader_get_id(reader) != VSCARD_UNDEFINED_READER_ID);
-    vreader_power_on(reader, atr, &atr_len);
-    send_msg_generic_with_data(channel, reader, VSC_ATR, atr, atr_len, TRUE);
-}
-
-static void reader_added_cb(SpiceSmartcardManager *manager, VReader *reader,
-                            gpointer user_data)
-{
-    SpiceSmartcardChannel *channel = SPICE_SMARTCARD_CHANNEL(user_data);
-    const char *reader_name = vreader_get_name(reader);
-
-    if (vreader_get_id(reader) != -1 ||
-        g_list_find(channel->priv->pending_reader_additions, reader))
-        return;
-
-    channel->priv->pending_reader_additions =
-        g_list_append(channel->priv->pending_reader_additions, reader);
-
-    send_msg_generic_with_data(channel, reader, VSC_ReaderAdd,
-                               (uint8_t*)reader_name, strlen(reader_name), TRUE);
-}
-
-static void reader_removed_cb(SpiceSmartcardManager *manager, VReader *reader,
-                              gpointer user_data)
-{
-    SpiceSmartcardChannel *channel = SPICE_SMARTCARD_CHANNEL(user_data);
-
-    if (is_attached_to_server(reader)) {
-        send_msg_generic(channel, reader, VSC_ReaderRemove);
-    } else {
-        spice_channel_queue_reader_removal(channel, reader);
-    }
-}
-
-/* ------------------------------------------------------------------ */
-/* callbacks                                                          */
-static void card_inserted_cb(SpiceSmartcardManager *manager, VReader *reader,
-                             gpointer user_data)
-{
-    SpiceSmartcardChannel *channel = SPICE_SMARTCARD_CHANNEL(user_data);
-
-    if (is_attached_to_server(reader)) {
-        send_msg_atr(channel, reader);
-    } else {
-        spice_channel_queue_card_insertion(channel, reader);
-    }
-}
-
-static void card_removed_cb(SpiceSmartcardManager *manager, VReader *reader,
-                            gpointer user_data)
-{
-    SpiceSmartcardChannel *channel = SPICE_SMARTCARD_CHANNEL(user_data);
-
-    if (is_attached_to_server(reader)) {
-        send_msg_generic(channel, reader, VSC_CardRemove);
-    } else {
-        /* this does nothing when reader has no card insertion pending */
-        spice_channel_drop_pending_card_insertion(channel, reader);
-    }
-}
-#endif /* USE_SMARTCARD */
-
-static void spice_smartcard_channel_up_cb(GObject *source_object,
-                                          GAsyncResult *res,
-                                          gpointer user_data)
-{
-    SpiceChannel *channel = SPICE_CHANNEL(user_data);
-#ifdef USE_SMARTCARD
-    SpiceSmartcardManager *manager = spice_smartcard_manager_get();
-    GList *l, *list = NULL;
-#endif
-    GError *error = NULL;
-
-    g_return_if_fail(channel != NULL);
-    g_return_if_fail(SPICE_IS_SESSION(source_object));
-
-    spice_smartcard_manager_init_finish(SPICE_SESSION(source_object),
-                                        res, &error);
-    if (error) {
-        g_warning("%s", error->message);
-        goto end;
-    }
-
-#ifdef USE_SMARTCARD
-    list = spice_smartcard_manager_get_readers(manager);
-    for (l = list; l != NULL; l = l->next) {
-        VReader *reader = l->data;
-        gboolean has_card = vreader_card_is_present(reader) == VREADER_OK;
-
-        reader_added_cb(manager, reader, channel);
-        if (has_card)
-            card_inserted_cb(manager, reader, channel);
-
-        g_boxed_free(SPICE_TYPE_SMARTCARD_READER, reader);
-    }
-#endif
-
-end:
-#ifdef USE_SMARTCARD
-    g_list_free(list);
-#endif
-    g_clear_error(&error);
-}
-
-static void spice_smartcard_channel_up(SpiceChannel *channel)
-{
-    if (spice_session_is_for_migration(spice_channel_get_session(channel)))
-        return;
-
-    spice_smartcard_manager_init_async(spice_channel_get_session(channel),
-                                       g_cancellable_new(),
-                                       spice_smartcard_channel_up_cb,
-                                       channel);
-}
-
-static void handle_smartcard_msg(SpiceChannel *channel, SpiceMsgIn *in)
-{
-#ifdef USE_SMARTCARD
-    SpiceSmartcardChannel *smartcard_channel = SPICE_SMARTCARD_CHANNEL(channel);
-    SpiceSmartcardChannelPrivate *priv = smartcard_channel->priv;
-    SpiceMsgSmartcard *msg = spice_msg_in_parsed(in);
-    VReader *reader;
-
-    CHANNEL_DEBUG(channel, "handle msg %d", msg->type);
-    switch (msg->type) {
-        case VSC_Error:
-            g_return_if_fail(priv->in_flight_message != NULL);
-            CHANNEL_DEBUG(channel, "in flight %d", priv->in_flight_message->message_type);
-            switch (priv->in_flight_message->message_type) {
-                case VSC_ReaderAdd:
-                    g_return_if_fail(priv->pending_reader_additions != NULL);
-                    reader = priv->pending_reader_additions->data;
-                    g_return_if_fail(reader != NULL);
-                    g_return_if_fail(vreader_get_id(reader) == -1);
-                    priv->pending_reader_additions =
-                        g_list_delete_link(priv->pending_reader_additions,
-                                           priv->pending_reader_additions);
-                    vreader_set_id(reader, msg->reader_id);
-
-                    if (spice_channel_has_pending_card_insertion(smartcard_channel, reader)) {
-                        send_msg_atr(smartcard_channel, reader);
-                        spice_channel_drop_pending_card_insertion(smartcard_channel, reader);
-                    }
-
-                    if (spice_channel_has_pending_reader_removal(smartcard_channel, reader)) {
-                        send_msg_generic(smartcard_channel, reader, VSC_CardRemove);
-                        spice_channel_drop_pending_reader_removal(smartcard_channel, reader);
-                    }
-                    break;
-                case VSC_APDU:
-                case VSC_ATR:
-                case VSC_CardRemove:
-                case VSC_Error:
-                case VSC_ReaderRemove:
-                    break;
-                default:
-                    g_warning("Unexpected message: %d", priv->in_flight_message->message_type);
-                    break;
-            }
-            smartcard_message_complete_in_flight(smartcard_channel);
-
-            break;
-
-        case VSC_APDU:
-        case VSC_Init: {
-            const unsigned int APDU_BUFFER_SIZE = 270;
-            VReaderStatus reader_status;
-            uint8_t data_out[APDU_BUFFER_SIZE + sizeof(uint32_t)];
-            int data_out_len = sizeof(data_out);
-
-            g_return_if_fail(msg->reader_id != VSCARD_UNDEFINED_READER_ID);
-            reader = vreader_get_reader_by_id(msg->reader_id);
-            g_return_if_fail(reader != NULL); //FIXME: add log message
-
-            reader_status = vreader_xfr_bytes(reader,
-                                              msg->data, msg->length,
-                                              data_out, &data_out_len);
-            if (reader_status == VREADER_OK) {
-                send_msg_generic_with_data(smartcard_channel,
-                                           reader, VSC_APDU,
-                                           data_out, data_out_len, FALSE);
-            } else {
-                uint32_t error_code;
-                error_code = GUINT32_TO_LE(reader_status);
-                send_msg_generic_with_data(smartcard_channel,
-                                           reader, VSC_Error,
-                                           (uint8_t*)&error_code,
-                                           sizeof (error_code), FALSE);
-            }
-            break;
-        }
-        default:
-            g_return_if_reached();
-    }
-#endif
-}
diff --git a/gtk/channel-smartcard.h b/gtk/channel-smartcard.h
deleted file mode 100644
index 28c8b88..0000000
--- a/gtk/channel-smartcard.h
+++ /dev/null
@@ -1,68 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 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/>.
-*/
-#ifndef __SPICE_CLIENT_SMARTCARD_CHANNEL_H__
-#define __SPICE_CLIENT_SMARTCARD_CHANNEL_H__
-
-#include "spice-client.h"
-
-G_BEGIN_DECLS
-
-#define SPICE_TYPE_SMARTCARD_CHANNEL            (spice_smartcard_channel_get_type())
-#define SPICE_SMARTCARD_CHANNEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), SPICE_TYPE_SMARTCARD_CHANNEL, SpiceSmartcardChannel))
-#define SPICE_SMARTCARD_CHANNEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), SPICE_TYPE_SMARTCARD_CHANNEL, SpiceSmartcardChannelClass))
-#define SPICE_IS_SMARTCARD_CHANNEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), SPICE_TYPE_SMARTCARD_CHANNEL))
-#define SPICE_IS_SMARTCARD_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SPICE_TYPE_SMARTCARD_CHANNEL))
-#define SPICE_SMARTCARD_CHANNEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), SPICE_TYPE_SMARTCARD_CHANNEL, SpiceSmartcardChannelClass))
-
-typedef struct _SpiceSmartcardChannel SpiceSmartcardChannel;
-typedef struct _SpiceSmartcardChannelClass SpiceSmartcardChannelClass;
-typedef struct _SpiceSmartcardChannelPrivate SpiceSmartcardChannelPrivate;
-
-/**
- * SpiceSmartcardChannel:
- *
- * The #SpiceSmartcardChannel struct is opaque and should not be accessed directly.
- */
-struct _SpiceSmartcardChannel {
-    SpiceChannel parent;
-
-    /*< private >*/
-    SpiceSmartcardChannelPrivate *priv;
-    /* Do not add fields to this struct */
-};
-
-/**
- * SpiceSmartcardChannelClass:
- * @parent_class: Parent class.
- *
- * Class structure for #SpiceSmartcardChannel.
- */
-struct _SpiceSmartcardChannelClass {
-    SpiceChannelClass parent_class;
-
-    /* signals */
-
-    /*< private >*/
-    /* Do not add fields to this struct */
-};
-
-GType spice_smartcard_channel_get_type(void);
-
-G_END_DECLS
-
-#endif /* __SPICE_CLIENT_SMARTCARD_CHANNEL_H__ */
diff --git a/gtk/channel-usbredir-priv.h b/gtk/channel-usbredir-priv.h
deleted file mode 100644
index 2c4c6f7..0000000
--- a/gtk/channel-usbredir-priv.h
+++ /dev/null
@@ -1,61 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2011 Red Hat, Inc.
-
-   Red Hat Authors:
-   Hans de Goede <hdegoede at redhat.com>
-
-   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_CLIENT_USBREDIR_CHANNEL_PRIV_H__
-#define __SPICE_CLIENT_USBREDIR_CHANNEL_PRIV_H__
-
-#include <libusb.h>
-#include <usbredirfilter.h>
-#include "spice-client.h"
-
-G_BEGIN_DECLS
-
-/* Note: this must be called before calling any other functions, and the
-   context should not be destroyed before the last device has been
-   disconnected */
-void spice_usbredir_channel_set_context(SpiceUsbredirChannel *channel,
-                                        libusb_context       *context);
-
-/* Note the context must be set, and the channel must be brought up
-   (through spice_channel_connect()), before calling this. */
-void spice_usbredir_channel_connect_device_async(
-                                        SpiceUsbredirChannel *channel,
-                                        libusb_device        *device,
-                                        SpiceUsbDevice       *spice_device,
-                                        GCancellable         *cancellable,
-                                        GAsyncReadyCallback   callback,
-                                        gpointer              user_data);
-gboolean spice_usbredir_channel_connect_device_finish(
-                                        SpiceUsbredirChannel *channel,
-                                        GAsyncResult         *res,
-                                        GError              **err);
-
-void spice_usbredir_channel_disconnect_device(SpiceUsbredirChannel *channel);
-
-libusb_device *spice_usbredir_channel_get_device(SpiceUsbredirChannel *channel);
-
-void spice_usbredir_channel_get_guest_filter(
-                          SpiceUsbredirChannel               *channel,
-                          const struct usbredirfilter_rule  **rules_ret,
-                          int                                *rules_count_ret);
-
-G_END_DECLS
-
-#endif /* __SPICE_CLIENT_USBREDIR_CHANNEL_PRIV_H__ */
diff --git a/gtk/channel-usbredir.c b/gtk/channel-usbredir.c
deleted file mode 100644
index d974434..0000000
--- a/gtk/channel-usbredir.c
+++ /dev/null
@@ -1,686 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2010-2012 Red Hat, Inc.
-
-   Red Hat Authors:
-   Hans de Goede <hdegoede at redhat.com>
-   Richard Hughes <rhughes at redhat.com>
-
-   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"
-
-#ifdef USE_USBREDIR
-#include <glib/gi18n.h>
-#include <usbredirhost.h>
-#if USE_POLKIT
-#include "usb-acl-helper.h"
-#endif
-#include "channel-usbredir-priv.h"
-#include "usb-device-manager-priv.h"
-#include "usbutil.h"
-#endif
-
-#include "spice-client.h"
-#include "spice-common.h"
-
-#include "spice-channel-priv.h"
-#include "glib-compat.h"
-
-/**
- * SECTION:channel-usbredir
- * @short_description: usb redirection
- * @title: USB Redirection Channel
- * @section_id:
- * @stability: API Stable (channel in development)
- * @include: channel-usbredir.h
- *
- * The Spice protocol defines a set of messages to redirect USB devices
- * from the Spice client to the VM. This channel handles these messages.
- */
-
-#ifdef USE_USBREDIR
-
-#define SPICE_USBREDIR_CHANNEL_GET_PRIVATE(obj)                                  \
-    (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_USBREDIR_CHANNEL, SpiceUsbredirChannelPrivate))
-
-enum SpiceUsbredirChannelState {
-    STATE_DISCONNECTED,
-#if USE_POLKIT
-    STATE_WAITING_FOR_ACL_HELPER,
-#endif
-    STATE_CONNECTED,
-    STATE_DISCONNECTING,
-};
-
-struct _SpiceUsbredirChannelPrivate {
-    libusb_device *device;
-    SpiceUsbDevice *spice_device;
-    libusb_context *context;
-    struct usbredirhost *host;
-    /* To catch usbredirhost error messages and report them as a GError */
-    GError **catch_error;
-    /* Data passed from channel handle msg to the usbredirhost read cb */
-    const uint8_t *read_buf;
-    int read_buf_size;
-    enum SpiceUsbredirChannelState state;
-#if USE_POLKIT
-    GSimpleAsyncResult *result;
-    SpiceUsbAclHelper *acl_helper;
-#endif
-};
-
-static void channel_set_handlers(SpiceChannelClass *klass);
-static void spice_usbredir_channel_up(SpiceChannel *channel);
-static void spice_usbredir_channel_dispose(GObject *obj);
-static void spice_usbredir_channel_finalize(GObject *obj);
-static void usbredir_handle_msg(SpiceChannel *channel, SpiceMsgIn *in);
-
-static void usbredir_log(void *user_data, int level, const char *msg);
-static int usbredir_read_callback(void *user_data, uint8_t *data, int count);
-static int usbredir_write_callback(void *user_data, uint8_t *data, int count);
-static void usbredir_write_flush_callback(void *user_data);
-
-static void *usbredir_alloc_lock(void);
-static void usbredir_lock_lock(void *user_data);
-static void usbredir_unlock_lock(void *user_data);
-static void usbredir_free_lock(void *user_data);
-
-#endif
-
-G_DEFINE_TYPE(SpiceUsbredirChannel, spice_usbredir_channel, SPICE_TYPE_CHANNEL)
-
-/* ------------------------------------------------------------------ */
-
-static void spice_usbredir_channel_init(SpiceUsbredirChannel *channel)
-{
-#ifdef USE_USBREDIR
-    channel->priv = SPICE_USBREDIR_CHANNEL_GET_PRIVATE(channel);
-#endif
-}
-
-#ifdef USE_USBREDIR
-static void spice_usbredir_channel_reset(SpiceChannel *c, gboolean migrating)
-{
-    SpiceUsbredirChannel *channel = SPICE_USBREDIR_CHANNEL(c);
-    SpiceUsbredirChannelPrivate *priv = channel->priv;
-
-    if (priv->host) {
-        if (priv->state == STATE_CONNECTED)
-            spice_usbredir_channel_disconnect_device(channel);
-        usbredirhost_close(priv->host);
-        priv->host = NULL;
-        /* Call set_context to re-create the host */
-        spice_usbredir_channel_set_context(channel, priv->context);
-    }
-    SPICE_CHANNEL_CLASS(spice_usbredir_channel_parent_class)->channel_reset(c, migrating);
-}
-#endif
-
-static void spice_usbredir_channel_class_init(SpiceUsbredirChannelClass *klass)
-{
-#ifdef USE_USBREDIR
-    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
-    SpiceChannelClass *channel_class = SPICE_CHANNEL_CLASS(klass);
-
-    gobject_class->dispose       = spice_usbredir_channel_dispose;
-    gobject_class->finalize      = spice_usbredir_channel_finalize;
-    channel_class->channel_up    = spice_usbredir_channel_up;
-    channel_class->channel_reset = spice_usbredir_channel_reset;
-
-    g_type_class_add_private(klass, sizeof(SpiceUsbredirChannelPrivate));
-    channel_set_handlers(SPICE_CHANNEL_CLASS(klass));
-#endif
-}
-
-#ifdef USE_USBREDIR
-static void spice_usbredir_channel_dispose(GObject *obj)
-{
-    SpiceUsbredirChannel *channel = SPICE_USBREDIR_CHANNEL(obj);
-
-    spice_usbredir_channel_disconnect_device(channel);
-
-    /* Chain up to the parent class */
-    if (G_OBJECT_CLASS(spice_usbredir_channel_parent_class)->dispose)
-        G_OBJECT_CLASS(spice_usbredir_channel_parent_class)->dispose(obj);
-}
-
-/*
- * Note we don't unref our device / acl_helper / result references in our
- * finalize. The reason for this is that depending on our state at dispose
- * time they are either:
- * 1) Already unreferenced
- * 2) Will be unreferenced by the disconnect_device call from dispose
- * 3) Will be unreferenced by spice_usbredir_channel_open_acl_cb
- *
- * Now the last one may seem like an issue, since what will happen if
- * spice_usbredir_channel_open_acl_cb will run after finalization?
- *
- * This will never happens since the GSimpleAsyncResult created before we
- * get into the STATE_WAITING_FOR_ACL_HELPER takes a reference to its
- * source object, which is our SpiceUsbredirChannel object, so
- * the finalize won't hapen until spice_usbredir_channel_open_acl_cb runs,
- * and unrefs priv->result which will in turn unref ourselve once the
- * complete_in_idle call it does has completed. And once
- * spice_usbredir_channel_open_acl_cb has run, all references we hold have
- * been released even in the 3th scenario.
- */
-static void spice_usbredir_channel_finalize(GObject *obj)
-{
-    SpiceUsbredirChannel *channel = SPICE_USBREDIR_CHANNEL(obj);
-
-    if (channel->priv->host)
-        usbredirhost_close(channel->priv->host);
-
-    /* Chain up to the parent class */
-    if (G_OBJECT_CLASS(spice_usbredir_channel_parent_class)->finalize)
-        G_OBJECT_CLASS(spice_usbredir_channel_parent_class)->finalize(obj);
-}
-
-static void channel_set_handlers(SpiceChannelClass *klass)
-{
-    static const spice_msg_handler handlers[] = {
-        [ SPICE_MSG_SPICEVMC_DATA ] = usbredir_handle_msg,
-    };
-
-    spice_channel_set_handlers(klass, handlers, G_N_ELEMENTS(handlers));
-}
-
-/* ------------------------------------------------------------------ */
-/* private api                                                        */
-
-G_GNUC_INTERNAL
-void spice_usbredir_channel_set_context(SpiceUsbredirChannel *channel,
-                                        libusb_context       *context)
-{
-    SpiceUsbredirChannelPrivate *priv = channel->priv;
-
-    g_return_if_fail(priv->host == NULL);
-
-    priv->context = context;
-    priv->host = usbredirhost_open_full(
-                                   context, NULL,
-                                   usbredir_log,
-                                   usbredir_read_callback,
-                                   usbredir_write_callback,
-                                   usbredir_write_flush_callback,
-                                   usbredir_alloc_lock,
-                                   usbredir_lock_lock,
-                                   usbredir_unlock_lock,
-                                   usbredir_free_lock,
-                                   channel, PACKAGE_STRING,
-                                   spice_util_get_debug() ? usbredirparser_debug : usbredirparser_warning,
-                                   usbredirhost_fl_write_cb_owns_buffer);
-    if (!priv->host)
-        g_error("Out of memory allocating usbredirhost");
-}
-
-static gboolean spice_usbredir_channel_open_device(
-    SpiceUsbredirChannel *channel, GError **err)
-{
-    SpiceUsbredirChannelPrivate *priv = channel->priv;
-    libusb_device_handle *handle = NULL;
-    int rc, status;
-
-    g_return_val_if_fail(priv->state == STATE_DISCONNECTED
-#if USE_POLKIT
-                         || priv->state == STATE_WAITING_FOR_ACL_HELPER
-#endif
-                         , FALSE);
-
-    rc = libusb_open(priv->device, &handle);
-    if (rc != 0) {
-        g_set_error(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
-                    "Could not open usb device: %s [%i]",
-                    spice_usbutil_libusb_strerror(rc), rc);
-        return FALSE;
-    }
-
-    priv->catch_error = err;
-    status = usbredirhost_set_device(priv->host, handle);
-    priv->catch_error = NULL;
-    if (status != usb_redir_success) {
-        g_return_val_if_fail(err == NULL || *err != NULL, FALSE);
-        return FALSE;
-    }
-
-    if (!spice_usb_device_manager_start_event_listening(
-            spice_usb_device_manager_get(
-                spice_channel_get_session(SPICE_CHANNEL(channel)), NULL),
-            err)) {
-        usbredirhost_set_device(priv->host, NULL);
-        return FALSE;
-    }
-
-    priv->state = STATE_CONNECTED;
-
-    return TRUE;
-}
-
-#if USE_POLKIT
-static void spice_usbredir_channel_open_acl_cb(
-    GObject *gobject, GAsyncResult *acl_res, gpointer user_data)
-{
-    SpiceUsbAclHelper *acl_helper = SPICE_USB_ACL_HELPER(gobject);
-    SpiceUsbredirChannel *channel = SPICE_USBREDIR_CHANNEL(user_data);
-    SpiceUsbredirChannelPrivate *priv = channel->priv;
-    GError *err = NULL;
-
-    g_return_if_fail(acl_helper == priv->acl_helper);
-    g_return_if_fail(priv->state == STATE_WAITING_FOR_ACL_HELPER ||
-                     priv->state == STATE_DISCONNECTING);
-
-    spice_usb_acl_helper_open_acl_finish(acl_helper, acl_res, &err);
-    if (!err && priv->state == STATE_DISCONNECTING) {
-        err = g_error_new_literal(G_IO_ERROR, G_IO_ERROR_CANCELLED,
-                                  "USB redirection channel connect cancelled");
-    }
-    if (!err) {
-        spice_usbredir_channel_open_device(channel, &err);
-    }
-    if (err) {
-        g_simple_async_result_take_error(priv->result, err);
-        libusb_unref_device(priv->device);
-        priv->device = NULL;
-        g_boxed_free(spice_usb_device_get_type(), priv->spice_device);
-        priv->spice_device = NULL;
-        priv->state  = STATE_DISCONNECTED;
-    }
-
-    spice_usb_acl_helper_close_acl(priv->acl_helper);
-    g_clear_object(&priv->acl_helper);
-    g_object_set(spice_channel_get_session(SPICE_CHANNEL(channel)),
-                 "inhibit-keyboard-grab", FALSE, NULL);
-
-    g_simple_async_result_complete_in_idle(priv->result);
-    g_clear_object(&priv->result);
-}
-#endif
-
-G_GNUC_INTERNAL
-void spice_usbredir_channel_connect_device_async(
-                                          SpiceUsbredirChannel *channel,
-                                          libusb_device        *device,
-                                          SpiceUsbDevice       *spice_device,
-                                          GCancellable         *cancellable,
-                                          GAsyncReadyCallback   callback,
-                                          gpointer              user_data)
-{
-    SpiceUsbredirChannelPrivate *priv = channel->priv;
-    GSimpleAsyncResult *result;
-#if ! USE_POLKIT
-    GError *err = NULL;
-#endif
-
-    g_return_if_fail(SPICE_IS_USBREDIR_CHANNEL(channel));
-    g_return_if_fail(device != NULL);
-
-    CHANNEL_DEBUG(channel, "connecting usb channel %p", channel);
-
-    result = g_simple_async_result_new(G_OBJECT(channel), callback, user_data,
-                                 spice_usbredir_channel_connect_device_async);
-
-    if (!priv->host) {
-        g_simple_async_result_set_error(result,
-                            SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
-                            "Error libusb context not set");
-        goto done;
-    }
-
-    if (priv->state != STATE_DISCONNECTED) {
-        g_simple_async_result_set_error(result,
-                            SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
-                            "Error channel is busy");
-        goto done;
-    }
-
-    priv->device = libusb_ref_device(device);
-    priv->spice_device = g_boxed_copy(spice_usb_device_get_type(),
-                                      spice_device);
-#if USE_POLKIT
-    priv->result = result;
-    priv->state  = STATE_WAITING_FOR_ACL_HELPER;
-    priv->acl_helper = spice_usb_acl_helper_new();
-    g_object_set(spice_channel_get_session(SPICE_CHANNEL(channel)),
-                 "inhibit-keyboard-grab", TRUE, NULL);
-    spice_usb_acl_helper_open_acl(priv->acl_helper,
-                                  libusb_get_bus_number(device),
-                                  libusb_get_device_address(device),
-                                  cancellable,
-                                  spice_usbredir_channel_open_acl_cb,
-                                  channel);
-    return;
-#else
-    if (!spice_usbredir_channel_open_device(channel, &err)) {
-        g_simple_async_result_take_error(result, err);
-        libusb_unref_device(priv->device);
-        priv->device = NULL;
-        g_boxed_free(spice_usb_device_get_type(), priv->spice_device);
-        priv->spice_device = NULL;
-    }
-#endif
-
-done:
-    g_simple_async_result_complete_in_idle(result);
-    g_object_unref(result);
-}
-
-G_GNUC_INTERNAL
-gboolean spice_usbredir_channel_connect_device_finish(
-                                               SpiceUsbredirChannel *channel,
-                                               GAsyncResult         *res,
-                                               GError              **err)
-{
-    GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT(res);
-
-    g_return_val_if_fail(g_simple_async_result_is_valid(res, G_OBJECT(channel),
-                                 spice_usbredir_channel_connect_device_async),
-                         FALSE);
-
-    if (g_simple_async_result_propagate_error(result, err))
-        return FALSE;
-
-    return TRUE;
-}
-
-G_GNUC_INTERNAL
-void spice_usbredir_channel_disconnect_device(SpiceUsbredirChannel *channel)
-{
-    SpiceUsbredirChannelPrivate *priv = channel->priv;
-
-    CHANNEL_DEBUG(channel, "disconnecting device from usb channel %p", channel);
-
-    switch (priv->state) {
-    case STATE_DISCONNECTED:
-    case STATE_DISCONNECTING:
-        break;
-#if USE_POLKIT
-    case STATE_WAITING_FOR_ACL_HELPER:
-        priv->state = STATE_DISCONNECTING;
-        /* We're still waiting for the acl helper -> cancel it */
-        spice_usb_acl_helper_close_acl(priv->acl_helper);
-        break;
-#endif
-    case STATE_CONNECTED:
-        /*
-         * This sets the usb event thread run condition to FALSE, therefor
-         * it must be done before usbredirhost_set_device NULL, as
-         * usbredirhost_set_device NULL will interrupt the
-         * libusb_handle_events call in the thread.
-         */
-        {
-            SpiceSession *session = spice_channel_get_session(SPICE_CHANNEL(channel));
-            if (session != NULL)
-                spice_usb_device_manager_stop_event_listening(
-                    spice_usb_device_manager_get(session, NULL));
-        }
-        /* This also closes the libusb handle we passed from open_device */
-        usbredirhost_set_device(priv->host, NULL);
-        libusb_unref_device(priv->device);
-        priv->device = NULL;
-        g_boxed_free(spice_usb_device_get_type(), priv->spice_device);
-        priv->spice_device = NULL;
-        priv->state  = STATE_DISCONNECTED;
-        break;
-    }
-}
-
-G_GNUC_INTERNAL
-libusb_device *spice_usbredir_channel_get_device(SpiceUsbredirChannel *channel)
-{
-    return channel->priv->device;
-}
-
-G_GNUC_INTERNAL
-void spice_usbredir_channel_get_guest_filter(
-                          SpiceUsbredirChannel               *channel,
-                          const struct usbredirfilter_rule  **rules_ret,
-                          int                                *rules_count_ret)
-{
-    SpiceUsbredirChannelPrivate *priv = channel->priv;
-
-    g_return_if_fail(priv->host != NULL);
-
-    usbredirhost_get_guest_filter(priv->host, rules_ret, rules_count_ret);
-}
-
-/* ------------------------------------------------------------------ */
-/* callbacks (any context)                                            */
-
-/* Note that this function must be re-entrant safe, as it can get called
-   from both the main thread as well as from the usb event handling thread */
-static void usbredir_write_flush_callback(void *user_data)
-{
-    SpiceUsbredirChannel *channel = SPICE_USBREDIR_CHANNEL(user_data);
-    SpiceUsbredirChannelPrivate *priv = channel->priv;
-
-    if (spice_channel_get_state(SPICE_CHANNEL(channel)) !=
-            SPICE_CHANNEL_STATE_READY)
-        return;
-
-    if (!priv->host)
-        return;
-
-    usbredirhost_write_guest_data(priv->host);
-}
-
-static void usbredir_log(void *user_data, int level, const char *msg)
-{
-    SpiceUsbredirChannel *channel = user_data;
-    SpiceUsbredirChannelPrivate *priv = channel->priv;
-
-    if (priv->catch_error && level == usbredirparser_error) {
-        CHANNEL_DEBUG(channel, "%s", msg);
-        /* Remove "usbredirhost: " prefix from usbredirhost messages */
-        if (strncmp(msg, "usbredirhost: ", 14) == 0)
-            g_set_error_literal(priv->catch_error, SPICE_CLIENT_ERROR,
-                                SPICE_CLIENT_ERROR_FAILED, msg + 14);
-        else
-            g_set_error_literal(priv->catch_error, SPICE_CLIENT_ERROR,
-                                SPICE_CLIENT_ERROR_FAILED, msg);
-        return;
-    }
-
-    switch (level) {
-        case usbredirparser_error:
-            g_critical("%s", msg); break;
-        case usbredirparser_warning:
-            g_warning("%s", msg); break;
-        default:
-            CHANNEL_DEBUG(channel, "%s", msg); break;
-    }
-}
-
-static int usbredir_read_callback(void *user_data, uint8_t *data, int count)
-{
-    SpiceUsbredirChannel *channel = user_data;
-    SpiceUsbredirChannelPrivate *priv = channel->priv;
-
-    if (priv->read_buf_size < count) {
-        count = priv->read_buf_size;
-    }
-
-    memcpy(data, priv->read_buf, count);
-
-    priv->read_buf_size -= count;
-    if (priv->read_buf_size) {
-        priv->read_buf += count;
-    } else {
-        priv->read_buf = NULL;
-    }
-
-    return count;
-}
-
-static void usbredir_free_write_cb_data(uint8_t *data, void *user_data)
-{
-    SpiceUsbredirChannel *channel = user_data;
-    SpiceUsbredirChannelPrivate *priv = channel->priv;
-
-    usbredirhost_free_write_buffer(priv->host, data);
-}
-
-static int usbredir_write_callback(void *user_data, uint8_t *data, int count)
-{
-    SpiceUsbredirChannel *channel = user_data;
-    SpiceMsgOut *msg_out;
-
-    msg_out = spice_msg_out_new(SPICE_CHANNEL(channel),
-                                SPICE_MSGC_SPICEVMC_DATA);
-    spice_marshaller_add_ref_full(msg_out->marshaller, data, count,
-                                  usbredir_free_write_cb_data, channel);
-    spice_msg_out_send(msg_out);
-
-    return count;
-}
-
-static void *usbredir_alloc_lock(void) {
-#if GLIB_CHECK_VERSION(2,32,0)
-    GMutex *mutex;
-
-    mutex = g_new0(GMutex, 1);
-    g_mutex_init(mutex);
-
-    return mutex;
-#else
-    return g_mutex_new();
-#endif
-}
-
-static void usbredir_lock_lock(void *user_data) {
-    GMutex *mutex = user_data;
-
-    g_mutex_lock(mutex);
-}
-
-static void usbredir_unlock_lock(void *user_data) {
-    GMutex *mutex = user_data;
-
-    g_mutex_unlock(mutex);
-}
-
-static void usbredir_free_lock(void *user_data) {
-    GMutex *mutex = user_data;
-
-#if GLIB_CHECK_VERSION(2,32,0)
-    g_mutex_clear(mutex);
-    g_free(mutex);
-#else
-    g_mutex_free(mutex);
-#endif
-}
-
-/* --------------------------------------------------------------------- */
-
-typedef struct device_error_data {
-    SpiceUsbredirChannel *channel;
-    SpiceUsbDevice *spice_device;
-    GError *error;
-    struct coroutine *caller;
-} device_error_data;
-
-/* main context */
-static gboolean device_error(gpointer user_data)
-{
-    device_error_data *data = user_data;
-    SpiceUsbredirChannel *channel = data->channel;
-    SpiceUsbredirChannelPrivate *priv = channel->priv;
-
-    /* Check that the device has not changed before we manage to run */
-    if (data->spice_device == priv->spice_device) {
-        spice_usbredir_channel_disconnect_device(channel);
-        spice_usb_device_manager_device_error(
-                spice_usb_device_manager_get(
-                    spice_channel_get_session(SPICE_CHANNEL(channel)), NULL),
-                data->spice_device, data->error);
-    }
-
-    coroutine_yieldto(data->caller, NULL);
-    return FALSE;
-}
-
-/* --------------------------------------------------------------------- */
-/* coroutine context                                                     */
-static void spice_usbredir_channel_up(SpiceChannel *c)
-{
-    SpiceUsbredirChannel *channel = SPICE_USBREDIR_CHANNEL(c);
-    SpiceUsbredirChannelPrivate *priv = channel->priv;
-
-    /* Flush any pending writes */
-    usbredirhost_write_guest_data(priv->host);
-}
-
-static void usbredir_handle_msg(SpiceChannel *c, SpiceMsgIn *in)
-{
-    SpiceUsbredirChannel *channel = SPICE_USBREDIR_CHANNEL(c);
-    SpiceUsbredirChannelPrivate *priv = channel->priv;
-    device_error_data data;
-    int r, size;
-    uint8_t *buf;
-
-    g_return_if_fail(priv->host != NULL);
-
-    /* No recursion allowed! */
-    g_return_if_fail(priv->read_buf == NULL);
-
-    buf = spice_msg_in_raw(in, &size);
-    priv->read_buf = buf;
-    priv->read_buf_size = size;
-
-    r = usbredirhost_read_guest_data(priv->host);
-    if (r != 0) {
-        SpiceUsbDevice *spice_device = priv->spice_device;
-        gchar *desc;
-        GError *err;
-
-        g_return_if_fail(spice_device != NULL);
-
-        desc = spice_usb_device_get_description(spice_device, NULL);
-        switch (r) {
-        case usbredirhost_read_parse_error:
-            err = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
-                              _("usbredir protocol parse error for %s"), desc);
-            break;
-        case usbredirhost_read_device_rejected:
-            err = g_error_new(SPICE_CLIENT_ERROR,
-                              SPICE_CLIENT_USB_DEVICE_REJECTED,
-                              _("%s rejected by host"), desc);
-            break;
-        case usbredirhost_read_device_lost:
-            err = g_error_new(SPICE_CLIENT_ERROR,
-                              SPICE_CLIENT_USB_DEVICE_LOST,
-                              _("%s disconnected (fatal IO error)"), desc);
-            break;
-        default:
-            err = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
-                              _("Unknown error (%d) for %s"), r, desc);
-        }
-        g_free(desc);
-
-        CHANNEL_DEBUG(c, "%s", err->message);
-
-        data.channel = channel;
-        data.caller = coroutine_self();
-        data.spice_device = g_boxed_copy(spice_usb_device_get_type(), spice_device);
-        data.error = err;
-        g_idle_add(device_error, &data);
-        coroutine_yield(NULL);
-
-        g_boxed_free(spice_usb_device_get_type(), data.spice_device);
-
-        g_error_free(err);
-    }
-}
-
-#endif /* USE_USBREDIR */
diff --git a/gtk/channel-usbredir.h b/gtk/channel-usbredir.h
deleted file mode 100644
index 0cc4fbf..0000000
--- a/gtk/channel-usbredir.h
+++ /dev/null
@@ -1,71 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2011 Red Hat, Inc.
-
-   Red Hat Authors:
-   Hans de Goede <hdegoede at redhat.com>
-
-   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_CLIENT_USBREDIR_CHANNEL_H__
-#define __SPICE_CLIENT_USBREDIR_CHANNEL_H__
-
-#include "spice-client.h"
-
-G_BEGIN_DECLS
-
-#define SPICE_TYPE_USBREDIR_CHANNEL            (spice_usbredir_channel_get_type())
-#define SPICE_USBREDIR_CHANNEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), SPICE_TYPE_USBREDIR_CHANNEL, SpiceUsbredirChannel))
-#define SPICE_USBREDIR_CHANNEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), SPICE_TYPE_USBREDIR_CHANNEL, SpiceUsbredirChannelClass))
-#define SPICE_IS_USBREDIR_CHANNEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), SPICE_TYPE_USBREDIR_CHANNEL))
-#define SPICE_IS_USBREDIR_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SPICE_TYPE_USBREDIR_CHANNEL))
-#define SPICE_USBREDIR_CHANNEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), SPICE_TYPE_USBREDIR_CHANNEL, SpiceUsbredirChannelClass))
-
-typedef struct _SpiceUsbredirChannel SpiceUsbredirChannel;
-typedef struct _SpiceUsbredirChannelClass SpiceUsbredirChannelClass;
-typedef struct _SpiceUsbredirChannelPrivate SpiceUsbredirChannelPrivate;
-
-/**
- * SpiceUsbredirChannel:
- *
- * The #SpiceUsbredirChannel struct is opaque and should not be accessed directly.
- */
-struct _SpiceUsbredirChannel {
-    SpiceChannel parent;
-
-    /*< private >*/
-    SpiceUsbredirChannelPrivate *priv;
-    /* Do not add fields to this struct */
-};
-
-/**
- * SpiceUsbredirChannelClass:
- * @parent_class: Parent class.
- *
- * Class structure for #SpiceUsbredirChannel.
- */
-struct _SpiceUsbredirChannelClass {
-    SpiceChannelClass parent_class;
-
-    /* signals */
-
-    /*< private >*/
-    /* Do not add fields to this struct */
-};
-
-GType spice_usbredir_channel_get_type(void);
-
-G_END_DECLS
-
-#endif /* __SPICE_CLIENT_USBREDIR_CHANNEL_H__ */
diff --git a/gtk/channel-webdav.c b/gtk/channel-webdav.c
deleted file mode 100644
index bde728e..0000000
--- a/gtk/channel-webdav.c
+++ /dev/null
@@ -1,613 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2013 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-channel-priv.h"
-#include "spice-session-priv.h"
-#include "spice-marshal.h"
-#include "glib-compat.h"
-#include "vmcstream.h"
-#include "giopipe.h"
-
-/**
- * SECTION:channel-webdav
- * @short_description: exports a directory
- * @title: WebDAV Channel
- * @section_id:
- * @see_also: #SpiceChannel
- * @stability: Stable
- * @include: channel-webdav.h
- *
- * The "webdav" channel exports a directory to the guest for file
- * manipulation (read/write/copy etc). The underlying protocol is
- * implemented using WebDAV (RFC 4918).
- *
- * By default, the shared directory is the one associated with GLib
- * %G_USER_DIRECTORY_PUBLIC_SHARE. You can specify a different
- * directory with #SpiceSession #SpiceSession:shared-dir property.
- *
- * Since: 0.24
- */
-
-#define SPICE_WEBDAV_CHANNEL_GET_PRIVATE(obj)                                  \
-    (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_WEBDAV_CHANNEL, SpiceWebdavChannelPrivate))
-
-typedef struct _OutputQueue OutputQueue;
-
-struct _SpiceWebdavChannelPrivate {
-    SpiceVmcStream *stream;
-    GCancellable *cancellable;
-    GHashTable *clients;
-    OutputQueue *queue;
-
-    gboolean demuxing;
-    struct _demux {
-        gint64 client;
-        guint16 size;
-        guint8 *buf;
-    } demux;
-};
-
-G_DEFINE_TYPE(SpiceWebdavChannel, spice_webdav_channel, SPICE_TYPE_PORT_CHANNEL)
-
-static void spice_webdav_handle_msg(SpiceChannel *channel, SpiceMsgIn *msg);
-
-struct _OutputQueue {
-    GOutputStream *output;
-    gboolean flushing;
-    guint idle_id;
-    GQueue *queue;
-};
-
-typedef struct _OutputQueueElem {
-    OutputQueue *queue;
-    const guint8 *buf;
-    gsize size;
-    GFunc pushed_cb;
-    gpointer user_data;
-} OutputQueueElem;
-
-static OutputQueue* output_queue_new(GOutputStream *output)
-{
-    OutputQueue *queue = g_new0(OutputQueue, 1);
-
-    queue->output = g_object_ref(output);
-    queue->queue = g_queue_new();
-
-    return queue;
-}
-
-static void output_queue_free(OutputQueue *queue)
-{
-    g_warn_if_fail(g_queue_get_length(queue->queue) == 0);
-    g_warn_if_fail(!queue->flushing);
-
-    g_queue_free_full(queue->queue, g_free);
-    g_clear_object(&queue->output);
-    if (queue->idle_id)
-        g_source_remove(queue->idle_id);
-    g_free(queue);
-}
-
-static gboolean output_queue_idle(gpointer user_data);
-
-static void output_queue_flush_cb(GObject *source_object,
-                                  GAsyncResult *res,
-                                  gpointer user_data)
-{
-    GError *error = NULL;
-    OutputQueueElem *e = user_data;
-    OutputQueue *q = e->queue;
-
-    q->flushing = FALSE;
-    g_output_stream_flush_finish(G_OUTPUT_STREAM(source_object),
-                                 res, &error);
-    if (error)
-        g_warning("error: %s", error->message);
-
-    g_clear_error(&error);
-
-    if (!q->idle_id)
-        q->idle_id = g_idle_add(output_queue_idle, q);
-
-    g_free(e);
-}
-
-static gboolean output_queue_idle(gpointer user_data)
-{
-    OutputQueue *q = user_data;
-    OutputQueueElem *e;
-    GError *error = NULL;
-
-    if (q->flushing) {
-        q->idle_id = 0;
-        return FALSE;
-    }
-
-    e = g_queue_pop_head(q->queue);
-    if (!e) {
-        q->idle_id = 0;
-        return FALSE;
-    }
-
-    if (!g_output_stream_write_all(q->output, e->buf, e->size, NULL, NULL, &error))
-        goto err;
-    else if (e->pushed_cb)
-        e->pushed_cb(q, e->user_data);
-
-    q->flushing = TRUE;
-    g_output_stream_flush_async(q->output, G_PRIORITY_DEFAULT, NULL, output_queue_flush_cb, e);
-
-    return TRUE;
-
-err:
-    g_warning("failed to write to output stream");
-    if (error)
-        g_warning("error: %s", error->message);
-    g_clear_error(&error);
-
-    q->idle_id = 0;
-    return FALSE;
-}
-
-static void output_queue_push(OutputQueue *q, const guint8 *buf, gsize size,
-                              GFunc pushed_cb, gpointer user_data)
-{
-    OutputQueueElem *e = g_new(OutputQueueElem, 1);
-
-    e->buf = buf;
-    e->size = size;
-    e->pushed_cb = pushed_cb;
-    e->user_data = user_data;
-    e->queue = q;
-    g_queue_push_tail(q->queue, e);
-
-    if (!q->idle_id && !q->flushing)
-        q->idle_id = g_idle_add(output_queue_idle, q);
-}
-
-typedef struct Client
-{
-    guint refs;
-    SpiceWebdavChannel *self;
-    GIOStream *pipe;
-    gint64 id;
-    GCancellable *cancellable;
-
-    struct _mux {
-        gint64 id;
-        guint16 size;
-        guint8 *buf;
-    } mux;
-} Client;
-
-static void
-client_unref(Client *client)
-{
-    if (--client->refs > 0)
-        return;
-
-    g_free(client->mux.buf);
-
-    g_object_unref(client->pipe);
-    g_object_unref(client->cancellable);
-
-    g_free(client);
-}
-
-static Client *
-client_ref(Client *client)
-{
-    client->refs++;
-    return client;
-}
-
-static void client_start_read(SpiceWebdavChannel *self, Client *client);
-
-static void remove_client(SpiceWebdavChannel *self, Client *client)
-{
-    SpiceWebdavChannelPrivate *c;
-
-    if (g_cancellable_is_cancelled(client->cancellable))
-        return;
-
-    g_cancellable_cancel(client->cancellable);
-
-    c = self->priv;
-    g_hash_table_remove(c->clients, &client->id);
-}
-
-static void mux_pushed_cb(OutputQueue *q, gpointer user_data)
-{
-    Client *client = user_data;
-
-    if (client->mux.size == 0) {
-        remove_client(client->self, client);
-    } else {
-        client_start_read(client->self, client);
-    }
-
-    client_unref(client);
-}
-
-#define MAX_MUX_SIZE G_MAXUINT16
-
-static void server_reply_cb(GObject *source_object,
-                            GAsyncResult *res,
-                            gpointer user_data)
-{
-    Client *client = user_data;
-    SpiceWebdavChannel *self = client->self;
-    SpiceWebdavChannelPrivate *c = self->priv;
-    GError *err = NULL;
-    gssize size;
-
-    size = g_input_stream_read_finish(G_INPUT_STREAM(source_object), res, &err);
-    if (err || g_cancellable_is_cancelled(client->cancellable))
-        goto end;
-
-    g_return_if_fail(size <= MAX_MUX_SIZE);
-    g_return_if_fail(size >= 0);
-    client->mux.size = size;
-
-    output_queue_push(c->queue, (guint8 *)&client->mux.id, sizeof(gint64), NULL, NULL);
-    client->mux.size = GUINT16_TO_LE(client->mux.size);
-    output_queue_push(c->queue, (guint8 *)&client->mux.size, sizeof(guint16), NULL, NULL);
-    output_queue_push(c->queue, (guint8 *)client->mux.buf, size, (GFunc)mux_pushed_cb, client);
-
-    return;
-
-end:
-    if (err) {
-        if (!g_cancellable_is_cancelled(client->cancellable))
-            g_warning("read error: %s", err->message);
-        remove_client(self, client);
-        g_clear_error(&err);
-    }
-
-    client_unref(client);
-}
-
-static void client_start_read(SpiceWebdavChannel *self, Client *client)
-{
-    GInputStream *input;
-
-    input = g_io_stream_get_input_stream(G_IO_STREAM(client->pipe));
-    g_input_stream_read_async(input, client->mux.buf, MAX_MUX_SIZE,
-                              G_PRIORITY_DEFAULT, client->cancellable, server_reply_cb,
-                              client_ref(client));
-}
-
-static void start_demux(SpiceWebdavChannel *self);
-
-static void demux_to_client_finish(SpiceWebdavChannel *self,
-                                   Client *client, gboolean fail)
-{
-    SpiceWebdavChannelPrivate *c = self->priv;
-
-    if (fail) {
-        remove_client(self, client);
-    }
-
-    c->demuxing = FALSE;
-    start_demux(self);
-}
-
-static void demux_to_client_cb(GObject *source, GAsyncResult *result, gpointer user_data)
-{
-    Client *client = user_data;
-    SpiceWebdavChannelPrivate *c = client->self->priv;
-    GError *error = NULL;
-    gboolean fail;
-    gsize size;
-
-    g_output_stream_write_all_finish(G_OUTPUT_STREAM(source), result, &size, &error);
-
-    if (error) {
-        CHANNEL_DEBUG(client->self, "write failed: %s", error->message);
-        g_clear_error(&error);
-    }
-
-    fail = (size != c->demux.size);
-    g_warn_if_fail(size == c->demux.size);
-    demux_to_client_finish(client->self, client, fail);
-}
-
-static void demux_to_client(SpiceWebdavChannel *self,
-                            Client *client)
-{
-    SpiceWebdavChannelPrivate *c = self->priv;
-    gsize size = c->demux.size;
-
-    CHANNEL_DEBUG(self, "pushing %"G_GSIZE_FORMAT" to client %p", size, client);
-
-    if (size > 0) {
-        g_output_stream_write_all_async(g_io_stream_get_output_stream(client->pipe),
-                                        c->demux.buf, size, G_PRIORITY_DEFAULT,
-                                        c->cancellable, demux_to_client_cb, client);
-        return;
-    } else {
-        /* Nothing to write */
-        demux_to_client_finish(self, client, FALSE);
-    }
-}
-
-static void start_client(SpiceWebdavChannel *self)
-{
-#ifdef USE_PHODAV
-    SpiceWebdavChannelPrivate *c = self->priv;
-    Client *client;
-    GIOStream *peer = NULL;
-    SpiceSession *session;
-    SoupServer *server;
-    GSocketAddress *addr;
-    GError *error = NULL;
-
-    session = spice_channel_get_session(SPICE_CHANNEL(self));
-    server = phodav_server_get_soup_server(spice_session_get_webdav_server(session));
-
-    CHANNEL_DEBUG(self, "starting client %" G_GINT64_FORMAT, c->demux.client);
-
-    client = g_new0(Client, 1);
-    client->refs = 1;
-    client->id = c->demux.client;
-    client->self = self;
-    client->mux.id = GINT64_TO_LE(client->id);
-    client->mux.buf = g_malloc0(MAX_MUX_SIZE);
-    client->cancellable = g_cancellable_new();
-    spice_make_pipe(&client->pipe, &peer);
-
-    addr = g_inet_socket_address_new_from_string ("127.0.0.1", 0);
-    if (!soup_server_accept_iostream(server, peer, addr, addr, &error))
-        goto fail;
-
-    g_hash_table_insert(c->clients, &client->id, client);
-
-    client_start_read(self, client);
-    demux_to_client(self, client);
-
-    g_clear_object(&addr);
-    return;
-
-fail:
-    if (error)
-        CHANNEL_DEBUG(self, "failed to start client: %s", error->message);
-
-    g_clear_object(&addr);
-    g_clear_object(&peer);
-    g_clear_error(&error);
-    client_unref(client);
-#endif
-}
-
-static void data_read_cb(GObject *source_object,
-                         GAsyncResult *res,
-                         gpointer user_data)
-{
-    SpiceWebdavChannel *self = user_data;
-    SpiceWebdavChannelPrivate *c;
-    Client *client;
-    GError *error = NULL;
-    gssize size;
-
-    size = spice_vmc_input_stream_read_all_finish(G_INPUT_STREAM(source_object), res, &error);
-    if (error) {
-        g_warning("error: %s", error->message);
-        g_clear_error(&error);
-        return;
-    }
-
-    c = self->priv;
-    g_return_if_fail(size == c->demux.size);
-
-    client = g_hash_table_lookup(c->clients, &c->demux.client);
-
-    if (client)
-        demux_to_client(self, client);
-    else
-        start_client(self);
-}
-
-
-static void size_read_cb(GObject *source_object,
-                         GAsyncResult *res,
-                         gpointer user_data)
-{
-    SpiceWebdavChannel *self = user_data;
-    SpiceWebdavChannelPrivate *c;
-    GInputStream *istream = G_INPUT_STREAM(source_object);
-    GError *error = NULL;
-    gssize size;
-
-    size = spice_vmc_input_stream_read_all_finish(G_INPUT_STREAM(source_object), res, &error);
-    if (error || size != sizeof(guint16))
-        goto end;
-
-    c = self->priv;
-    c->demux.size = GUINT16_FROM_LE(c->demux.size);
-    spice_vmc_input_stream_read_all_async(istream,
-        c->demux.buf, c->demux.size,
-        G_PRIORITY_DEFAULT, c->cancellable, data_read_cb, self);
-    return;
-
-end:
-    if (error) {
-        g_warning("error: %s", error->message);
-        g_clear_error(&error);
-    }
-}
-
-static void client_read_cb(GObject *source_object,
-                               GAsyncResult *res,
-                               gpointer user_data)
-{
-    SpiceWebdavChannel *self = user_data;
-    SpiceWebdavChannelPrivate *c = self->priv;
-    GInputStream *istream = G_INPUT_STREAM(source_object);
-    GError *error = NULL;
-    gssize size;
-
-    size = spice_vmc_input_stream_read_all_finish(G_INPUT_STREAM(source_object), res, &error);
-    if (error || size != sizeof(gint64))
-        goto end;
-
-    c->demux.client = GINT64_FROM_LE(c->demux.client);
-    spice_vmc_input_stream_read_all_async(istream,
-        &c->demux.size, sizeof(guint16),
-        G_PRIORITY_DEFAULT, c->cancellable, size_read_cb, self);
-    return;
-
-end:
-    if (error) {
-        g_warning("error: %s", error->message);
-        g_clear_error(&error);
-    }
-}
-
-static void start_demux(SpiceWebdavChannel *self)
-{
-    SpiceWebdavChannelPrivate *c = self->priv;
-    GInputStream *istream = g_io_stream_get_input_stream(G_IO_STREAM(c->stream));
-
-    if (c->demuxing)
-        return;
-
-    c->demuxing = TRUE;
-
-    CHANNEL_DEBUG(self, "start demux");
-    spice_vmc_input_stream_read_all_async(istream, &c->demux.client, sizeof(gint64),
-        G_PRIORITY_DEFAULT, c->cancellable, client_read_cb, self);
-
-}
-
-static void port_event(SpiceWebdavChannel *self, gint event)
-{
-    SpiceWebdavChannelPrivate *c = self->priv;
-
-    CHANNEL_DEBUG(self, "port event:%d", event);
-    if (event == SPICE_PORT_EVENT_OPENED) {
-        g_cancellable_reset(c->cancellable);
-        start_demux(self);
-    } else {
-        g_cancellable_cancel(c->cancellable);
-        c->demuxing = FALSE;
-        g_hash_table_remove_all(c->clients);
-    }
-}
-
-static void client_remove_unref(gpointer data)
-{
-    Client *client = data;
-
-    g_cancellable_cancel(client->cancellable);
-    client_unref(client);
-}
-
-static void spice_webdav_channel_init(SpiceWebdavChannel *channel)
-{
-    SpiceWebdavChannelPrivate *c = SPICE_WEBDAV_CHANNEL_GET_PRIVATE(channel);
-
-    channel->priv = c;
-    c->stream = spice_vmc_stream_new(SPICE_CHANNEL(channel));
-    c->cancellable = g_cancellable_new();
-    c->clients = g_hash_table_new_full(g_int64_hash, g_int64_equal,
-                                       NULL, client_remove_unref);
-    c->demux.buf = g_malloc0(MAX_MUX_SIZE);
-
-    GOutputStream *ostream = g_io_stream_get_output_stream(G_IO_STREAM(c->stream));
-    c->queue = output_queue_new(ostream);
-}
-
-static void spice_webdav_channel_finalize(GObject *object)
-{
-    SpiceWebdavChannelPrivate *c = SPICE_WEBDAV_CHANNEL(object)->priv;
-
-    g_free(c->demux.buf);
-
-    G_OBJECT_CLASS(spice_webdav_channel_parent_class)->finalize(object);
-}
-
-static void spice_webdav_channel_dispose(GObject *object)
-{
-    SpiceWebdavChannelPrivate *c = SPICE_WEBDAV_CHANNEL(object)->priv;
-
-    g_cancellable_cancel(c->cancellable);
-    g_clear_object(&c->cancellable);
-    g_clear_pointer(&c->queue, output_queue_free);
-    g_clear_object(&c->stream);
-    g_hash_table_unref(c->clients);
-
-    G_OBJECT_CLASS(spice_webdav_channel_parent_class)->dispose(object);
-}
-
-static void spice_webdav_channel_up(SpiceChannel *channel)
-{
-    CHANNEL_DEBUG(channel, "up");
-}
-
-static void spice_webdav_channel_class_init(SpiceWebdavChannelClass *klass)
-{
-    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
-    SpiceChannelClass *channel_class = SPICE_CHANNEL_CLASS(klass);
-
-    gobject_class->dispose      = spice_webdav_channel_dispose;
-    gobject_class->finalize     = spice_webdav_channel_finalize;
-    channel_class->handle_msg   = spice_webdav_handle_msg;
-    channel_class->channel_up   = spice_webdav_channel_up;
-
-    g_signal_override_class_handler("port-event",
-                                    SPICE_TYPE_WEBDAV_CHANNEL,
-                                    G_CALLBACK(port_event));
-
-    g_type_class_add_private(klass, sizeof(SpiceWebdavChannelPrivate));
-}
-
-/* coroutine context */
-static void webdav_handle_msg(SpiceChannel *channel, SpiceMsgIn *in)
-{
-    SpiceWebdavChannel *self = SPICE_WEBDAV_CHANNEL(channel);
-    SpiceWebdavChannelPrivate *c = self->priv;
-    int size;
-    uint8_t *buf;
-
-    buf = spice_msg_in_raw(in, &size);
-    CHANNEL_DEBUG(channel, "len:%d buf:%p", size, buf);
-
-    spice_vmc_input_stream_co_data(
-        SPICE_VMC_INPUT_STREAM(g_io_stream_get_input_stream(G_IO_STREAM(c->stream))),
-        buf, size);
-}
-
-
-/* coroutine context */
-static void spice_webdav_handle_msg(SpiceChannel *channel, SpiceMsgIn *msg)
-{
-    int type = spice_msg_in_type(msg);
-    SpiceChannelClass *parent_class;
-
-    parent_class = SPICE_CHANNEL_CLASS(spice_webdav_channel_parent_class);
-
-    if (type == SPICE_MSG_SPICEVMC_DATA)
-        webdav_handle_msg(channel, msg);
-    else if (parent_class->handle_msg)
-        parent_class->handle_msg(channel, msg);
-    else
-        g_return_if_reached();
-}
diff --git a/gtk/channel-webdav.h b/gtk/channel-webdav.h
deleted file mode 100644
index 7940706..0000000
--- a/gtk/channel-webdav.h
+++ /dev/null
@@ -1,68 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2013 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_WEBDAV_CHANNEL_H__
-#define __SPICE_WEBDAV_CHANNEL_H__
-
-#include <gio/gio.h>
-#include "spice-client.h"
-#include "channel-port.h"
-
-G_BEGIN_DECLS
-
-#define SPICE_TYPE_WEBDAV_CHANNEL            (spice_webdav_channel_get_type())
-#define SPICE_WEBDAV_CHANNEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), SPICE_TYPE_WEBDAV_CHANNEL, SpiceWebdavChannel))
-#define SPICE_WEBDAV_CHANNEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), SPICE_TYPE_WEBDAV_CHANNEL, SpiceWebdavChannelClass))
-#define SPICE_IS_WEBDAV_CHANNEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), SPICE_TYPE_WEBDAV_CHANNEL))
-#define SPICE_IS_WEBDAV_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SPICE_TYPE_WEBDAV_CHANNEL))
-#define SPICE_WEBDAV_CHANNEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), SPICE_TYPE_WEBDAV_CHANNEL, SpiceWebdavChannelClass))
-
-typedef struct _SpiceWebdavChannel SpiceWebdavChannel;
-typedef struct _SpiceWebdavChannelClass SpiceWebdavChannelClass;
-typedef struct _SpiceWebdavChannelPrivate SpiceWebdavChannelPrivate;
-
-/**
- * SpiceWebdavChannel:
- *
- * The #SpiceWebdavChannel struct is opaque and should not be accessed directly.
- */
-struct _SpiceWebdavChannel {
-    SpicePortChannel parent;
-
-    /*< private >*/
-    SpiceWebdavChannelPrivate *priv;
-    /* Do not add fields to this struct */
-};
-
-/**
- * SpiceWebdavChannelClass:
- * @parent_class: Parent class.
- *
- * Class structure for #SpiceWebdavChannel.
- */
-struct _SpiceWebdavChannelClass {
-    SpicePortChannelClass parent_class;
-
-    /*< private >*/
-    /* Do not add fields to this struct */
-};
-
-GType spice_webdav_channel_get_type(void);
-
-G_END_DECLS
-
-#endif /* __SPICE_WEBDAV_CHANNEL_H__ */
diff --git a/gtk/client_sw_canvas.c b/gtk/client_sw_canvas.c
deleted file mode 100644
index a69abe0..0000000
--- a/gtk/client_sw_canvas.c
+++ /dev/null
@@ -1,20 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2014 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/>.
-*/
-#define SW_CANVAS_CACHE
-
-#include "common/sw_canvas.c"
diff --git a/gtk/client_sw_canvas.h b/gtk/client_sw_canvas.h
deleted file mode 100644
index 1180c5b..0000000
--- a/gtk/client_sw_canvas.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2014 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_CLIENT_SW_CANVAS_H__
-#define __SPICE_CLIENT_SW_CANVAS_H__
-
-#define SW_CANVAS_CACHE
-
-#include <common/sw_canvas.h>
-
-#endif /* __SPICE_CLIENT_SW_CANVAS_H__ */
diff --git a/gtk/continuation.c b/gtk/continuation.c
deleted file mode 100644
index adce858..0000000
--- a/gtk/continuation.c
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * GTK VNC Widget
- *
- * Copyright (C) 2006  Anthony Liguori <anthony at codemonkey.ws>
- *
- * 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.0 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, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
- */
-#include "config.h"
-
-/* keep this above system headers, but below config.h */
-#ifdef _FORTIFY_SOURCE
-#undef _FORTIFY_SOURCE
-#endif
-
-#include <errno.h>
-#include <glib.h>
-
-#include "continuation.h"
-
-/*
- * va_args to makecontext() must be type 'int', so passing
- * the pointer we need may require several int args. This
- * union is a quick hack to let us do that
- */
-union cc_arg {
-	void *p;
-	int i[2];
-};
-
-static void continuation_trampoline(int i0, int i1)
-{
-	union cc_arg arg;
-	struct continuation *cc;
-	arg.i[0] = i0;
-	arg.i[1] = i1;
-	cc = arg.p;
-
-	if (_setjmp(cc->jmp) == 0) {
-		ucontext_t tmp;
-		swapcontext(&tmp, &cc->last);
-	}
-
-	cc->entry(cc);
-}
-
-void cc_init(struct continuation *cc)
-{
-	volatile union cc_arg arg;
-	arg.p = cc;
-	if (getcontext(&cc->uc) == -1)
-		g_error("getcontext() failed: %s", g_strerror(errno));
-	cc->uc.uc_link = &cc->last;
-	cc->uc.uc_stack.ss_sp = cc->stack;
-	cc->uc.uc_stack.ss_size = cc->stack_size;
-	cc->uc.uc_stack.ss_flags = 0;
-
-	makecontext(&cc->uc, (void *)continuation_trampoline, 2, arg.i[0], arg.i[1]);
-	swapcontext(&cc->last, &cc->uc);
-}
-
-int cc_release(struct continuation *cc)
-{
-	if (cc->release)
-		return cc->release(cc);
-
-	return 0;
-}
-
-int cc_swap(struct continuation *from, struct continuation *to)
-{
-	to->exited = 0;
-	if (getcontext(&to->last) == -1)
-		return -1;
-	else if (to->exited == 0)
-		to->exited = 1; // so when coroutine finishes
-        else if (to->exited == 1)
-                return 1; // it ends up here
-
-	if (_setjmp(from->jmp) == 0)
-		_longjmp(to->jmp, 1);
-
-	return 0;
-}
-/*
- * Local variables:
- *  c-indent-level: 8
- *  c-basic-offset: 8
- *  tab-width: 8
- * End:
- */
diff --git a/gtk/continuation.h b/gtk/continuation.h
deleted file mode 100644
index 675a257..0000000
--- a/gtk/continuation.h
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * GTK VNC Widget
- *
- * Copyright (C) 2006  Anthony Liguori <anthony at codemonkey.ws>
- *
- * 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.0 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, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
- */
-
-#ifndef _CONTINUATION_H_
-#define _CONTINUATION_H_
-
-#include <stddef.h>
-#include <ucontext.h>
-#include <setjmp.h>
-
-struct continuation
-{
-	char *stack;
-	size_t stack_size;
-	void (*entry)(struct continuation *cc);
-	int (*release)(struct continuation *cc);
-
-	/* private */
-	ucontext_t uc;
-	ucontext_t last;
-	int exited;
-	jmp_buf jmp;
-};
-
-void cc_init(struct continuation *cc);
-
-int cc_release(struct continuation *cc);
-
-/* you can use an uninitialized struct continuation for from if you do not have
-   the current continuation handy. */
-int cc_swap(struct continuation *from, struct continuation *to);
-
-#define offset_of(type, member) ((unsigned long)(&((type *)0)->member))
-#define container_of(obj, type, member) \
-        (type *)(((char *)obj) - offset_of(type, member))
-
-#endif
-/*
- * Local variables:
- *  c-indent-level: 8
- *  c-basic-offset: 8
- *  tab-width: 8
- * End:
- */
diff --git a/gtk/controller/Makefile.am b/gtk/controller/Makefile.am
deleted file mode 100644
index 00552e8..0000000
--- a/gtk/controller/Makefile.am
+++ /dev/null
@@ -1,100 +0,0 @@
-NULL =
-
-AM_CPPFLAGS =					\
-	-DG_LOG_DOMAIN=\"GSpiceController\"	\
-	$(GIO_CFLAGS)				\
-	$(COMMON_CFLAGS)			\
-	$(NULL)
-
-# http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html
-AM_LDFLAGS =					\
-	-no-undefined				\
-	$(GIO_LIBS)				\
-	$(NULL)
-
-AM_VALAFLAGS =							\
-	--pkg gio-2.0						\
-	--pkg spice-protocol --vapidir=$(top_srcdir)/data	\
-	--pkg custom --vapidir=$(srcdir)			\
-	-C 							\
-	$(NULL)
-
-lib_LTLIBRARIES = libspice-controller.la
-noinst_PROGRAMS = test-controller spice-controller-dump
-
-libspice_controller_la_VALASOURCES =		\
-	menu.vala				\
-	controller.vala				\
-	foreign-menu.vala			\
-	util.vala				\
-	$(NULL)
-
-libspice_controller_la_BUILT_SOURCES =			\
-	$(libspice_controller_la_VALASOURCES:.vala=.c)	\
-	spice-controller.h				\
-	$(NULL)
-
-BUILT_SOURCES =						\
-	$(libspice_controller_la_BUILT_SOURCES)		\
-	controller.vala.stamp				\
-	$(NULL)
-
-libspice_controller_la_SOURCES =			\
-	$(libspice_controller_la_BUILT_SOURCES)		\
-	custom.h					\
-	spice-controller-listener.c			\
-	spice-controller-listener.h			\
-	spice-foreign-menu-listener.c			\
-	spice-foreign-menu-listener.h			\
-	$(NULL)
-
-if OS_WIN32
-libspice_controller_la_SOURCES +=		\
-	namedpipe.c				\
-	namedpipe.h				\
-	namedpipeconnection.c			\
-	namedpipeconnection.h			\
-	namedpipelistener.c			\
-	namedpipelistener.h			\
-	win32-util.c                            \
-	win32-util.h                            \
-	$(NULL)
-endif
-libspice_controller_la_LDFLAGS =		\
-	$(AM_LDFLAGS)				\
-	-version-info 0:0:0			\
-	$(NULL)
-
-libspice_controllerincludedir = $(includedir)/spice-controller
-libspice_controllerinclude_HEADERS =		\
-	spice-controller.h
-
-test_controller_SOURCES = test.c
-test_controller_LDADD = libspice-controller.la
-
-spice_controller_dump_SOURCES = dump.c
-spice_controller_dump_LDADD = libspice-controller.la
-
-controller.vala.stamp: $(libspice_controller_la_VALASOURCES) custom.vapi
-	@if test -z "$(VALAC)"; then						  \
-		echo "" ;							  \
-		echo "  *** Error: missing valac!" ;				  \
-		echo "  *** You must run autogen.sh or configure --enable-vala" ; \
-		echo "" ;							  \
-		exit 1 ;							  \
-	fi
-	$(VALA_V)$(VALAC) $(VALAFLAGS) $(AM_VALAFLAGS)			\
-	  $(addprefix $(srcdir)/,$(libspice_controller_la_VALASOURCES))	\
-	  -H spice-controller.h
-	@touch $@
-
-$(libspice_controller_la_BUILT_SOURCES): controller.vala.stamp
-
-EXTRA_DIST =					\
-	$(libspice_controller_la_VALASOURCES)	\
-	controller.vala.stamp			\
-	custom.vapi				\
-	gio-windows-2.0.vapi			\
-	$(NULL)
-
--include $(top_srcdir)/git.mk
diff --git a/gtk/controller/controller.vala b/gtk/controller/controller.vala
deleted file mode 100644
index 84b4527..0000000
--- a/gtk/controller/controller.vala
+++ /dev/null
@@ -1,286 +0,0 @@
-// Copyright (C) 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/>.
-
-using GLib;
-using Custom;
-using Win32;
-using Spice;
-using SpiceProtocol;
-
-namespace SpiceCtrl {
-
-public errordomain Error {
-	VALUE,
-}
-
-public class Controller: Object {
-	public string host { private set; get; }
-	public uint32 port { private set; get; }
-	public uint32 sport { private set; get; }
-	public string password { private set; get; }
-	public SpiceProtocol.Controller.Display display_flags { private set; get; }
-	public string tls_ciphers { private set; get; }
-	public string host_subject { private set; get; }
-	public string ca_file { private set; get; }
-	public string title { private set; get; }
-	public string hotkeys { private set; get; }
-	public string[] secure_channels { private set; get; }
-	public string[] disable_channels { private set; get; }
-	public SpiceCtrl.Menu? menu  { private set; get; }
-	public bool enable_smartcard { private set; get; }
-	public bool send_cad { private set; get; }
-	public string[] disable_effects {private set; get; }
-	public uint32 color_depth {private set; get; }
-	public bool enable_usbredir { private set; get; }
-	public bool enable_usb_autoshare { private set; get; }
-	public string usb_filter { private set; get; }
-	public string proxy { private set; get; }
-
-	public signal void do_connect ();
-	public signal void show ();
-	public signal void hide ();
-
-	public signal void client_connected ();
-
-	public void menu_item_click_msg (int32 item_id) {
-		var msg = SpiceProtocol.Controller.MsgValue ();
-		msg.base.size = (uint32)sizeof (SpiceProtocol.Controller.MsgValue);
-		msg.base.id = SpiceProtocol.Controller.MsgId.MENU_ITEM_CLICK;
-		msg.value = item_id;
-		unowned uint8[] p = ((uint8[])(&msg))[0:msg.base.size];
-		send_msg.begin (p);
-	}
-
-	public async bool send_msg (uint8[] p) throws GLib.Error {
-		// vala FIXME: pass Controller.Msg instead
-		// vala doesn't keep reference on the struct in async methods
-		// it copies only base, which is not enough to transmit the whole
-		// message.
-		try {
-			if (excl_connection != null) {
-				yield output_stream_write (excl_connection.output_stream, p);
-			} else {
-				foreach (var c in clients)
-					yield output_stream_write (c.output_stream, p);
-			}
-		} catch (GLib.Error e) {
-			warning (e.message);
-		}
-
-		return true;
-	}
-
-	private GLib.IOStream? excl_connection;
-	private int nclients;
-	List<IOStream> clients;
-
-	private bool handle_message (SpiceProtocol.Controller.Msg* msg) {
-		var v = (SpiceProtocol.Controller.MsgValue*)(msg);
-		var d = (SpiceProtocol.Controller.MsgData*)(msg);
-		unowned string str = (string)(&d.data);
-
-		switch (msg.id) {
-		case SpiceProtocol.Controller.MsgId.HOST:
-			host = str;
-			debug ("got HOST: %s".printf (str));
-			break;
-		case SpiceProtocol.Controller.MsgId.PORT:
-			port = v.value;
-			debug ("got PORT: %u".printf (port));
-			break;
-		case SpiceProtocol.Controller.MsgId.SPORT:
-			sport = v.value;
-			debug ("got SPORT: %u".printf (sport));
-			break;
-		case SpiceProtocol.Controller.MsgId.PASSWORD:
-			password = str;
-			debug ("got PASSWORD");
-			break;
-
-		case SpiceProtocol.Controller.MsgId.SECURE_CHANNELS:
-			secure_channels = str.split(",");
-			debug ("got SECURE_CHANNELS %s".printf (str));
-			break;
-
-		case SpiceProtocol.Controller.MsgId.DISABLE_CHANNELS:
-			disable_channels = str.split(",");
-			debug ("got DISABLE_CHANNELS %s".printf (str));
-			break;
-
-		case SpiceProtocol.Controller.MsgId.TLS_CIPHERS:
-			tls_ciphers = str;
-			debug ("got TLS_CIPHERS %s".printf (str));
-			break;
-		case SpiceProtocol.Controller.MsgId.CA_FILE:
-			ca_file = str;
-			debug ("got CA_FILE %s".printf (str));
-			break;
-		case SpiceProtocol.Controller.MsgId.HOST_SUBJECT:
-			host_subject = str;
-			debug ("got HOST_SUBJECT %s".printf (str));
-			break;
-
-		case SpiceProtocol.Controller.MsgId.FULL_SCREEN:
-			display_flags = (SpiceProtocol.Controller.Display)v.value;
-			debug ("got FULL_SCREEN 0x%x".printf (v.value));
-			break;
-		case SpiceProtocol.Controller.MsgId.SET_TITLE:
-			title = str;
-			debug ("got TITLE %s".printf (str));
-			break;
-		case SpiceProtocol.Controller.MsgId.ENABLE_SMARTCARD:
-			enable_smartcard = (bool)v.value;
-			debug ("got ENABLE_SMARTCARD 0x%x".printf (v.value));
-			break;
-
-		case SpiceProtocol.Controller.MsgId.CREATE_MENU:
-			menu = new SpiceCtrl.Menu.from_string (str);
-			debug ("got CREATE_MENU %s".printf (str));
-			break;
-		case SpiceProtocol.Controller.MsgId.DELETE_MENU:
-			menu = null;
-			debug ("got DELETE_MENU request");
-			break;
-
-		case SpiceProtocol.Controller.MsgId.SEND_CAD:
-			send_cad = (bool)v.value;
-			debug ("got SEND_CAD %u".printf (v.value));
-			break;
-
-		case SpiceProtocol.Controller.MsgId.HOTKEYS:
-			hotkeys = str;
-			debug ("got HOTKEYS %s".printf (str));
-			break;
-
-		case SpiceProtocol.Controller.MsgId.COLOR_DEPTH:
-			color_depth = v.value;
-			debug ("got COLOR_DEPTH %u".printf (v.value));
-			break;
-		case SpiceProtocol.Controller.MsgId.DISABLE_EFFECTS:
-			disable_effects = str.split(",");
-			debug ("got DISABLE_EFFECTS %s".printf (str));
-			break;
-
-		case SpiceProtocol.Controller.MsgId.CONNECT:
-			do_connect ();
-			debug ("got CONNECT request");
-			break;
-		case SpiceProtocol.Controller.MsgId.SHOW:
-			show ();
-			debug ("got SHOW request");
-			break;
-		case SpiceProtocol.Controller.MsgId.HIDE:
-			hide ();
-			debug ("got HIDE request");
-			break;
-		case SpiceProtocol.Controller.MsgId.ENABLE_USB:
-			enable_usbredir = (bool)v.value;
-			debug ("got ENABLE_USB %u".printf (v.value));
-			break;
-		case SpiceProtocol.Controller.MsgId.ENABLE_USB_AUTOSHARE:
-			enable_usb_autoshare = (bool)v.value;
-			debug ("got ENABLE_USB_AUTOSHARE %u".printf (v.value));
-			break;
-		case SpiceProtocol.Controller.MsgId.USB_FILTER:
-			usb_filter = str;
-			debug ("got USB_FILTER %s".printf (str));
-			break;
-		case SpiceProtocol.Controller.MsgId.PROXY:
-			proxy = str;
-			debug ("got PROXY %s".printf (str));
-			break;
-		default:
-			debug ("got unknown msg.id %u".printf (msg.id));
-			warn_if_reached ();
-			return false;
-		}
-		return true;
-	}
-
-	private async void handle_client (IOStream c) throws GLib.Error {
-		var excl = false;
-
-		debug ("new socket client, reading init header");
-
-		var p = new uint8[sizeof(SpiceProtocol.Controller.Init)];
-		var init = (SpiceProtocol.Controller.Init*)p;
-		yield input_stream_read (c.input_stream, p);
-		if (warn_if (init.base.magic != SpiceProtocol.Controller.MAGIC))
-			return;
-		if (warn_if (init.base.version != SpiceProtocol.Controller.VERSION))
-			return;
-		if (warn_if (init.base.size < sizeof (SpiceProtocol.Controller.Init)))
-			return;
-		if (warn_if (init.credentials != 0))
-			return;
-		if (warn_if (excl_connection != null))
-			return;
-
-		excl = (bool)(init.flags & SpiceProtocol.Controller.Flag.EXCLUSIVE);
-		if (excl) {
-			if (nclients > 1) {
-				warning (@"Can't make the client exclusive, there is already $nclients connected clients");
-				return;
-			}
-			excl_connection = c;
-		}
-
-		client_connected ();
-
-		for (;;) {
-			var t = new uint8[sizeof(SpiceProtocol.Controller.Msg)];
-			yield input_stream_read (c.input_stream, t);
-			var msg = (SpiceProtocol.Controller.Msg*)t;
-			debug ("new message " + msg.id.to_string () + "size " + msg.size.to_string ());
-			if (warn_if (msg.size < sizeof (SpiceProtocol.Controller.Msg)))
-				break;
-
-			if (msg.size > sizeof (SpiceProtocol.Controller.Msg)) {
-				t.resize ((int)msg.size);
-				msg = (SpiceProtocol.Controller.Msg*)t;
-				yield input_stream_read (c.input_stream, t[sizeof(SpiceProtocol.Controller.Msg):msg.size]);
-			}
-
-			handle_message (msg);
-		}
-
-		if (excl)
-			excl_connection = null;
-	}
-
-	public Controller() {
-	}
-
-	public async void listen (string? addr = null) throws GLib.Error, SpiceCtrl.Error
-	{
-		var listener = ControllerListener.new_listener (addr);
-
-		for (;;) {
-			var c = yield listener.accept_async ();
-			nclients += 1;
-			clients.append (c);
-			try {
-				yield handle_client (c);
-			} catch (GLib.Error e) {
-				warning (e.message);
-			}
-			c.close ();
-			clients.remove (c);
-			nclients -= 1;
-		}
-	}
-}
-
-} // SpiceCtrl
diff --git a/gtk/controller/custom.h b/gtk/controller/custom.h
deleted file mode 100644
index 7f849fc..0000000
--- a/gtk/controller/custom.h
+++ /dev/null
@@ -1,22 +0,0 @@
-#ifndef CUSTOM_H_
-#define CUSTOM_H_
-
-#include <glib.h>
-
-static inline gboolean g_warn_if_expr (gboolean condition,
-                                       const char *pretty_func,
-                                       const char *expression) {
-  if G_UNLIKELY(condition) {
-      g_log (G_LOG_DOMAIN,
-             G_LOG_LEVEL_CRITICAL,
-             "%s: `%s' condition reached",
-             pretty_func,
-             expression);
-    }
-
-  return condition;
-}
-
-#define g_warn_if(expr) g_warn_if_expr((expr), __PRETTY_FUNCTION__, #expr)
-
-#endif
diff --git a/gtk/controller/custom.vapi b/gtk/controller/custom.vapi
deleted file mode 100644
index a12fdec..0000000
--- a/gtk/controller/custom.vapi
+++ /dev/null
@@ -1,28 +0,0 @@
-using GLib;
-
-namespace Custom {
-
-	[CCode (cname = "g_warn_if", cheader_filename = "custom.h")]
-	public bool warn_if(bool condition);
-}
-
-namespace Spice {
-
-	[CCode (cname = "GObject", ref_function = "g_object_ref", unref_function = "g_object_unref", free_function = "")]
-	class ControllerListener {
-		[CCode (cname = "spice_controller_listener_new", cheader_filename = "spice-controller-listener.h")]
-		public static ControllerListener new_listener (string addr) throws GLib.Error;
-
-		[CCode (cname = "spice_controller_listener_accept_async", cheader_filename = "spice-controller-listener.h")]
-		public async unowned GLib.IOStream accept_async (GLib.Cancellable? cancellable = null, out GLib.Object? source_object = null) throws GLib.Error;
-	}
-
-	[CCode (cname = "GObject", ref_function = "g_object_ref", unref_function = "g_object_unref", free_function = "")]
-	class ForeignMenuListener {
-		[CCode (cname = "spice_foreign_menu_listener_new", cheader_filename = "spice-foreign-menu-listener.h")]
-		public static ForeignMenuListener new_listener (string addr) throws GLib.Error;
-
-		[CCode (cname = "spice_foreign_menu_listener_accept_async", cheader_filename = "spice-foreign-menu-listener.h")]
-		public async unowned GLib.IOStream accept_async (GLib.Cancellable? cancellable = null, out GLib.Object? source_object = null) throws GLib.Error;
-	}
-}
diff --git a/gtk/controller/dump.c b/gtk/controller/dump.c
deleted file mode 100644
index 831a1d7..0000000
--- a/gtk/controller/dump.c
+++ /dev/null
@@ -1,118 +0,0 @@
-/* Copyright (C) 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 <stdio.h>
-#include <stdint.h>
-
-#ifdef WIN32
-#include <windows.h>
-#else
-#include <sys/socket.h>
-#ifdef HAVE_SYS_TYPES_H
-#include <sys/types.h>
-#endif
-#include <sys/un.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <string.h>
-#include <errno.h>
-#endif
-
-#include "spice-controller.h"
-
-SpiceCtrlController *ctrl = NULL;
-SpiceCtrlForeignMenu *menu = NULL;
-GMainLoop *loop = NULL;
-
-void signaled (GObject *gobject, const gchar *signal_name)
-{
-    g_message ("signaled: %s", signal_name);
-}
-
-void notified (GObject *gobject, GParamSpec *pspec,
-               gpointer user_data)
-{
-    GValue value = { 0, };
-    GValue strvalue = { 0, };
-
-    g_return_if_fail (gobject != NULL);
-    g_return_if_fail (pspec != NULL);
-
-    g_value_init (&value, pspec->value_type);
-    g_value_init (&strvalue, G_TYPE_STRING);
-    g_object_get_property (gobject, pspec->name, &value);
-
-    if (pspec->value_type == G_TYPE_STRV) {
-      gchar** p = (gchar **)g_value_get_boxed (&value);
-      g_message ("notify::%s == ", pspec->name);
-      while (*p)
-        g_message ("%s", *p++);
-    } else if (G_TYPE_IS_OBJECT(pspec->value_type)) {
-      GObject *o = g_value_get_object (&value);
-      g_message ("notify::%s == %s", pspec->name, o ? G_OBJECT_TYPE_NAME (o) : "null");
-    } else {
-      g_value_transform (&value, &strvalue);
-      g_message ("notify::%s  = %s", pspec->name, g_value_get_string (&strvalue));
-    }
-
-    g_value_unset (&value);
-    g_value_unset (&strvalue);
-}
-
-void connect_signals (gpointer obj)
-{
-    guint i, n_ids = 0;
-    guint *ids = NULL;
-    GType type = G_OBJECT_TYPE (obj);
-
-    ids = g_signal_list_ids (type, &n_ids);
-    for (i = 0; i < n_ids; i++) {
-        const gchar *name = g_signal_name (ids[i]);
-        g_signal_connect (obj, name, G_CALLBACK (signaled), (gpointer)name);
-    }
-}
-
-int main (int argc, char *argv[])
-{
-#if !GLIB_CHECK_VERSION(2,36,0)
-    g_type_init ();
-#endif
-    loop = g_main_loop_new (NULL, FALSE);
-
-    if (argc > 1 && g_str_equal(argv[1], "--menu")) {
-        menu = spice_ctrl_foreign_menu_new ();
-        g_signal_connect (menu, "notify", G_CALLBACK (notified), NULL);
-        connect_signals (menu);
-
-        spice_ctrl_foreign_menu_listen (menu, NULL, NULL, NULL);
-    } else {
-        ctrl = spice_ctrl_controller_new ();
-        g_signal_connect (ctrl, "notify", G_CALLBACK (notified), NULL);
-        connect_signals (ctrl);
-
-        spice_ctrl_controller_listen (ctrl, NULL, NULL, NULL);
-    }
-
-    g_main_loop_run (loop);
-
-    if (ctrl != NULL)
-        g_object_unref (ctrl);
-    if (menu != NULL)
-        g_object_unref (menu);
-
-    return 0;
-}
diff --git a/gtk/controller/foreign-menu.vala b/gtk/controller/foreign-menu.vala
deleted file mode 100644
index 005955a..0000000
--- a/gtk/controller/foreign-menu.vala
+++ /dev/null
@@ -1,197 +0,0 @@
-// Copyright (C) 2012 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/>.
-
-using Custom;
-
-namespace SpiceCtrl {
-
-public class ForeignMenu: Object {
-
-	public Menu menu { get; private set; }
-    public string title { get; private set; }
-
-	public signal void client_connected ();
-
-	private int nclients;
-	private List<IOStream> clients;
-
-	public ForeignMenu() {
-		menu = new Menu ();
-	}
-
-	public void menu_item_click_msg (int32 item_id) {
-		debug ("clicked id: %d".printf (item_id));
-
-		var msg = SpiceProtocol.ForeignMenu.Event ();
-		msg.base.size = (uint32)sizeof (SpiceProtocol.ForeignMenu.Event);
-		msg.base.id = SpiceProtocol.ForeignMenu.MsgId.ITEM_EVENT;
-		msg.id = item_id;
-		msg.action = SpiceProtocol.ForeignMenu.EventType.CLICK;
-
-		unowned uint8[] p = ((uint8[])(&msg))[0:msg.base.size];
-		send_msg.begin (p);
-	}
-
-	public void menu_item_checked_msg (int32 item_id, bool checked = true) {
-		debug ("%schecked id: %d".printf (checked ? "" : "un", item_id));
-
-		var msg = SpiceProtocol.ForeignMenu.Event ();
-		msg.base.size = (uint32)sizeof (SpiceProtocol.ForeignMenu.Event);
-		msg.base.id = SpiceProtocol.ForeignMenu.MsgId.ITEM_EVENT;
-		msg.id = item_id;
-		msg.action = checked ?
-			SpiceProtocol.ForeignMenu.EventType.CHECKED :
-			SpiceProtocol.ForeignMenu.EventType.UNCHECKED;
-
-		unowned uint8[] p = ((uint8[])(&msg))[0:msg.base.size];
-		send_msg.begin (p);
-	}
-
-	public void app_activated_msg (bool activated = true) {
-		var msg = SpiceProtocol.ForeignMenu.Msg ();
-		msg.size = (uint32)sizeof (SpiceProtocol.ForeignMenu.Event);
-		msg.id = activated ?
-			SpiceProtocol.ForeignMenu.MsgId.APP_ACTIVATED :
-			SpiceProtocol.ForeignMenu.MsgId.APP_DEACTIVATED;
-
-		unowned uint8[] p = ((uint8[])(&msg))[0:msg.size];
-		send_msg.begin (p);
-	}
-
-	public async bool send_msg (owned uint8[] p) throws GLib.Error {
-		// vala FIXME: pass Controller.Msg instead
-		// vala doesn't keep reference on the struct in async methods
-		// it copies only base, which is not enough to transmit the whole
-		// message.
-		try {
-			foreach (var c in clients) {
-				yield output_stream_write (c.output_stream, p);
-			}
-		} catch (GLib.Error e) {
-			warning (e.message);
-		}
-
-		return true;
-	}
-
-	SpiceProtocol.Controller.MenuFlags get_menu_flags (uint32 type) {
-		SpiceProtocol.Controller.MenuFlags flags = 0;
-
-		if ((SpiceProtocol.ForeignMenu.MenuFlags.CHECKED & type) != 0)
-			flags |= SpiceProtocol.Controller.MenuFlags.CHECKED;
-		if ((SpiceProtocol.ForeignMenu.MenuFlags.DIM & type) != 0)
-			flags |= SpiceProtocol.Controller.MenuFlags.GRAYED;
-
-		return flags;
-	}
-
-	private bool handle_message (SpiceProtocol.ForeignMenu.Msg* msg) {
-		switch (msg.id) {
-		case SpiceProtocol.ForeignMenu.MsgId.SET_TITLE:
-			var t = (SpiceProtocol.ForeignMenu.SetTitle*)(msg);
-			title = t.string;
-			break;
-		case SpiceProtocol.ForeignMenu.MsgId.ADD_ITEM:
-			var i = (SpiceProtocol.ForeignMenu.AddItem*)(msg);
-			debug ("add id:%u type:%u position:%u title:%s", i.id, i.type, i.position, i.string);
-			menu.items.append (new MenuItem ((int)i.id, i.string, get_menu_flags (i.type)));
-			notify_property ("menu");
-			break;
-		case SpiceProtocol.ForeignMenu.MsgId.MODIFY_ITEM:
-			debug ("deprecated: modify item");
-			break;
-		case SpiceProtocol.ForeignMenu.MsgId.REMOVE_ITEM:
-			var i = (SpiceProtocol.ForeignMenu.RmItem*)(msg);
-			debug ("not implemented: remove id:%u".printf (i.id));
-			break;
-		case SpiceProtocol.ForeignMenu.MsgId.CLEAR:
-			menu = new Menu ();
-			break;
-		default:
-			warn_if_reached ();
-			return false;
-		}
-		return true;
-	}
-
-	private async void handle_client (IOStream c) throws GLib.Error {
-		debug ("new socket client, reading init header");
-
-		var p = new uint8[sizeof(SpiceProtocol.ForeignMenu.InitHeader)];
-		var header = (SpiceProtocol.ForeignMenu.InitHeader*)p;
-		yield input_stream_read (c.input_stream, p);
-		if (warn_if (header.magic != SpiceProtocol.ForeignMenu.MAGIC))
-			return;
-		if (warn_if (header.version != SpiceProtocol.ForeignMenu.VERSION))
-			return;
-		if (warn_if (header.size < sizeof (SpiceProtocol.ForeignMenu.Init)))
-			return;
-
-		var cp = new uint8[sizeof(uint64)];
-		yield input_stream_read (c.input_stream, cp);
-		uint64 credentials = *(uint64*)cp;
-		if (warn_if (credentials != 0))
-			return;
-
-		var title_size = header.size - sizeof(SpiceProtocol.ForeignMenu.Init);
-		var title = new uint8[title_size + 1];
-		yield c.input_stream.read_async (title[0:title_size]);
-		this.title = (string)title;
-
-		client_connected ();
-
-		for (;;) {
-			var t = new uint8[sizeof(SpiceProtocol.ForeignMenu.Msg)];
-			yield input_stream_read (c.input_stream, t);
-			var msg = (SpiceProtocol.ForeignMenu.Msg*)t;
-			debug ("new message " + msg.id.to_string () + "size " + msg.size.to_string ());
-
-			if (warn_if (msg.size < sizeof (SpiceProtocol.ForeignMenu.Msg)))
-				break;
-
-			if (msg.size > sizeof (SpiceProtocol.ForeignMenu.Msg)) {
-				t.resize ((int)msg.size);
-				msg = (SpiceProtocol.ForeignMenu.Msg*)t;
-
-				yield input_stream_read (c.input_stream, t[sizeof(SpiceProtocol.ForeignMenu.Msg):msg.size]);
-			}
-
-			handle_message (msg);
-		}
-
-	}
-
-	public async void listen (string? addr = null) throws GLib.Error, SpiceCtrl.Error
-	{
-		var listener = Spice.ForeignMenuListener.new_listener (addr);
-
-		for (;;) {
-			var c = yield listener.accept_async ();
-			nclients += 1;
-			clients.append (c);
-			try {
-				yield handle_client (c);
-			} catch (GLib.Error e) {
-				warning (e.message);
-			}
-			c.close ();
-			clients.remove (c);
-			nclients -= 1;
-		}
-	}
-
-}
-
-} // SpiceCtrl
diff --git a/gtk/controller/gio-windows-2.0.vapi b/gtk/controller/gio-windows-2.0.vapi
deleted file mode 100644
index a09cfe8..0000000
--- a/gtk/controller/gio-windows-2.0.vapi
+++ /dev/null
@@ -1,30 +0,0 @@
-/* gio-windows-2.0.vapi generated by vapigen. */
-/* NOT YET UPSTREAM: https://bugzilla.gnome.org/show_bug.cgi?id=650052 */
-
-[CCode (cprefix = "GLib", lower_case_cprefix = "glib_")]
-namespace GLib {
-	[CCode (cheader_filename = "gio/gwin32inputstream.h")]
-	public class Win32InputStream : GLib.InputStream {
-		public weak GLib.InputStream parent_instance;
-		[CCode (cname = "g_win32_input_stream_new", type = "GInputStream*", has_construct_function = false)]
-		public Win32InputStream (void* handle, bool close_handle);
-		[CCode (cname = "g_win32_input_stream_get_close_handle")]
-		public static bool get_close_handle (GLib.Win32InputStream stream);
-		[CCode (cname = "g_win32_input_stream_get_handle")]
-		public static void* get_handle (GLib.Win32InputStream stream);
-		[CCode (cname = "g_win32_input_stream_set_close_handle")]
-		public static void set_close_handle (GLib.Win32InputStream stream, bool close_handle);
-	}
-	[CCode (cheader_filename = "gio/gwin32inputstream.h")]
-	public class Win32OutputStream : GLib.OutputStream {
-		public weak GLib.OutputStream parent_instance;
-		[CCode (cname = "g_win32_output_stream_new", type = "GOutputStream*", has_construct_function = false)]
-		public Win32OutputStream (void* handle, bool close_handle);
-		[CCode (cname = "g_win32_output_stream_get_close_handle")]
-		public static bool get_close_handle (GLib.Win32OutputStream stream);
-		[CCode (cname = "g_win32_output_stream_get_handle")]
-		public static void* get_handle (GLib.Win32OutputStream stream);
-		[CCode (cname = "g_win32_output_stream_set_close_handle")]
-		public static void set_close_handle (GLib.Win32OutputStream stream, bool close_handle);
-	}
-}
diff --git a/gtk/controller/menu.vala b/gtk/controller/menu.vala
deleted file mode 100644
index 7e8fc16..0000000
--- a/gtk/controller/menu.vala
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright (C) 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/>.
-
-using GLib;
-using Custom;
-using SpiceProtocol.Controller;
-
-namespace SpiceCtrl {
-
-public class MenuItem: Object {
-
-	public Menu submenu;
-	public int parent_id;
-	public int id;
-	public string text;
-	public string accel;
-	public SpiceProtocol.Controller.MenuFlags flags;
-
-	public MenuItem (int id, string text, SpiceProtocol.Controller.MenuFlags flags) {
-		this.id = id;
-		this.text = text;
-		this.flags = flags;
-	}
-
-	public MenuItem.from_string (string str) throws SpiceCtrl.Error {
-		var params = str.split (SpiceProtocol.Controller.MENU_PARAM_DELIMITER);
-		if (warn_if (params.length != 5))
-			throw new SpiceCtrl.Error.VALUE(""); /* Vala: why is it mandatory to give a string? */
-		parent_id = int.parse (params[0]);
-		id = int.parse (params[1]);
-		var textaccel = params[2].split ("\t");
-		text = textaccel[0];
-		if (textaccel.length > 1)
-			accel = textaccel[1];
-		flags = (SpiceProtocol.Controller.MenuFlags)int.parse (params[3]);
-
-		submenu = new Menu ();
-	}
-
-	public string to_string () {
-		var sub = submenu.to_string ();
-		var str = @"pid: $parent_id, id: $id, text: \"$text\", flags: $flags";
-		foreach (var l in sub.to_string ().split ("\n")) {
-			if (l == "")
-				continue;
-			str += @"\n    $l";
-		}
-		return str;
-	}
-}
-
-public class Menu: Object {
-
-	public List<MenuItem> items;
-
-	public Menu? find_id (int id) {
-		if (id == 0)
-			return this;
-
-		foreach (var item in items) {
-			if (item.id == id)
-				return item.submenu;
-
-			var menu = item.submenu.find_id (id);
-			if (menu != null)
-				return menu;
-		}
-
-		return null;
-	}
-
-	public Menu.from_string (string str) {
-		foreach (var itemstr  in str.split (SpiceProtocol.Controller.MENU_ITEM_DELIMITER)) {
-			try {
-				if (itemstr.length == 0)
-					continue;
-				var item = new MenuItem.from_string (itemstr);
-				var parent = find_id (item.parent_id);
-				if (parent == null)
-					throw new SpiceCtrl.Error.VALUE("Invalid parent menu id");
-				parent.items.append (item);
-			} catch (SpiceCtrl.Error e) {
-				warning (e.message);
-			}
-		}
-	}
-
-	public string to_string () {
-		var str = "";
-		foreach (var i in items)
-			str += @"\n$i";
-		return str;
-	}
-}
-
-} // SpiceCtrl
diff --git a/gtk/controller/namedpipe.c b/gtk/controller/namedpipe.c
deleted file mode 100644
index 5312218..0000000
--- a/gtk/controller/namedpipe.c
+++ /dev/null
@@ -1,270 +0,0 @@
-/*
-   Copyright (C) 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 "namedpipe.h"
-
-#include <windows.h>
-#include <stdio.h>
-#include <conio.h>
-#include <tchar.h>
-
-static void     spice_named_pipe_initable_iface_init (GInitableIface  *iface);
-static gboolean spice_named_pipe_initable_init       (GInitable       *initable,
-                                                      GCancellable    *cancellable,
-                                                      GError         **error);
-
-G_DEFINE_TYPE_WITH_CODE (SpiceNamedPipe, spice_named_pipe, G_TYPE_OBJECT,
-			 G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
-						spice_named_pipe_initable_iface_init));
-
-enum
-{
-  PROP_0,
-  PROP_NAME,
-  PROP_HANDLE,
-};
-
-struct _SpiceNamedPipePrivate
-{
-  gchar *               name;
-  GError *              construct_error;
-  guint                 inited : 1;
-  HANDLE                handle;
-};
-
-static void
-spice_named_pipe_finalize (GObject *object)
-{
-  SpiceNamedPipe *np = SPICE_NAMED_PIPE (object);
-
-  g_clear_error (&np->priv->construct_error);
-
-  g_free (np->priv->name);
-  np->priv->name = NULL;
-
-  if (np->priv->handle)
-    {
-      CloseHandle (np->priv->handle);
-      np->priv->handle = NULL;
-    }
-
-  if (G_OBJECT_CLASS (spice_named_pipe_parent_class)->finalize)
-    G_OBJECT_CLASS (spice_named_pipe_parent_class)->finalize (object);
-}
-
-#define DEFAULT_PIPE_BUF_SIZE 4096
-
-static void
-spice_named_pipe_constructed (GObject *object)
-{
-  SpiceNamedPipe *np = SPICE_NAMED_PIPE (object);
-
-  if (np->priv->handle)
-    /* TODO: find a way to ensure user provided handle is a named
-       pipe, in overlapped mode */
-    goto end;
-
-  np->priv->handle = CreateNamedPipe (np->priv->name,
-      PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
-      PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
-      PIPE_UNLIMITED_INSTANCES,
-      DEFAULT_PIPE_BUF_SIZE, DEFAULT_PIPE_BUF_SIZE,
-      0, NULL);
-
-  if (np->priv->handle == INVALID_HANDLE_VALUE)
-    {
-      int errsv = GetLastError ();
-      gchar *emsg = g_win32_error_message (errsv);
-
-      g_set_error (&np->priv->construct_error,
-                   G_IO_ERROR,
-                   g_io_error_from_win32_error (errsv),
-                   "Error CreateNamedPipe(): %s",
-                   emsg);
-
-      g_free (emsg);
-      return;
-    }
-
-  /* TODO: we could have a client backlog by creating many pipes, the
-     maximum number of outstanding connections.. or we could just let
-     the named_pipe_listener take multiple NamedPipe instances */
-end:
-  g_assert (np->priv->handle != INVALID_HANDLE_VALUE);
-  return;
-}
-
-static void
-spice_named_pipe_get_property (GObject    *object,
-                               guint       prop_id,
-                               GValue     *value,
-                               GParamSpec *pspec)
-{
-  SpiceNamedPipe *np = SPICE_NAMED_PIPE (object);
-
-  switch (prop_id)
-    {
-      case PROP_NAME:
-        g_value_set_string (value, np->priv->name);
-        break;
-      case PROP_HANDLE:
-        g_value_set_pointer (value, np->priv->handle);
-        break;
-      default:
-        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-    }
-}
-
-static void
-spice_named_pipe_set_property (GObject      *object,
-                               guint         prop_id,
-                               const GValue *value,
-                               GParamSpec   *pspec)
-{
-  SpiceNamedPipe *np = SPICE_NAMED_PIPE (object);
-
-  switch (prop_id)
-    {
-      case PROP_NAME:
-        g_free (np->priv->name);
-        np->priv->name = g_value_dup_string (value);
-        break;
-      case PROP_HANDLE:
-        np->priv->handle = g_value_get_pointer (value);
-        break;
-      default:
-        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-    }
-}
-
-static void
-spice_named_pipe_class_init (SpiceNamedPipeClass *klass)
-{
-  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
-
-  g_type_class_add_private (klass, sizeof (SpiceNamedPipePrivate));
-
-  gobject_class->set_property = spice_named_pipe_set_property;
-  gobject_class->get_property = spice_named_pipe_get_property;
-  gobject_class->finalize = spice_named_pipe_finalize;
-  gobject_class->constructed = spice_named_pipe_constructed;
-
-  g_object_class_install_property (gobject_class, PROP_NAME,
-				   g_param_spec_string ("name",
-                                                        "Pipe Name",
-                                                        "The NamedPipe name",
-                                                        NULL,
-                                                        G_PARAM_CONSTRUCT_ONLY |
-                                                        G_PARAM_READWRITE |
-                                                        G_PARAM_STATIC_STRINGS));
-
-  g_object_class_install_property (gobject_class, PROP_HANDLE,
-                                   g_param_spec_pointer ("handle",
-                                                         "Pipe handle",
-                                                         "The pipe handle",
-                                                         G_PARAM_CONSTRUCT_ONLY |
-                                                         G_PARAM_READWRITE |
-                                                         G_PARAM_STATIC_STRINGS));
-}
-
-static void
-spice_named_pipe_init (SpiceNamedPipe *np)
-{
-  np->priv = G_TYPE_INSTANCE_GET_PRIVATE (np,
-                                          SPICE_TYPE_NAMED_PIPE,
-                                          SpiceNamedPipePrivate);
-}
-
-static gboolean
-spice_named_pipe_initable_init (GInitable *initable,
-                                GCancellable *cancellable,
-                                GError  **error)
-{
-  SpiceNamedPipe  *np;
-
-  g_return_val_if_fail (SPICE_IS_NAMED_PIPE (initable), FALSE);
-
-  np = SPICE_NAMED_PIPE (initable);
-
-  if (cancellable != NULL)
-    {
-      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
-                           "Cancellable initialization not supported");
-      return FALSE;
-    }
-
-  np->priv->inited = TRUE;
-
-  if (np->priv->construct_error)
-    {
-      if (error)
-	*error = g_error_copy (np->priv->construct_error);
-      return FALSE;
-    }
-
-
-  return TRUE;
-}
-
-static void
-spice_named_pipe_initable_iface_init (GInitableIface *iface)
-{
-  iface->init = spice_named_pipe_initable_init;
-}
-
-SpiceNamedPipe *
-spice_named_pipe_new (const gchar *name, GError **error)
-{
-  return SPICE_NAMED_PIPE (g_initable_new (SPICE_TYPE_NAMED_PIPE,
-                                           NULL, error,
-                                           "name", name,
-                                           NULL));
-}
-
-void *
-spice_named_pipe_get_handle (SpiceNamedPipe *namedpipe)
-{
-  g_return_val_if_fail (SPICE_IS_NAMED_PIPE (namedpipe), NULL);
-
-  return namedpipe->priv->handle;
-}
-
-gboolean
-spice_named_pipe_close (SpiceNamedPipe *np,
-                        GError **error)
-{
-  BOOL res;
-
-  g_return_val_if_fail (SPICE_IS_NAMED_PIPE (np), FALSE);
-
-  res = CloseHandle (np->priv->handle);
-  np->priv->handle = NULL;
-  if (!res)
-    {
-      int errsv = GetLastError ();
-      gchar *emsg = g_win32_error_message (errsv);
-
-      g_set_error (error, G_IO_ERROR,
-		   g_io_error_from_win32_error (errsv),
-		   "Error closing handle: %s",
-		   emsg);
-      g_free (emsg);
-      return FALSE;
-    }
-
-  return TRUE;
-}
diff --git a/gtk/controller/namedpipe.h b/gtk/controller/namedpipe.h
deleted file mode 100644
index e0e873b..0000000
--- a/gtk/controller/namedpipe.h
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
-   Copyright (C) 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/>.
-*/
-#ifndef __NAMED_PIPE_H__
-#define __NAMED_PIPE_H__
-
-#include <gio/gio.h>
-
-G_BEGIN_DECLS
-
-#define SPICE_TYPE_NAMED_PIPE                       (spice_named_pipe_get_type ())
-#define SPICE_NAMED_PIPE(inst)                      (G_TYPE_CHECK_INSTANCE_CAST ((inst),                     \
-                                                     SPICE_TYPE_NAMED_PIPE, SpiceNamedPipe))
-#define SPICE_NAMED_PIPE_CLASS(class)               (G_TYPE_CHECK_CLASS_CAST ((class),                       \
-                                                     SPICE_TYPE_NAMED_PIPE, SpiceNamedPipeClass))
-#define SPICE_IS_NAMED_PIPE(inst)                   (G_TYPE_CHECK_INSTANCE_TYPE ((inst),                     \
-                                                     SPICE_TYPE_NAMED_PIPE))
-#define SPICE_IS_NAMED_PIPE_CLASS(class)            (G_TYPE_CHECK_CLASS_TYPE ((class),                       \
-                                                     SPICE_TYPE_NAMED_PIPE))
-#define SPICE_NAMED_PIPE_GET_CLASS(inst)            (G_TYPE_INSTANCE_GET_CLASS ((inst),                      \
-                                                     SPICE_TYPE_NAMED_PIPE, SpiceNamedPipeClass))
-
-typedef struct _SpiceNamedPipe                       SpiceNamedPipe;
-typedef struct _SpiceNamedPipePrivate                SpiceNamedPipePrivate;
-typedef struct _SpiceNamedPipeClass                  SpiceNamedPipeClass;
-
-struct _SpiceNamedPipeClass
-{
-  GObjectClass parent_class;
-};
-
-struct _SpiceNamedPipe
-{
-  GObject parent_instance;
-  SpiceNamedPipePrivate *priv;
-};
-
-GType            spice_named_pipe_get_type  (void) G_GNUC_CONST;
-
-SpiceNamedPipe * spice_named_pipe_new       (const gchar *name, GError **error);
-void *           spice_named_pipe_get_handle(SpiceNamedPipe *namedpipe);
-gboolean         spice_named_pipe_close     (SpiceNamedPipe *namedpipe,
-                                             GError **error);
-G_END_DECLS
-
-#endif /* __NAMED_PIPE_H__ */
diff --git a/gtk/controller/namedpipeconnection.c b/gtk/controller/namedpipeconnection.c
deleted file mode 100644
index 3173b61..0000000
--- a/gtk/controller/namedpipeconnection.c
+++ /dev/null
@@ -1,245 +0,0 @@
-/*
-   Copyright (C) 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 "namedpipeconnection.h"
-
-#include <windows.h>
-#include <stdio.h>
-#include <conio.h>
-#include <tchar.h>
-
-#include <gio/gwin32inputstream.h>
-#include <gio/gwin32outputstream.h>
-
-G_DEFINE_TYPE (SpiceNamedPipeConnection, spice_named_pipe_connection,
-               G_TYPE_IO_STREAM)
-
-enum
-{
-  PROP_0,
-  PROP_NAMED_PIPE,
-};
-
-struct _SpiceNamedPipeConnectionPrivate
-{
-  GInputStream   *input_stream;
-  GOutputStream  *output_stream;
-  SpiceNamedPipe *namedpipe;
-  gboolean       in_dispose;
-};
-
-static void
-spice_named_pipe_connection_init (SpiceNamedPipeConnection *connection)
-{
-  connection->priv = G_TYPE_INSTANCE_GET_PRIVATE (connection,
-                                                  SPICE_TYPE_NAMED_PIPE_CONNECTION,
-                                                  SpiceNamedPipeConnectionPrivate);
-}
-
-static void
-spice_named_pipe_connection_get_property (GObject    *object,
-                                          guint       prop_id,
-                                          GValue     *value,
-                                          GParamSpec *pspec)
-{
-  SpiceNamedPipeConnection *c = SPICE_NAMED_PIPE_CONNECTION (object);
-
-  switch (prop_id)
-    {
-      case PROP_NAMED_PIPE:
-        g_return_if_fail (c->priv->namedpipe == NULL);
-        g_value_set_object (value, c->priv->namedpipe);
-        break;
-      default:
-        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-    }
-}
-
-static void
-spice_named_pipe_connection_set_property (GObject      *object,
-                                          guint         prop_id,
-                                          const GValue *value,
-                                          GParamSpec   *pspec)
-{
-  SpiceNamedPipeConnection *c = SPICE_NAMED_PIPE_CONNECTION (object);
-
-  switch (prop_id)
-    {
-      case PROP_NAMED_PIPE:
-        c->priv->namedpipe = g_value_get_object (value);
-        break;
-      default:
-        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-    }
-}
-
-static GInputStream *
-spice_named_pipe_connection_get_input_stream (GIOStream *io_stream)
-{
-  SpiceNamedPipeConnection *c = SPICE_NAMED_PIPE_CONNECTION (io_stream);
-  HANDLE h = spice_named_pipe_get_handle (c->priv->namedpipe);
-
-  g_return_val_if_fail (h != NULL, NULL);
-
-  if (c->priv->input_stream == NULL)
-    c->priv->input_stream = g_win32_input_stream_new (h, FALSE);
-
-  return c->priv->input_stream;
-}
-
-static GOutputStream *
-spice_named_pipe_connection_get_output_stream (GIOStream *io_stream)
-{
-  SpiceNamedPipeConnection *c = SPICE_NAMED_PIPE_CONNECTION (io_stream);
-  HANDLE h = spice_named_pipe_get_handle (c->priv->namedpipe);
-
-  g_return_val_if_fail (h != NULL, NULL);
-
-  if (c->priv->output_stream == NULL)
-    c->priv->output_stream = g_win32_output_stream_new (h, FALSE);
-
-  return c->priv->output_stream;
-}
-
-static void
-spice_named_pipe_connection_dispose (GObject *object)
-{
-  SpiceNamedPipeConnection *c = SPICE_NAMED_PIPE_CONNECTION (object);
-
-  c->priv->in_dispose = TRUE;
-
-  if (G_OBJECT_CLASS (spice_named_pipe_connection_parent_class)->dispose)
-    G_OBJECT_CLASS (spice_named_pipe_connection_parent_class)->dispose (object);
-
-  c->priv->in_dispose = FALSE;
-}
-
-static void
-spice_named_pipe_connection_finalize (GObject *object)
-{
-  SpiceNamedPipeConnection *c = SPICE_NAMED_PIPE_CONNECTION (object);
-
-  if (c->priv->output_stream)
-    {
-      g_object_unref (c->priv->output_stream);
-      c->priv->output_stream = NULL;
-    }
-
-  if (c->priv->input_stream)
-    {
-      g_object_unref (c->priv->input_stream);
-      c->priv->input_stream = NULL;
-    }
-
-  g_object_unref (c->priv->namedpipe);
-
-  if (G_OBJECT_CLASS (spice_named_pipe_connection_parent_class)->finalize)
-    G_OBJECT_CLASS (spice_named_pipe_connection_parent_class)->finalize (object);
-}
-
-static gboolean
-spice_named_pipe_connection_close (GIOStream     *stream,
-                                   GCancellable  *cancellable,
-                                   GError       **error)
-{
-  SpiceNamedPipeConnection *c = SPICE_NAMED_PIPE_CONNECTION (stream);
-
-  if (c->priv->output_stream)
-    g_output_stream_close (c->priv->output_stream, cancellable, NULL);
-  if (c->priv->input_stream)
-    g_input_stream_close (c->priv->input_stream, cancellable, NULL);
-
-  /* Don't close the underlying socket if this is being called
-   * as part of dispose(); when destroying the GSocketConnection,
-   * we only want to close the socket if we're holding the last
-   * reference on it, and in that case it will close itself when
-   * we unref namedpipe in finalize().
-   */
-  if (c->priv->in_dispose)
-    return TRUE;
-
-  return spice_named_pipe_close (c->priv->namedpipe, error);
-}
-
-static void
-spice_named_pipe_connection_close_async (GIOStream           *stream,
-                                         int                  io_priority,
-                                         GCancellable        *cancellable,
-                                         GAsyncReadyCallback  callback,
-                                         gpointer             user_data)
-{
-  GSimpleAsyncResult *res;
-  GIOStreamClass *class;
-  GError *error;
-
-  class = G_IO_STREAM_GET_CLASS (stream);
-
-  /* namedpipe close is not blocking, just do it! */
-  error = NULL;
-  if (class->close_fn &&
-      !class->close_fn (stream, cancellable, &error))
-    {
-      g_simple_async_report_take_gerror_in_idle (G_OBJECT (stream),
-                                                 callback, user_data,
-                                                 error);
-      return;
-    }
-
-  res = g_simple_async_result_new (G_OBJECT (stream),
-				   callback,
-				   user_data,
-				   spice_named_pipe_connection_close_async);
-  g_simple_async_result_complete_in_idle (res);
-  g_object_unref (res);
-}
-
-static gboolean
-spice_named_pipe_connection_close_finish (GIOStream     *stream,
-                                          GAsyncResult  *result,
-                                          GError       **error)
-{
-  return TRUE;
-}
-
-static void
-spice_named_pipe_connection_class_init (SpiceNamedPipeConnectionClass *klass)
-{
-  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
-  GIOStreamClass *stream_class = G_IO_STREAM_CLASS (klass);
-
-  g_type_class_add_private (klass, sizeof (SpiceNamedPipeConnectionPrivate));
-
-  gobject_class->set_property = spice_named_pipe_connection_set_property;
-  gobject_class->get_property = spice_named_pipe_connection_get_property;
-  gobject_class->dispose = spice_named_pipe_connection_dispose;
-  gobject_class->finalize = spice_named_pipe_connection_finalize;
-
-  stream_class->get_input_stream = spice_named_pipe_connection_get_input_stream;
-  stream_class->get_output_stream = spice_named_pipe_connection_get_output_stream;
-  stream_class->close_fn = spice_named_pipe_connection_close;
-  stream_class->close_async = spice_named_pipe_connection_close_async;
-  stream_class->close_finish = spice_named_pipe_connection_close_finish;
-
-  g_object_class_install_property (gobject_class, PROP_NAMED_PIPE,
-                                   g_param_spec_object ("namedpipe",
-                                                        "NamedPipe",
-                                                        "The associated NamedPipe",
-                                                        SPICE_TYPE_NAMED_PIPE,
-                                                        G_PARAM_CONSTRUCT_ONLY |
-                                                        G_PARAM_READWRITE |
-                                                        G_PARAM_STATIC_STRINGS));
-}
diff --git a/gtk/controller/namedpipeconnection.h b/gtk/controller/namedpipeconnection.h
deleted file mode 100644
index 86f0be6..0000000
--- a/gtk/controller/namedpipeconnection.h
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
-   Copyright (C) 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/>.
-*/
-#ifndef __NAMED_PIPE_CONNECTION_H__
-#define __NAMED_PIPE_CONNECTION_H__
-
-#include <gio/gio.h>
-#include "namedpipe.h"
-
-G_BEGIN_DECLS
-
-#define SPICE_TYPE_NAMED_PIPE_CONNECTION                     (spice_named_pipe_connection_get_type ())
-#define SPICE_NAMED_PIPE_CONNECTION(inst)                    (G_TYPE_CHECK_INSTANCE_CAST ((inst), \
-                                                              SPICE_TYPE_NAMED_PIPE_CONNECTION, SpiceNamedPipeConnection))
-#define SPICE_NAMED_PIPE_CONNECTION_CLASS(class)             (G_TYPE_CHECK_CLASS_CAST ((class),                       \
-                                                              SPICE_TYPE_NAMED_PIPE_CONNECTION, SpiceNamedPipeConnectionClass))
-#define SPICE_IS_NAMED_PIPE_CONNECTION(inst)                 (G_TYPE_CHECK_INSTANCE_TYPE ((inst),                     \
-                                                              SPICE_TYPE_NAMED_PIPE_CONNECTION))
-#define SPICE_IS_NAMED_PIPE_CONNECTION_CLASS(class)          (G_TYPE_CHECK_CLASS_TYPE ((class),                       \
-                                                              SPICE_TYPE_NAMED_PIPE_CONNECTION))
-#define SPICE_NAMED_PIPE_CONNECTION_GET_CLASS(inst)          (G_TYPE_INSTANCE_GET_CLASS ((inst),                      \
-                                                              SPICE_TYPE_NAMED_PIPE_CONNECTION, SpiceNamedPipeConnectionClass))
-
-typedef struct _SpiceNamedPipeConnection                     SpiceNamedPipeConnection;
-typedef struct _SpiceNamedPipeConnectionPrivate              SpiceNamedPipeConnectionPrivate;
-typedef struct _SpiceNamedPipeConnectionClass                SpiceNamedPipeConnectionClass;
-
-struct _SpiceNamedPipeConnectionClass
-{
-  GIOStreamClass parent_class;
-};
-
-struct _SpiceNamedPipeConnection
-{
-  GIOStream parent_instance;
-  SpiceNamedPipeConnectionPrivate *priv;
-};
-
-GType    spice_named_pipe_connection_get_type                (void) G_GNUC_CONST;
-
-G_END_DECLS
-
-#endif /* __NAMED_PIPE_CONNECTION_H__ */
diff --git a/gtk/controller/namedpipelistener.c b/gtk/controller/namedpipelistener.c
deleted file mode 100644
index 820c606..0000000
--- a/gtk/controller/namedpipelistener.c
+++ /dev/null
@@ -1,329 +0,0 @@
-/*
-   Copyright (C) 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 "namedpipelistener.h"
-
-#include <windows.h>
-#include <stdio.h>
-#include <conio.h>
-#include <tchar.h>
-
-static GSource *g_win32_handle_source_add (HANDLE      handle,
-                                           GSourceFunc callback,
-                                           gpointer    user_data);
-
-G_DEFINE_TYPE (SpiceNamedPipeListener, spice_named_pipe_listener, G_TYPE_OBJECT);
-
-struct _SpiceNamedPipeListenerPrivate
-{
-  GQueue             namedpipes;
-};
-
-static void
-spice_named_pipe_listener_dispose (GObject *object)
-{
-  SpiceNamedPipeListener *listener = SPICE_NAMED_PIPE_LISTENER (object);
-  SpiceNamedPipe *p;
-
-  while ((p = g_queue_pop_head (&listener->priv->namedpipes)) != NULL)
-    g_object_unref (p);
-
-  g_return_if_fail (g_queue_get_length (&listener->priv->namedpipes) == 0);
-  g_queue_clear (&listener->priv->namedpipes);
-
-  if (G_OBJECT_CLASS (spice_named_pipe_listener_parent_class)->dispose)
-    G_OBJECT_CLASS (spice_named_pipe_listener_parent_class)->dispose (object);
-}
-
-static void
-spice_named_pipe_listener_class_init (SpiceNamedPipeListenerClass *klass)
-{
-  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
-
-  g_type_class_add_private (klass, sizeof (SpiceNamedPipeListenerPrivate));
-
-  gobject_class->dispose = spice_named_pipe_listener_dispose;
-}
-
-static void
-spice_named_pipe_listener_init (SpiceNamedPipeListener *listener)
-{
-  listener->priv = G_TYPE_INSTANCE_GET_PRIVATE (listener,
-                                                SPICE_TYPE_NAMED_PIPE_LISTENER,
-                                                SpiceNamedPipeListenerPrivate);
-
-  g_queue_init (&listener->priv->namedpipes);
-}
-
-void
-spice_named_pipe_listener_add_named_pipe (SpiceNamedPipeListener *listener,
-                                          SpiceNamedPipe         *namedpipe)
-{
-  g_return_if_fail (SPICE_IS_NAMED_PIPE_LISTENER (listener));
-  g_return_if_fail (SPICE_IS_NAMED_PIPE (namedpipe));
-
-  g_queue_push_head (&listener->priv->namedpipes, g_object_ref (namedpipe));
-}
-
-typedef struct {
-  GCancellable *cancellable;
-  GSource *source;
-  GSimpleAsyncResult *async_result;
-  SpiceNamedPipe *np;
-  OVERLAPPED overlapped;
-} ConnectData;
-
-static void
-connect_cancelled (GCancellable *cancellable,
-                   gpointer      user_data)
-{
-  ConnectData *c = user_data;
-  GError *error = NULL;
-
-  g_source_destroy (c->source);
-  c->source = NULL;
-
-  g_cancellable_set_error_if_cancelled (cancellable, &error);
-  g_simple_async_result_set_from_error (c->async_result, error);
-  g_error_free (error);
-
-  g_simple_async_result_complete (c->async_result);
-  g_object_unref (c->async_result);
-}
-
-static gboolean
-connect_ready (gpointer user_data)
-{
-  ConnectData *c = user_data;
-  gulong cbret;
-  gboolean success;
-
-  /* Now complete the result (assuming it wasn't already completed) */
-  g_return_val_if_fail (c->async_result != NULL, FALSE);
-
-  success = GetOverlappedResult (c->np, &c->overlapped, &cbret, FALSE);
-  if (!success)
-    {
-      int errsv = GetLastError ();
-      gchar *emsg = g_win32_error_message (errsv);
-
-      g_simple_async_result_set_error (c->async_result,
-                                       G_IO_ERROR,
-                                       G_IO_ERROR_INVALID_ARGUMENT,
-                                       "GetOverlappedResult(): %s %d",
-                                       emsg, errsv);
-    }
-
-  g_simple_async_result_complete (c->async_result);
-  g_object_unref (c->async_result); /* TODO: that sould free c? */
-
-  return FALSE;
-}
-
-static void
-connect_data_free (gpointer data)
-{
-  ConnectData *c = data;
-
-  if (c->source)
-    {
-      g_source_destroy (c->source);
-      g_source_unref (c->source);
-      c->source = NULL;
-    }
-  if (c->cancellable)
-    {
-      g_signal_handlers_disconnect_by_func (c->cancellable, connect_cancelled, c);
-      g_object_unref (c->cancellable);
-      c->cancellable = NULL;
-    }
-
-  if (c->async_result) /* this is only a weak reference */
-      c->async_result = NULL;
-
-  if (c->overlapped.hEvent != NULL)
-    {
-      CloseHandle (c->overlapped.hEvent);
-      c->overlapped.hEvent = NULL;
-    }
-
-  if (c->np != NULL)
-    {
-      g_object_unref (c->np);
-      c->np = NULL;
-    }
-
-  g_free (c);
-}
-
-void
-spice_named_pipe_listener_accept_async (SpiceNamedPipeListener  *listener,
-                                        GCancellable            *cancellable,
-                                        GAsyncReadyCallback      callback,
-                                        gpointer                 user_data)
-{
-  ConnectData *c;
-  SpiceNamedPipe *namedpipe;
-
-  g_return_if_fail (SPICE_IS_NAMED_PIPE_LISTENER (listener));
-
-  namedpipe = SPICE_NAMED_PIPE (g_queue_pop_head (&listener->priv->namedpipes));
-  /* do not unref, we keep that ref */
-  g_return_if_fail (namedpipe != NULL);
-
-  c = g_new0 (ConnectData, 1);
-  c->np = namedpipe; /* transfer what used to be the avail_namedpipes ref */
-  c->async_result = g_simple_async_result_new (G_OBJECT (listener), callback, user_data,
-                                               spice_named_pipe_listener_accept_async);
-  c->overlapped.hEvent = CreateEvent (NULL, /* default security attribute */
-                                      TRUE, /* manual-reset event */
-                                      TRUE, /* initial state = signaled */
-                                      NULL); /* unnamed event object */
-  g_simple_async_result_set_op_res_gpointer (c->async_result, c, connect_data_free);
-
-  if (ConnectNamedPipe (spice_named_pipe_get_handle (namedpipe), &c->overlapped) != 0)
-    {
-      /* we shouldn't get there if the listener is in non-blocking */
-      g_warn_if_reached ();
-    }
-
-  switch (GetLastError ())
-    {
-      case ERROR_SUCCESS:
-      case ERROR_IO_PENDING:
-        break;
-      case ERROR_PIPE_CONNECTED:
-        g_simple_async_result_complete_in_idle (c->async_result);
-        g_object_unref (c->async_result);
-        return;
-      default:
-        g_simple_async_report_error_in_idle (G_OBJECT (listener),
-            callback, user_data,
-            G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
-            "ConnectNamedPipe() failed %ld", GetLastError ());
-        g_object_unref (c->async_result);
-        return;
-    }
-
-  c->source = g_win32_handle_source_add (c->overlapped.hEvent,
-                                         connect_ready, c);
-
-  if (cancellable)
-    {
-      c->cancellable = g_object_ref (cancellable);
-      g_signal_connect (cancellable, "cancelled",
-                        G_CALLBACK (connect_cancelled), c);
-    }
-}
-
-SpiceNamedPipeConnection *
-spice_named_pipe_listener_accept_finish (SpiceNamedPipeListener *listener,
-                                         GAsyncResult           *result,
-                                         GObject               **source_object,
-                                         GError                **error)
-{
-  GSimpleAsyncResult *simple;
-  ConnectData *c;
-  SpiceNamedPipeConnection *connection;
-
-  g_return_val_if_fail (SPICE_IS_NAMED_PIPE_LISTENER (listener), NULL);
-  g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), NULL);
-  g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (listener),
-                                                        spice_named_pipe_listener_accept_async),
-                        NULL);
-
-  simple = G_SIMPLE_ASYNC_RESULT (result);
-  if (g_simple_async_result_propagate_error (simple, error))
-      return NULL;
-
-  c = g_simple_async_result_get_op_res_gpointer (simple);
-
-  connection = g_object_new (SPICE_TYPE_NAMED_PIPE_CONNECTION,
-                             "namedpipe", c->np,
-                             NULL);
-  return connection;
-}
-
-SpiceNamedPipeListener *
-spice_named_pipe_listener_new (void)
-{
-  return g_object_new (SPICE_TYPE_NAMED_PIPE_LISTENER, NULL);
-}
-
-/* Windows HANDLE GSource - from gio/gwin32resolver.c */
-
-typedef struct {
-  GSource source;
-  GPollFD pollfd;
-} GWin32HandleSource;
-
-static gboolean
-g_win32_handle_source_prepare (GSource *source,
-                               gint    *timeout)
-{
-  *timeout = -1;
-  return FALSE;
-}
-
-static gboolean
-g_win32_handle_source_check (GSource *source)
-{
-  GWin32HandleSource *hsource = (GWin32HandleSource *)source;
-
-  return hsource->pollfd.revents;
-}
-
-static gboolean
-g_win32_handle_source_dispatch (GSource     *source,
-                                GSourceFunc  callback,
-                                gpointer     user_data)
-{
-  return (*callback) (user_data);
-}
-
-static void
-g_win32_handle_source_finalize (GSource *source)
-{
-  ;
-}
-
-GSourceFuncs g_win32_handle_source_funcs = {
-  g_win32_handle_source_prepare,
-  g_win32_handle_source_check,
-  g_win32_handle_source_dispatch,
-  g_win32_handle_source_finalize
-};
-
-static GSource *
-g_win32_handle_source_add (HANDLE      handle,
-                           GSourceFunc callback,
-                           gpointer    user_data)
-{
-  GWin32HandleSource *hsource;
-  GSource *source;
-
-  source = g_source_new (&g_win32_handle_source_funcs, sizeof (GWin32HandleSource));
-  hsource = (GWin32HandleSource *)source;
-  hsource->pollfd.fd = (gint)handle;
-  hsource->pollfd.events = G_IO_IN;
-  hsource->pollfd.revents = 0;
-  g_source_add_poll (source, &hsource->pollfd);
-
-  g_source_set_callback (source, callback, user_data, NULL);
-  g_source_attach (source, g_main_context_get_thread_default ());
-  return source;
-}
diff --git a/gtk/controller/namedpipelistener.h b/gtk/controller/namedpipelistener.h
deleted file mode 100644
index c2dbd0a..0000000
--- a/gtk/controller/namedpipelistener.h
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
-   Copyright (C) 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/>.
-*/
-#ifndef __NAMED_PIPE_LISTENER_H__
-#define __NAMED_PIPE_LISTENER_H__
-
-#include <gio/gio.h>
-
-#include "namedpipe.h"
-#include "namedpipeconnection.h"
-
-G_BEGIN_DECLS
-
-#define SPICE_TYPE_NAMED_PIPE_LISTENER                       (spice_named_pipe_listener_get_type ())
-#define SPICE_NAMED_PIPE_LISTENER(inst)                      (G_TYPE_CHECK_INSTANCE_CAST ((inst),                     \
-                                                              SPICE_TYPE_NAMED_PIPE_LISTENER, SpiceNamedPipeListener))
-#define SPICE_NAMED_PIPE_LISTENER_CLASS(class)               (G_TYPE_CHECK_CLASS_CAST ((class),                       \
-                                                              SPICE_TYPE_NAMED_PIPE_LISTENER, SpiceNamedPipeListenerClass))
-#define SPICE_IS_NAMED_PIPE_LISTENER(inst)                   (G_TYPE_CHECK_INSTANCE_TYPE ((inst),                     \
-                                                              SPICE_TYPE_NAMED_PIPE_LISTENER))
-#define SPICE_IS_NAMED_PIPE_LISTENER_CLASS(class)            (G_TYPE_CHECK_CLASS_TYPE ((class),                       \
-                                                              SPICE_TYPE_NAMED_PIPE_LISTENER))
-#define SPICE_NAMED_PIPE_LISTENER_GET_CLASS(inst)            (G_TYPE_INSTANCE_GET_CLASS ((inst),                      \
-                                                              SPICE_TYPE_NAMED_PIPE_LISTENER, SpiceNamedPipeListenerClass))
-
-typedef struct _SpiceNamedPipeListener                       SpiceNamedPipeListener;
-typedef struct _SpiceNamedPipeListenerPrivate                SpiceNamedPipeListenerPrivate;
-typedef struct _SpiceNamedPipeListenerClass                  SpiceNamedPipeListenerClass;
-
-struct _SpiceNamedPipeListenerClass
-{
-  GObjectClass parent_class;
-};
-
-struct _SpiceNamedPipeListener
-{
-  GObject parent_instance;
-  SpiceNamedPipeListenerPrivate *priv;
-};
-
-GType                       spice_named_pipe_listener_get_type       (void) G_GNUC_CONST;
-
-SpiceNamedPipeListener *    spice_named_pipe_listener_new            (void);
-void                        spice_named_pipe_listener_add_named_pipe (SpiceNamedPipeListener  *listener,
-                                                                      SpiceNamedPipe          *namedpipe);
-void                        spice_named_pipe_listener_accept_async   (SpiceNamedPipeListener *listener,
-                                                                      GCancellable           *cancellable,
-                                                                      GAsyncReadyCallback     callback,
-                                                                      gpointer                user_data);
-SpiceNamedPipeConnection *  spice_named_pipe_listener_accept_finish  (SpiceNamedPipeListener *listener,
-                                                                      GAsyncResult           *result,
-                                                                      GObject               **source_object,
-                                                                      GError                **error);
-
-G_END_DECLS
-
-#endif /* __NAMED_PIPE_LISTENER_H__ */
diff --git a/gtk/controller/spice-controller-listener.c b/gtk/controller/spice-controller-listener.c
deleted file mode 100644
index 98baf33..0000000
--- a/gtk/controller/spice-controller-listener.c
+++ /dev/null
@@ -1,159 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2012 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 <glib/gstdio.h>
-
-#include "spice-controller-listener.h"
-
-#ifdef G_OS_WIN32
-#include <windows.h>
-#include "namedpipe.h"
-#include "namedpipelistener.h"
-#include "win32-util.h"
-#endif
-
-#ifdef G_OS_UNIX
-#include <gio/gunixsocketaddress.h>
-#endif
-
-/**
- * SpiceControllerListenerError:
- * @SPICE_CONTROLLER_LISTENER_ERROR_VALUE: invalid value.
- *
- * Possible errors of controller listener related functions.
- **/
-
-/**
- * SPICE_CONTROLLER_LISTENER_ERROR:
- *
- * The error domain of the controller listener subsystem.
- **/
-GQuark
-spice_controller_listener_error_quark (void)
-{
-  return g_quark_from_static_string ("spice-controller-listener-error");
-}
-
-GObject*
-spice_controller_listener_new (const gchar *address, GError **error)
-{
-    GObject *listener = NULL;
-    gchar *addr = NULL;
-
-    g_return_val_if_fail (error == NULL || *error == NULL, NULL);
-
-    addr = g_strdup (address);
-
-#ifdef G_OS_WIN32
-    if (addr == NULL)
-        addr = g_strdup (g_getenv ("SPICE_XPI_NAMEDPIPE"));
-    if (addr == NULL)
-        addr = g_strdup_printf ("\\\\.\\pipe\\SpiceController-%" G_GUINT64_FORMAT, (guint64)GetCurrentProcessId ());
-#else
-    if (addr == NULL)
-        addr = g_strdup (g_getenv ("SPICE_XPI_SOCKET"));
-#endif
-    if (addr == NULL) {
-        g_set_error (error,
-                     SPICE_CONTROLLER_LISTENER_ERROR,
-                     SPICE_CONTROLLER_LISTENER_ERROR_VALUE,
-#ifdef G_OS_WIN32
-                     "Missing namedpipe address"
-#else
-                     "Missing socket address"
-#endif
-                     );
-        goto end;
-    }
-
-    g_unlink (addr);
-
-#ifdef G_OS_WIN32
-    {
-        SpiceNamedPipe *np;
-
-        listener = G_OBJECT (spice_named_pipe_listener_new ());
-
-        np = spice_win32_user_pipe_new (addr, error);
-        if (!np) {
-            g_object_unref (listener);
-            listener = NULL;
-            goto end;
-        }
-
-        spice_named_pipe_listener_add_named_pipe (SPICE_NAMED_PIPE_LISTENER (listener), np);
-    }
-#else
-    {
-        listener = G_OBJECT (g_socket_listener_new ());
-
-        if (!g_socket_listener_add_address (G_SOCKET_LISTENER (listener),
-                                            G_SOCKET_ADDRESS (g_unix_socket_address_new (addr)),
-                                            G_SOCKET_TYPE_STREAM,
-                                            G_SOCKET_PROTOCOL_DEFAULT,
-                                            NULL,
-                                            NULL,
-                                            error))
-            g_warning ("failed to add address");
-    }
-#endif
-
-end:
-    g_free (addr);
-    return listener;
-}
-
-void
-spice_controller_listener_accept_async (GObject *listener,
-                                        GCancellable *cancellable,
-                                        GAsyncReadyCallback callback,
-                                        gpointer user_data)
-{
-    g_return_if_fail(G_IS_OBJECT(listener));
-
-#ifdef G_OS_WIN32
-    spice_named_pipe_listener_accept_async (SPICE_NAMED_PIPE_LISTENER (listener), cancellable, callback, user_data);
-#else
-    g_socket_listener_accept_async (G_SOCKET_LISTENER (listener), cancellable, callback, user_data);
-#endif
-}
-
-GIOStream*
-spice_controller_listener_accept_finish (GObject *listener,
-                                         GAsyncResult *result,
-                                         GObject **source_object,
-                                         GError **error)
-{
-    g_return_val_if_fail(G_IS_OBJECT(listener), NULL);
-
-#ifdef G_OS_WIN32
-    SpiceNamedPipeConnection *np;
-    np = spice_named_pipe_listener_accept_finish (SPICE_NAMED_PIPE_LISTENER (listener), result, source_object, error);
-    if (np)
-        return G_IO_STREAM (np);
-#else
-    GSocketConnection *socket;
-    socket = g_socket_listener_accept_finish (G_SOCKET_LISTENER (listener), result, source_object, error);
-    if (socket)
-        return G_IO_STREAM (socket);
-#endif
-
-    return NULL;
-}
diff --git a/gtk/controller/spice-controller-listener.h b/gtk/controller/spice-controller-listener.h
deleted file mode 100644
index a50bdea..0000000
--- a/gtk/controller/spice-controller-listener.h
+++ /dev/null
@@ -1,47 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2012 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_CONTROLLER_LISTENER_H__
-#define __SPICE_CONTROLLER_LISTENER_H__
-
-#include <gio/gio.h>
-
-G_BEGIN_DECLS
-
-#define SPICE_CONTROLLER_LISTENER_ERROR spice_controller_listener_error_quark ()
-GQuark spice_controller_listener_error_quark (void);
-
-typedef enum
-{
-    SPICE_CONTROLLER_LISTENER_ERROR_VALUE /* incorrect value */
-} SpiceControllerListenerError;
-
-
-GObject* spice_controller_listener_new (const gchar *address, GError **error);
-
-void spice_controller_listener_accept_async (GObject *listener,
-                                             GCancellable *cancellable,
-                                             GAsyncReadyCallback callback,
-                                             gpointer user_data);
-
-GIOStream* spice_controller_listener_accept_finish (GObject *listener,
-                                                    GAsyncResult *result,
-                                                    GObject **source_object,
-                                                    GError **error);
-G_END_DECLS
-
-#endif /* __SPICE_CONTROLLER_LISTENER_H__ */
diff --git a/gtk/controller/spice-foreign-menu-listener.c b/gtk/controller/spice-foreign-menu-listener.c
deleted file mode 100644
index 5e62606..0000000
--- a/gtk/controller/spice-foreign-menu-listener.c
+++ /dev/null
@@ -1,161 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2012 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 <glib/gstdio.h>
-
-#include "spice-foreign-menu-listener.h"
-
-#ifdef G_OS_WIN32
-#include <windows.h>
-#include "namedpipe.h"
-#include "namedpipelistener.h"
-#include "win32-util.h"
-#endif
-
-#ifdef G_OS_UNIX
-#include <gio/gunixsocketaddress.h>
-#endif
-
-/**
- * SpiceForeignMenuListenerError:
- * @SPICE_FOREIGN_MENU_LISTENER_ERROR_VALUE: invalid value.
- *
- * Possible errors of foreign menu listener related functions.
- **/
-
-/**
- * SPICE_FOREIGN_MENU_LISTENER_ERROR:
- *
- * The error domain of the foreign menu listener subsystem.
- **/
-GQuark
-spice_foreign_menu_listener_error_quark (void)
-{
-  return g_quark_from_static_string ("spice-foreign-menu-listener-error");
-}
-
-GObject*
-spice_foreign_menu_listener_new (const gchar *address, GError **error)
-{
-    GObject *listener = NULL;
-    gchar *addr = NULL;
-
-    g_return_val_if_fail (error == NULL || *error == NULL, NULL);
-
-    addr = g_strdup (address);
-
-#ifdef G_OS_WIN32
-    if (addr == NULL)
-        addr = g_strdup (g_getenv ("SPICE_FOREIGN_MENU_NAMEDPIPE"));
-    if (addr == NULL)
-        addr = g_strdup_printf ("\\\\.\\pipe\\SpiceForeignMenu-%" G_GUINT64_FORMAT, (guint64)GetCurrentProcessId ());
-#else
-    if (addr == NULL)
-        addr = g_strdup (g_getenv ("SPICE_FOREIGN_MENU_SOCKET"));
-    if (addr == NULL)
-        addr = g_strdup_printf ("/tmp/SpiceForeignMenu-%" G_GUINT64_FORMAT ".uds", (guint64)getpid ());
-#endif
-    if (addr == NULL) {
-        g_set_error (error,
-                     SPICE_FOREIGN_MENU_LISTENER_ERROR,
-                     SPICE_FOREIGN_MENU_LISTENER_ERROR_VALUE,
-#ifdef G_OS_WIN32
-                     "Missing namedpipe address"
-#else
-                     "Missing socket address"
-#endif
-                     );
-        goto end;
-    }
-
-    g_unlink (addr);
-
-#ifdef G_OS_WIN32
-    {
-        SpiceNamedPipe *np;
-
-        listener = G_OBJECT (spice_named_pipe_listener_new ());
-
-        np = spice_win32_user_pipe_new (addr, error);
-        if (!np) {
-            g_object_unref (listener);
-            listener = NULL;
-            goto end;
-        }
-
-        spice_named_pipe_listener_add_named_pipe (SPICE_NAMED_PIPE_LISTENER (listener), np);
-    }
-#else
-    {
-        listener = G_OBJECT (g_socket_listener_new ());
-
-        if (!g_socket_listener_add_address (G_SOCKET_LISTENER (listener),
-                                            G_SOCKET_ADDRESS (g_unix_socket_address_new (addr)),
-                                            G_SOCKET_TYPE_STREAM,
-                                            G_SOCKET_PROTOCOL_DEFAULT,
-                                            NULL,
-                                            NULL,
-                                            error))
-            g_warning ("failed to add address");
-    }
-#endif
-
-end:
-    g_free (addr);
-    return listener;
-}
-
-void
-spice_foreign_menu_listener_accept_async (GObject *listener,
-                                          GCancellable *cancellable,
-                                          GAsyncReadyCallback callback,
-                                          gpointer user_data)
-{
-    g_return_if_fail(G_IS_OBJECT(listener));
-
-#ifdef G_OS_WIN32
-    spice_named_pipe_listener_accept_async (SPICE_NAMED_PIPE_LISTENER (listener), cancellable, callback, user_data);
-#else
-    g_socket_listener_accept_async (G_SOCKET_LISTENER (listener), cancellable, callback, user_data);
-#endif
-}
-
-GIOStream*
-spice_foreign_menu_listener_accept_finish (GObject *listener,
-                                           GAsyncResult *result,
-                                           GObject **source_object,
-                                           GError **error)
-{
-    g_return_val_if_fail(G_IS_OBJECT(listener), NULL);
-
-#ifdef G_OS_WIN32
-    SpiceNamedPipeConnection *np;
-    np = spice_named_pipe_listener_accept_finish (SPICE_NAMED_PIPE_LISTENER (listener), result, source_object, error);
-    if (np)
-        return G_IO_STREAM (np);
-#else
-    GSocketConnection *socket;
-    socket = g_socket_listener_accept_finish (G_SOCKET_LISTENER (listener), result, source_object, error);
-    if (socket)
-        return G_IO_STREAM (socket);
-#endif
-
-    return NULL;
-}
diff --git a/gtk/controller/spice-foreign-menu-listener.h b/gtk/controller/spice-foreign-menu-listener.h
deleted file mode 100644
index 1071528..0000000
--- a/gtk/controller/spice-foreign-menu-listener.h
+++ /dev/null
@@ -1,47 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2012 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_FOREIGN_MENU_LISTENER_H__
-#define __SPICE_FOREIGN_MENU_LISTENER_H__
-
-#include <gio/gio.h>
-
-G_BEGIN_DECLS
-
-#define SPICE_FOREIGN_MENU_LISTENER_ERROR spice_foreign_menu_listener_error_quark ()
-GQuark spice_foreign_menu_listener_error_quark (void);
-
-typedef enum
-{
-    SPICE_FOREIGN_MENU_LISTENER_ERROR_VALUE /* incorrect value */
-} SpiceForeignMenuListenerError;
-
-
-GObject* spice_foreign_menu_listener_new (const gchar *address, GError **error);
-
-void spice_foreign_menu_listener_accept_async (GObject *listener,
-                                             GCancellable *cancellable,
-                                             GAsyncReadyCallback callback,
-                                             gpointer user_data);
-
-GIOStream* spice_foreign_menu_listener_accept_finish (GObject *listener,
-                                                    GAsyncResult *result,
-                                                    GObject **source_object,
-                                                    GError **error);
-G_END_DECLS
-
-#endif /* __SPICE_FOREIGN_MENU_LISTENER_H__ */
diff --git a/gtk/controller/test.c b/gtk/controller/test.c
deleted file mode 100644
index c08fe21..0000000
--- a/gtk/controller/test.c
+++ /dev/null
@@ -1,292 +0,0 @@
-/* Copyright (C) 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 <stdio.h>
-#include <stdint.h>
-#include <spice/controller_prot.h>
-
-#include "spice-controller.h"
-
-#ifdef WIN32
-#include <windows.h>
-#define PIPE_NAME TEXT("\\\\.\\pipe\\SpiceController-%lu")
-static HANDLE pipe = INVALID_HANDLE_VALUE;
-#else
-
-#include <sys/socket.h>
-#ifdef HAVE_SYS_TYPES_H
-#include <sys/types.h>
-#endif
-#include <sys/un.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <string.h>
-#include <errno.h>
-
-#define PIPE_NAME "/tmp/test"
-static int sock = -1;
-
-#endif
-
-#define PIPE_NAME_MAX_LEN 256
-
-void write_to_pipe (const void* data, size_t len)
-{
-#ifdef WIN32
-    DWORD written;
-    if (!WriteFile (pipe, data, len, &written, NULL) || written != len) {
-        printf("Write to pipe failed %u\n", GetLastError());
-    }
-#else
-    if (send (sock, data, len, 0) != len) {
-        printf ("send failed, (%d) %s\n", errno, strerror(errno));
-    }
-#endif
-}
-
-gboolean send_init (void)
-{
-    ControllerInit msg = {
-      { CONTROLLER_MAGIC, CONTROLLER_VERSION, sizeof (msg) },
-      0,
-      CONTROLLER_FLAG_EXCLUSIVE
-    };
-
-    write_to_pipe(&msg, sizeof (msg));
-    return FALSE;
-}
-
-void send_msg (uint32_t id)
-{
-    ControllerMsg msg = {
-      id, sizeof (msg)
-    };
-
-    write_to_pipe (&msg, sizeof (msg));
-}
-
-void send_value (uint32_t id, uint32_t value)
-{
-    ControllerValue msg = {
-      { id, sizeof(msg) },
-      value
-    };
-
-    write_to_pipe (&msg, sizeof (msg));
-}
-
-void send_data (uint32_t id, uint8_t* data, size_t data_size)
-{
-    size_t size = sizeof (ControllerData) + data_size;
-    ControllerData* msg = (ControllerData*)g_malloc0 (size);
-
-    msg->base.id = id;
-    msg->base.size = (uint32_t)size;
-    memcpy (msg->data, data, data_size);
-    write_to_pipe (msg, size);
-    g_free (msg);
-}
-
-ssize_t read_from_pipe (void* data, size_t size)
-{
-    ssize_t read;
-#ifdef WIN32
-    DWORD bytes;
-    if (!ReadFile (pipe, data, size, &bytes, NULL)) {
-        printf ("Read from pipe failed %u\n", GetLastError());
-    }
-    read = bytes;
-#else
-    read = recv (sock, data, size, 0);
-    if ((read == -1 || read == 0)) {
-        printf ("recv failed, (%d) %s\n", errno, strerror (errno));
-    }
-#endif
-    return read;
-}
-
-#define HOST "localhost"
-#define PORT 5931
-#define SPORT 0
-#define PWD "P at s5w0rd"
-#define SECURE_CHANNELS "main,inputs,playback"
-#define DISABLED_CHANNELS "playback,record"
-#define TITLE "Hello from controller"
-#define HOTKEYS "toggle-fullscreen=shift+f1,release-cursor=shift+f2"
-#define MENU "0\r4864\rS&end Ctrl+Alt+Del\tCtrl+Alt+End\r0\r\n" \
-    "0\r5120\r&Toggle full screen\tShift+F11\r0\r\n" \
-    "0\r1\r&Special keys\r4\r\n" \
-    "1\r5376\r&Send Shift+F11\r0\r\n" \
-    "1\r5632\r&Send Shift+F12\r0\r\n" \
-    "1\r5888\r&Send Ctrl+Alt+End\r0\r\n" \
-    "0\r1\r-\r1\r\n" \
-    "0\r2\rChange CD\r4\r\n" \
-    "2\r3\rNo CDs\r0\r\n" \
-    "2\r4\r[Eject]\r0\r\n" \
-    "0\r5\r-\r1\r\n" \
-    "0\r6\rPlay\r0\r\n" \
-    "0\r7\rSuspend\r0\r\n" \
-    "0\r8\rStop\r0\r\n"
-
-#define TLS_CIPHERS "TLS_C1PHERS"
-#define CA_FILE "C at _FILE"
-#define HOST_SUBJECT "Host_SUBJ3CT"
-
-SpiceCtrlController *ctrl;
-GMainLoop *loop;
-
-void signaled (GObject    *gobject, const gchar *signal_name)
-{
-    g_message ("signaled: %s", signal_name);
-    if (g_str_equal (signal_name, "hide")) {
-      spice_ctrl_controller_menu_item_click_msg (ctrl, 42);
-      g_timeout_add (1000, (GSourceFunc)g_main_loop_quit, loop);
-    }
-}
-
-void notified (GObject    *gobject, GParamSpec *pspec,
-               gpointer    user_data)
-{
-    GValue value = { 0, };
-    GValue strvalue = { 0, };
-
-    g_return_if_fail (gobject != NULL);
-    g_return_if_fail (pspec != NULL);
-
-    g_value_init (&value, pspec->value_type);
-    g_value_init (&strvalue, G_TYPE_STRING);
-    g_object_get_property (gobject, pspec->name, &value);
-
-    if (pspec->value_type == G_TYPE_STRV) {
-      gchar** p = (gchar **)g_value_get_boxed (&value);
-      g_message ("notify::%s == ", pspec->name);
-      while (*p)
-        g_message ("%s", *p++);
-    } else if (G_TYPE_IS_OBJECT(pspec->value_type)) {
-      GObject *o = g_value_get_object (&value);
-      g_message ("notify::%s == %s", pspec->name, o ? G_OBJECT_TYPE_NAME (o) : "null");
-    } else {
-      g_value_transform (&value, &strvalue);
-      g_message ("notify::%s  = %s", pspec->name, g_value_get_string (&strvalue));
-    }
-
-    g_value_unset (&value);
-    g_value_unset (&strvalue);
-}
-
-void connect_signals (gpointer obj)
-{
-    guint i, n_ids = 0;
-    guint *ids = NULL;
-    GType type = G_OBJECT_TYPE (obj);
-
-    ids = g_signal_list_ids (type, &n_ids);
-    for (i = 0; i < n_ids; i++) {
-        const gchar *name = g_signal_name (ids[i]);
-        g_signal_connect (obj, name, G_CALLBACK (signaled), (gpointer)name);
-    }
-}
-
-int main (int argc, char *argv[])
-{
-#ifdef WIN32
-    int spicec_pid = (argc > 1 ? atoi (argv[1]) : 0);
-#endif
-    char* host = (argc > 2 ? argv[2] : (char*)HOST);
-    int port = (argc > 3 ? atoi (argv[3]) : PORT);
-    char pipe_name[PIPE_NAME_MAX_LEN];
-    ControllerValue msg;
-    ssize_t read;
-
-#if !GLIB_CHECK_VERSION(2,36,0)
-    g_type_init ();
-#endif
-    ctrl = spice_ctrl_controller_new ();
-    loop = g_main_loop_new (NULL, FALSE);
-    g_signal_connect (ctrl, "notify", G_CALLBACK (notified), NULL);
-    connect_signals (ctrl);
-
-#ifdef WIN32
-    snprintf (pipe_name, PIPE_NAME_MAX_LEN, PIPE_NAME, spicec_pid);
-    spice_ctrl_controller_listen (ctrl, pipe_name, NULL, NULL);
-
-    printf ("Creating Spice controller connection %s\n", pipe_name);
-    pipe = CreateFile (pipe_name, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
-    if (pipe == INVALID_HANDLE_VALUE) {
-        printf ("Could not open pipe %u\n", GetLastError());
-        return -1;
-    }
-#else
-    spice_ctrl_controller_listen (ctrl, PIPE_NAME, NULL, NULL);
-
-    snprintf (pipe_name, PIPE_NAME_MAX_LEN, PIPE_NAME);
-    printf ("Creating a controller connection %s\n", pipe_name);
-    struct sockaddr_un remote;
-    if ((sock = socket (AF_UNIX, SOCK_STREAM, 0)) == -1) {
-        printf ("Could not open socket, (%d) %s\n", errno, strerror(errno));
-        return -1;
-    }
-    remote.sun_family = AF_UNIX;
-    strcpy (remote.sun_path, pipe_name);
-    if (connect (sock, (struct sockaddr *)&remote,
-                strlen (remote.sun_path) + sizeof(remote.sun_family)) == -1) {
-        printf ("Socket connect failed, (%d) %s\n", errno, strerror(errno));
-        close (sock);
-        return -1;
-    }
-#endif
-
-    /* TODO: we rely on socket / pipe buffer... which is lame :) */
-    send_init ();
-
-    send_data (CONTROLLER_HOST, (uint8_t*)host, strlen(host) + 1);
-    send_value (CONTROLLER_PORT, port);
-    send_value (CONTROLLER_SPORT, SPORT);
-    send_data (CONTROLLER_PASSWORD, (uint8_t*)PWD, strlen(PWD) + 1);
-    send_data (CONTROLLER_SECURE_CHANNELS, (uint8_t*)SECURE_CHANNELS, strlen(SECURE_CHANNELS) + 1);
-    send_data (CONTROLLER_DISABLE_CHANNELS, (uint8_t*)DISABLED_CHANNELS, strlen(DISABLED_CHANNELS) + 1);
-    send_data (CONTROLLER_TLS_CIPHERS, (uint8_t*)TLS_CIPHERS, sizeof(TLS_CIPHERS) + 1);
-    send_data (CONTROLLER_CA_FILE, (uint8_t*)CA_FILE, strlen(CA_FILE) + 1);
-    send_data (CONTROLLER_HOST_SUBJECT, (uint8_t*)HOST_SUBJECT, strlen(HOST_SUBJECT) + 1);
-    send_data (CONTROLLER_SET_TITLE, (uint8_t*)TITLE, strlen(TITLE) + 1);
-    send_data (CONTROLLER_HOTKEYS, (uint8_t*)HOTKEYS, strlen(HOTKEYS) + 1);
-    send_data (CONTROLLER_CREATE_MENU, (uint8_t*)MENU, strlen(MENU));
-
-    send_value (CONTROLLER_FULL_SCREEN, /*CONTROLLER_SET_FULL_SCREEN |*/ CONTROLLER_AUTO_DISPLAY_RES);
-
-    send_msg (CONTROLLER_SHOW);
-    send_msg (CONTROLLER_CONNECT);
-    send_msg (CONTROLLER_SHOW);
-    send_msg (CONTROLLER_DELETE_MENU);
-    send_msg (CONTROLLER_HIDE);
-
-    g_main_loop_run (loop);
-
-    while ((read = read_from_pipe (&msg, sizeof(msg))) == sizeof(msg)) {
-        printf ("Received id %u, size %u, value %u\n", msg.base.id, msg.base.size, msg.value);
-        if (msg.value == 42)
-          break;
-    }
-
-#ifdef WIN32
-    CloseHandle (pipe);
-#else
-    close (sock);
-#endif
-    g_object_unref (ctrl);
-    return 0;
-}
diff --git a/gtk/controller/util.vala b/gtk/controller/util.vala
deleted file mode 100644
index acd677e..0000000
--- a/gtk/controller/util.vala
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright (C) 2012 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/>.
-
-namespace SpiceCtrl {
-
-	public async void input_stream_read (InputStream stream, uint8[] buffer) throws GLib.IOError {
-		var length = buffer.length;
-		ssize_t i = 0;
-
-		while (i < length) {
-			var n = yield stream.read_async (buffer[i:length]);
-			if (n == 0)
-				throw new GLib.IOError.CLOSED ("closed stream") ;
-			i += n;
-		}
-	}
-
-	public async void output_stream_write (OutputStream stream, owned uint8[] buffer) throws GLib.IOError {
-		var length = buffer.length;
-		ssize_t i = 0;
-
-		while (i < length) {
-			var n = yield stream.write_async (buffer[i:length]);
-			if (n == 0)
-				throw new GLib.IOError.CLOSED ("closed stream") ;
-			i += n;
-		}
-	}
-
-}
diff --git a/gtk/controller/win32-util.c b/gtk/controller/win32-util.c
deleted file mode 100644
index c3e0400..0000000
--- a/gtk/controller/win32-util.c
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
-   Copyright (C) 2012 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 "win32-util.h"
-#include <windows.h>
-#include <sddl.h>
-#include <aclapi.h>
-
-gboolean
-spice_win32_set_low_integrity (void* handle, GError **error)
-{
-    g_return_val_if_fail (handle != NULL, FALSE);
-    g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
-
-    /* see also http://msdn.microsoft.com/en-us/library/bb625960.aspx */
-    PSECURITY_DESCRIPTOR psd = NULL;
-    PACL psacl = NULL;
-    BOOL sacl_present = FALSE;
-    BOOL sacl_defaulted = FALSE;
-    char *emsg;
-    int errsv;
-    gboolean success = FALSE;
-
-    if (!ConvertStringSecurityDescriptorToSecurityDescriptor ("S:(ML;;NW;;;LW)",
-                                                              SDDL_REVISION_1, &psd, NULL))
-        goto failed;
-
-    if (!GetSecurityDescriptorSacl (psd, &sacl_present, &psacl, &sacl_defaulted))
-        goto failed;
-
-    if (SetSecurityInfo (handle, SE_KERNEL_OBJECT, LABEL_SECURITY_INFORMATION,
-                         NULL, NULL, NULL, psacl) != ERROR_SUCCESS)
-        goto failed;
-
-    success = TRUE;
-    goto end;
-
-failed:
-    errsv = GetLastError ();
-    emsg = g_win32_error_message (errsv);
-    g_set_error (error, G_IO_ERROR,
-                 g_io_error_from_win32_error (errsv),
-                 "Error setting integrity: %s",
-                 emsg);
-    g_free (emsg);
-
-end:
-    if (psd != NULL)
-        LocalFree (psd);
-
-    return success;
-}
-
-static gboolean
-get_user_security_attributes (SECURITY_ATTRIBUTES* psa, SECURITY_DESCRIPTOR* psd, PACL* ppdacl)
-{
-    EXPLICIT_ACCESS ea;
-    TRUSTEE trst;
-    DWORD ret = 0;
-
-    ZeroMemory (psa, sizeof (*psa));
-    ZeroMemory (psd, sizeof (*psd));
-    psa->nLength = sizeof (*psa);
-    psa->bInheritHandle = FALSE;
-    psa->lpSecurityDescriptor = psd;
-
-    ZeroMemory (&trst, sizeof (trst));
-    trst.pMultipleTrustee = NULL;
-    trst.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE;
-    trst.TrusteeForm = TRUSTEE_IS_NAME;
-    trst.TrusteeType = TRUSTEE_IS_USER;
-    trst.ptstrName = "CURRENT_USER";
-
-    ZeroMemory (&ea, sizeof (ea));
-    ea.grfAccessPermissions = GENERIC_WRITE | GENERIC_READ;
-    ea.grfAccessMode = SET_ACCESS;
-    ea.grfInheritance = NO_INHERITANCE;
-    ea.Trustee = trst;
-
-    ret = SetEntriesInAcl (1, &ea, NULL, ppdacl);
-    if (ret != ERROR_SUCCESS)
-        return FALSE;
-
-   if (!InitializeSecurityDescriptor (psd, SECURITY_DESCRIPTOR_REVISION))
-       return FALSE;
-
-   if (!SetSecurityDescriptorDacl (psd, TRUE, *ppdacl, FALSE))
-       return FALSE;
-
-   return TRUE;
-}
-
-#define DEFAULT_PIPE_BUF_SIZE 4096
-
-SpiceNamedPipe*
-spice_win32_user_pipe_new (gchar *name, GError **error)
-{
-    SECURITY_ATTRIBUTES sa;
-    SECURITY_DESCRIPTOR sd;
-    PACL dacl = NULL;
-    HANDLE pipe;
-    SpiceNamedPipe *np = NULL;
-
-    g_return_val_if_fail (name != NULL, NULL);
-    g_return_val_if_fail (error != NULL, NULL);
-
-    if (!get_user_security_attributes (&sa, &sd, &dacl))
-        return NULL;
-
-    pipe = CreateNamedPipe (name,
-        PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED |
-    /* FIXME: why is FILE_FLAG_FIRST_PIPE_INSTANCE needed for WRITE_DAC
-     * (apparently needed by SetSecurityInfo). This will prevent
-     * multiple pipe listener....?! */
-        FILE_FLAG_FIRST_PIPE_INSTANCE | WRITE_DAC,
-        PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
-        PIPE_UNLIMITED_INSTANCES,
-        DEFAULT_PIPE_BUF_SIZE, DEFAULT_PIPE_BUF_SIZE,
-        0, &sa);
-
-    if (pipe == INVALID_HANDLE_VALUE) {
-        int errsv = GetLastError ();
-        gchar *emsg = g_win32_error_message (errsv);
-
-        g_set_error (error,
-                     G_IO_ERROR,
-                     g_io_error_from_win32_error (errsv),
-                     "Error CreateNamedPipe(): %s",
-                     emsg);
-
-        g_free (emsg);
-        goto end;
-    }
-
-    /* lower integrity on Vista/Win7+ */
-    if ((LOBYTE (g_win32_get_windows_version()) > 0x05) &&
-        !spice_win32_set_low_integrity (pipe, error))
-        goto end;
-
-    np = SPICE_NAMED_PIPE (g_initable_new (SPICE_TYPE_NAMED_PIPE,
-                                           NULL, error, "handle", pipe, NULL));
-
-end:
-    LocalFree (dacl);
-
-    return np;
-}
diff --git a/gtk/controller/win32-util.h b/gtk/controller/win32-util.h
deleted file mode 100644
index b24ac77..0000000
--- a/gtk/controller/win32-util.h
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
-   Copyright (C) 2012 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 __WIN32_UTIL_H__
-#define __WIN32_UTIL_H__
-
-#include <gio/gio.h>
-#include "namedpipe.h"
-
-G_BEGIN_DECLS
-
-gboolean        spice_win32_set_low_integrity (void* handle, GError **error);
-SpiceNamedPipe* spice_win32_user_pipe_new (gchar *name, GError **error);
-
-G_END_DECLS
-
-#endif /* __WIN32_UTIL_H__ */
diff --git a/gtk/coroutine.h b/gtk/coroutine.h
deleted file mode 100644
index 78dc467..0000000
--- a/gtk/coroutine.h
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * GTK VNC Widget
- *
- * Copyright (C) 2006  Anthony Liguori <anthony at codemonkey.ws>
- *
- * 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.0 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, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
- */
-
-#ifndef _COROUTINE_H_
-#define _COROUTINE_H_
-
-#include "config.h"
-
-#if WITH_UCONTEXT
-#include "continuation.h"
-#elif WITH_WINFIBER
-#include <windows.h>
-#else
-#include <glib.h>
-#endif
-
-struct coroutine
-{
-	size_t stack_size;
-	void *(*entry)(void *);
-	int (*release)(struct coroutine *);
-
-	/* read-only */
-	int exited;
-
-	/* private */
-	struct coroutine *caller;
-	void *data;
-
-#if WITH_UCONTEXT
-	struct continuation cc;
-#elif WITH_WINFIBER
-        LPVOID fiber;
-        int ret;
-#else
-	GThread *thread;
-	gboolean runnable;
-#endif
-};
-
-void coroutine_init(struct coroutine *co);
-
-int coroutine_release(struct coroutine *co);
-
-void *coroutine_swap(struct coroutine *from, struct coroutine *to, void *arg);
-
-struct coroutine *coroutine_self(void);
-
-void *coroutine_yieldto(struct coroutine *to, void *arg);
-
-void *coroutine_yield(void *arg);
-
-gboolean coroutine_is_main(struct coroutine *co);
-
-static inline gboolean coroutine_self_is_main(void) {
-	return coroutine_self() == NULL || coroutine_is_main(coroutine_self());
-}
-
-#endif
-/*
- * Local variables:
- *  c-indent-level: 8
- *  c-basic-offset: 8
- *  tab-width: 8
- * End:
- */
diff --git a/gtk/coroutine_gthread.c b/gtk/coroutine_gthread.c
deleted file mode 100644
index b0098fa..0000000
--- a/gtk/coroutine_gthread.c
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * GTK VNC Widget
- *
- * Copyright (C) 2006  Anthony Liguori <anthony at codemonkey.ws>
- *
- * 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.0 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, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
- */
-
-#include "config.h"
-
-#include "coroutine.h"
-#include <stdio.h>
-#include <stdlib.h>
-
-static GCond *run_cond;
-static GMutex *run_lock;
-static struct coroutine *current;
-static struct coroutine leader;
-
-#if 0
-#define CO_DEBUG(OP) fprintf(stderr, "%s %p %s %d\n", OP, g_thread_self(), __FUNCTION__, __LINE__)
-#else
-#define CO_DEBUG(OP)
-#endif
-
-static void coroutine_system_init(void)
-{
-	if (!g_thread_supported()) {
-	        CO_DEBUG("INIT");
-		g_thread_init(NULL);
-	}
-
-
-	run_cond = g_cond_new();
-	run_lock = g_mutex_new();
-	CO_DEBUG("LOCK");
-	g_mutex_lock(run_lock);
-
-	/* The thread that creates the first coroutine is the system coroutine
-	 * so let's fill out a structure for it */
-	leader.entry = NULL;
-	leader.release = NULL;
-	leader.stack_size = 0;
-	leader.exited = 0;
-	leader.thread = g_thread_self();
-	leader.runnable = TRUE; /* we're the one running right now */
-	leader.caller = NULL;
-	leader.data = NULL;
-
-	current = &leader;
-}
-
-static gpointer coroutine_thread(gpointer opaque)
-{
-	struct coroutine *co = opaque;
-	CO_DEBUG("LOCK");
-	g_mutex_lock(run_lock);
-	while (!co->runnable) {
-		CO_DEBUG("WAIT");
-		g_cond_wait(run_cond, run_lock);
-	}
-
-	CO_DEBUG("RUNNABLE");
-	current = co;
-	co->caller->data = co->entry(co->data);
-	co->exited = 1;
-
-	co->caller->runnable = TRUE;
-	CO_DEBUG("BROADCAST");
-	g_cond_broadcast(run_cond);
-	CO_DEBUG("UNLOCK");
-	g_mutex_unlock(run_lock);
-
-	return NULL;
-}
-
-void coroutine_init(struct coroutine *co)
-{
-	GError *err = NULL;
-
-	if (run_cond == NULL)
-		coroutine_system_init();
-
-	CO_DEBUG("NEW");
-	co->thread = g_thread_create_full(coroutine_thread, co, co->stack_size,
-					  FALSE, TRUE,
-					  G_THREAD_PRIORITY_NORMAL,
-					  &err);
-	if (err != NULL)
-		g_error("g_thread_create_full() failed: %s", err->message);
-
-	co->exited = 0;
-	co->runnable = FALSE;
-	co->caller = NULL;
-}
-
-int coroutine_release(struct coroutine *co G_GNUC_UNUSED)
-{
-	return 0;
-}
-
-void *coroutine_swap(struct coroutine *from, struct coroutine *to, void *arg)
-{
-	from->runnable = FALSE;
-	to->runnable = TRUE;
-	to->data = arg;
-	to->caller = from;
-	CO_DEBUG("BROADCAST");
-	g_cond_broadcast(run_cond);
-	CO_DEBUG("UNLOCK");
-	g_mutex_unlock(run_lock);
-	CO_DEBUG("LOCK");
-	g_mutex_lock(run_lock);
-	while (!from->runnable) {
-	        CO_DEBUG("WAIT");
-		g_cond_wait(run_cond, run_lock);
-	}
-	current = from;
-	to->caller = NULL;
-
-	CO_DEBUG("SWAPPED");
-	return from->data;
-}
-
-struct coroutine *coroutine_self(void)
-{
-	if (run_cond == NULL)
-		coroutine_system_init();
-
-	return current;
-}
-
-void *coroutine_yieldto(struct coroutine *to, void *arg)
-{
-	g_return_val_if_fail(!to->caller, NULL);
-	g_return_val_if_fail(!to->exited, NULL);
-
-	CO_DEBUG("SWAP");
-	return coroutine_swap(coroutine_self(), to, arg);
-}
-
-void *coroutine_yield(void *arg)
-{
-	struct coroutine *to = coroutine_self()->caller;
-	if (!to) {
-		fprintf(stderr, "Co-routine is yielding to no one\n");
-		abort();
-	}
-
-	CO_DEBUG("SWAP");
-	coroutine_self()->caller = NULL;
-	return coroutine_swap(coroutine_self(), to, arg);
-}
-
-gboolean coroutine_is_main(struct coroutine *co)
-{
-    return (co == &leader);
-}
diff --git a/gtk/coroutine_ucontext.c b/gtk/coroutine_ucontext.c
deleted file mode 100644
index d709a33..0000000
--- a/gtk/coroutine_ucontext.c
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * GTK VNC Widget
- *
- * Copyright (C) 2006  Anthony Liguori <anthony at codemonkey.ws>
- *
- * 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.0 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, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
- */
-
-#include "config.h"
-#include <glib.h>
-
-#ifdef HAVE_SYS_TYPES_H
-#include <sys/types.h>
-#endif
-#include <sys/mman.h>
-#include <errno.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include "coroutine.h"
-
-#ifndef MAP_ANONYMOUS
-# define MAP_ANONYMOUS MAP_ANON
-#endif
-
-int coroutine_release(struct coroutine *co)
-{
-	return cc_release(&co->cc);
-}
-
-static int _coroutine_release(struct continuation *cc)
-{
-	struct coroutine *co = container_of(cc, struct coroutine, cc);
-
-	if (co->release) {
-		int ret = co->release(co);
-		if (ret < 0)
-			return ret;
-	}
-
-	munmap(co->cc.stack, co->cc.stack_size);
-
-	co->caller = NULL;
-
-	return 0;
-}
-
-static void coroutine_trampoline(struct continuation *cc)
-{
-	struct coroutine *co = container_of(cc, struct coroutine, cc);
-	co->data = co->entry(co->data);
-}
-
-void coroutine_init(struct coroutine *co)
-{
-	if (co->stack_size == 0)
-		co->stack_size = 16 << 20;
-
-	co->cc.stack_size = co->stack_size;
-	co->cc.stack = mmap(0, co->stack_size,
-			    PROT_READ | PROT_WRITE,
-			    MAP_PRIVATE | MAP_ANONYMOUS,
-			    -1, 0);
-	if (co->cc.stack == MAP_FAILED)
-		g_error("mmap(%" G_GSIZE_FORMAT ") failed: %s",
-			co->stack_size, g_strerror(errno));
-
-	co->cc.entry = coroutine_trampoline;
-	co->cc.release = _coroutine_release;
-	co->exited = 0;
-
-	cc_init(&co->cc);
-}
-
-#if 0
-static __thread struct coroutine leader;
-static __thread struct coroutine *current;
-#else
-static struct coroutine leader;
-static struct coroutine *current;
-#endif
-
-struct coroutine *coroutine_self(void)
-{
-	if (current == NULL)
-		current = &leader;
-	return current;
-}
-
-void *coroutine_swap(struct coroutine *from, struct coroutine *to, void *arg)
-{
-	int ret;
-	to->data = arg;
-	current = to;
-	ret = cc_swap(&from->cc, &to->cc);
-	if (ret == 0)
-		return from->data;
-	else if (ret == 1) {
-		coroutine_release(to);
-		current = from;
-		to->exited = 1;
-		return to->data;
-	}
-
-	return NULL;
-}
-
-void *coroutine_yieldto(struct coroutine *to, void *arg)
-{
-	g_return_val_if_fail(!to->caller, NULL);
-	g_return_val_if_fail(!to->exited, NULL);
-
-	to->caller = coroutine_self();
-	return coroutine_swap(coroutine_self(), to, arg);
-}
-
-void *coroutine_yield(void *arg)
-{
-	struct coroutine *to = coroutine_self()->caller;
-	if (!to) {
-		fprintf(stderr, "Co-routine is yielding to no one\n");
-		abort();
-	}
-	coroutine_self()->caller = NULL;
-	return coroutine_swap(coroutine_self(), to, arg);
-}
-
-gboolean coroutine_is_main(struct coroutine *co)
-{
-    return (co == &leader);
-}
-/*
- * Local variables:
- *  c-indent-level: 8
- *  c-basic-offset: 8
- *  tab-width: 8
- * End:
- */
diff --git a/gtk/coroutine_winfibers.c b/gtk/coroutine_winfibers.c
deleted file mode 100644
index a56d33d..0000000
--- a/gtk/coroutine_winfibers.c
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * SpiceGtk coroutine with Windows fibers
- *
- * Copyright (C) 2011  Marc-André Lureau <marcandre.lureau at redhat.com>
- *
- * 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.0 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, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
- */
-
-#include "config.h"
-#include <stdio.h>
-#include <glib.h>
-
-#include "coroutine.h"
-
-static struct coroutine leader = { 0, };
-static struct coroutine *current = NULL;
-static struct coroutine *caller = NULL;
-
-int coroutine_release(struct coroutine *co)
-{
-	DeleteFiber(co->fiber);
-	return 0;
-}
-
-static void WINAPI coroutine_trampoline(LPVOID lpParameter)
-{
-	struct coroutine *co = (struct coroutine *)lpParameter;
-
-	co->data = co->entry(co->data);
-
-	if (co->release)
-		co->ret = co->release(co);
-	else
-		co->ret = 0;
-
-	co->caller = NULL;
-
-	// and switch back to caller
-	co->ret = 1;
-	SwitchToFiber(caller->fiber);
-}
-
-void coroutine_init(struct coroutine *co)
-{
-	if (leader.fiber == NULL) {
-		leader.fiber = ConvertThreadToFiber(&leader);
-		if (leader.fiber == NULL)
-			g_error("ConvertThreadToFiber() failed");
-	}
-
-	co->exited = 0;
-	co->fiber = CreateFiber(0, &coroutine_trampoline, co);
-	if (co->fiber == NULL)
-		g_error("CreateFiber() failed");
-
-	co->ret = 0;
-}
-
-struct coroutine *coroutine_self(void)
-{
-	if (current == NULL)
-		current = &leader;
-	return current;
-}
-
-void *coroutine_swap(struct coroutine *from, struct coroutine *to, void *arg)
-{
-	to->data = arg;
-	current = to;
-	caller = from;
-	SwitchToFiber(to->fiber);
-	if (to->ret == 0)
-		return from->data;
-	else if (to->ret == 1) {
-		coroutine_release(to);
-		current = &leader;
-		to->exited = 1;
-		return to->data;
-	}
-
-	return NULL;
-}
-
-void *coroutine_yieldto(struct coroutine *to, void *arg)
-{
-	g_return_val_if_fail(!to->caller, NULL);
-	g_return_val_if_fail(!to->exited, NULL);
-
-	to->caller = coroutine_self();
-	return coroutine_swap(coroutine_self(), to, arg);
-}
-
-void *coroutine_yield(void *arg)
-{
-	struct coroutine *to = coroutine_self()->caller;
-	if (!to) {
-		fprintf(stderr, "Co-routine is yielding to no one\n");
-		abort();
-	}
-	coroutine_self()->caller = NULL;
-	return coroutine_swap(coroutine_self(), to, arg);
-}
-
-gboolean coroutine_is_main(struct coroutine *co)
-{
-    return (co == &leader);
-}
-/*
- * Local variables:
- *  c-indent-level: 8
- *  c-basic-offset: 8
- *  tab-width: 8
- * End:
- */
diff --git a/gtk/decode-glz-tmpl.c b/gtk/decode-glz-tmpl.c
deleted file mode 100644
index b337a8b..0000000
--- a/gtk/decode-glz-tmpl.c
+++ /dev/null
@@ -1,336 +0,0 @@
-/*
-   Copyright (C) 2009 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/>.
-*/
-
-// External defines: PLT, RGBX/PLTXX/ALPHA, TO_RGB32.
-// If PLT4/1 and TO_RGB32 are defined, we need CAST_PLT_DISTANCE (
-// because then the number of pixels differ from the units used in the compression)
-
-/*
-    For each output pixel type the following macros are defined:
-    OUT_PIXEL                      - the output pixel type
-    COPY_PIXEL(p, out)              - assigns the pixel to the place pointed by out and
-                                      increases out. Used in RLE.
-                                      Need special handling because in alpha we copy only
-                                      the pad byte.
-    COPY_REF_PIXEL(ref, out)      - copies the pixel pointed by ref to the pixel pointed by out.
-                                    Increases ref and out.
-    COPY_COMP_PIXEL(encoder, out) - copies pixel from the compressed buffer to the decompressed
-                                    buffer. Increases out.
-*/
-
-#if !defined(LZ_RGB_ALPHA)
-#define COPY_PIXEL(p, out) (*(out++) = p)
-#define COPY_REF_PIXEL(ref, out) (*(out++) = *(ref++))
-#endif
-
-// decompressing plt to plt
-#ifdef LZ_PLT
-#ifndef TO_RGB32
-#define OUT_PIXEL one_byte_pixel_t
-#define FNAME(name) glz_plt_##name
-#define COPY_COMP_PIXEL(in, out) {(out)->a = *(in++); out++;}
-#else // TO_RGB32
-#define OUT_PIXEL rgb32_pixel_t
-#define COPY_PLT_ENTRY(ent, out) {\
-    (out)->b = ent; (out)->g = (ent >> 8); (out)->r = (ent >> 16); (out)->pad = 0;}
-#ifdef PLT8
-#define FNAME(name) glz_plt8_to_rgb32_##name
-#define COPY_COMP_PIXEL(in, out, palette) {         \
-    uint32_t rgb = palette->ents[*(in++)];          \
-    COPY_PLT_ENTRY(rgb, out);                       \
-    out++;                                          \
-}
-#elif defined(PLT4_BE)
-#define FNAME(name) glz_plt4_be_to_rgb32_##name
-#define COPY_COMP_PIXEL(in, out, palette){                                    \
-    uint8_t byte = *(in++);                                                   \
-    uint32_t rgb = palette->ents[((byte >> 4) & 0x0f) % (palette->num_ents)]; \
-    COPY_PLT_ENTRY(rgb, out);                                                 \
-    out++;                                                                    \
-    rgb = palette->ents[(byte & 0x0f) % (palette->num_ents)];                 \
-    COPY_PLT_ENTRY(rgb, out);                                                 \
-    out++;                                                                    \
-}
-#define CAST_PLT_DISTANCE(dist) (dist*2)
-#elif  defined(PLT4_LE)
-#define FNAME(name) glz_plt4_le_to_rgb32_##name
-#define COPY_COMP_PIXEL(in, out, palette){                                \
-    uint8_t byte = *(in++);                                               \
-    uint32_t rgb = palette->ents[(byte & 0x0f) % (palette->num_ents)];    \
-    COPY_PLT_ENTRY(rgb, out);                                             \
-    out++;                                                                \
-    rgb = palette->ents[((byte >> 4) & 0x0f) % (palette->num_ents)];      \
-    COPY_PLT_ENTRY(rgb, out);                                             \
-    out++;                                                                \
-}
-#define CAST_PLT_DISTANCE(dist) (dist*2)
-#elif defined(PLT1_BE) // TODO store palette entries for direct access
-#define FNAME(name) glz_plt1_be_to_rgb32_##name
-#define COPY_COMP_PIXEL(in, out, palette){                                \
-    uint8_t byte = *(in++);                                               \
-    int i;                                                                \
-    uint32_t fore = palette->ents[1];                                     \
-    uint32_t back = palette->ents[0];                                     \
-    for (i = 7; i >= 0; i--)                                              \
-    {                                                                     \
-        if ((byte >> i) & 1) {                                            \
-            COPY_PLT_ENTRY(fore, out);                                    \
-        } else {                                                          \
-            COPY_PLT_ENTRY(back, out);                                    \
-        }                                                                 \
-        out++;                                                            \
-    }                                                                     \
-}
-#define CAST_PLT_DISTANCE(dist) (dist*8)
-#elif defined(PLT1_LE)
-#define FNAME(name) glz_plt1_le_to_rgb32_##name
-#define COPY_COMP_PIXEL(in, out, palette){                                \
-    uint8_t byte = *(in++);                                               \
-    int i;                                                                \
-    uint32_t fore = palette->ents[1];                                     \
-    uint32_t back = palette->ents[0];                                     \
-    for (i = 0; i < 8; i++)                                               \
-    {                                                                     \
-        if ((byte >> i) & 1) {                                            \
-            COPY_PLT_ENTRY(fore, out);                                    \
-        } else {                                                          \
-            COPY_PLT_ENTRY(back, out);                                    \
-        }                                                                 \
-        out++;                                                            \
-    }                                                                     \
-}
-#define CAST_PLT_DISTANCE(dist) (dist*8)
-#endif // PLT Type
-#endif // TO_RGB32
-#endif
-
-#ifdef LZ_RGB16
-#ifndef TO_RGB32
-#define OUT_PIXEL rgb16_pixel_t
-#define FNAME(name) glz_rgb16_##name
-#define COPY_COMP_PIXEL(in, out) {*out = (*(in++)) << 8; *out |= *(in++); out++;}
-#else
-#define OUT_PIXEL rgb32_pixel_t
-#define FNAME(name) glz_rgb16_to_rgb32_##name
-#define COPY_COMP_PIXEL(in, out) {out->r = *(in++); out->b= *(in++);       \
-    out->g = (((out->r) << 6) | ((out->b) >> 2)) & ~0x07;                  \
-    out->g |= (out->g >> 5);                                               \
-    out->r = ((out->r << 1) & ~0x07) | ((out->r >> 4) & 0x07) ;            \
-    out->b = (out->b << 3) | ((out->b >> 2) & 0x07);                       \
-    out->pad = 0;                                                          \
-    out++;                                                                 \
-}
-#endif
-#endif
-
-#ifdef LZ_RGB24
-#define OUT_PIXEL rgb24_pixel_t
-#define FNAME(name) glz_rgb24_##name
-#define COPY_COMP_PIXEL(in, out) {  \
-    out->b = *(in++);               \
-    out->g = *(in++);               \
-    out->r = *(in++);               \
-    out++;                          \
-}
-#endif
-
-#ifdef LZ_RGB32
-#define OUT_PIXEL rgb32_pixel_t
-#define FNAME(name) glz_rgb32_##name
-#define COPY_COMP_PIXEL(in, out) {  \
-    out->b = *(in++);               \
-    out->g = *(in++);               \
-    out->r = *(in++);               \
-    out->pad = 0;                   \
-    out++;                          \
-}
-#endif
-
-#ifdef LZ_RGB_ALPHA
-#define OUT_PIXEL rgb32_pixel_t
-#define FNAME(name) glz_rgb_alpha_##name
-#define COPY_PIXEL(p, out) {out->pad = p.pad; out++;}
-#define COPY_REF_PIXEL(ref, out) {out->pad = ref->pad; out++; ref++;}
-#define COPY_COMP_PIXEL(in, out) {out->pad = *(in++); out++;}
-#endif
-
-// TODO: separate into routines that decode to dist,len. and to a routine that
-// actually copies the data.
-
-/* returns num of bytes read from in buf.
-   size should be in PIXEL */
-static size_t FNAME(decode)(SpiceGlzDecoderWindow *window,
-                            uint8_t* in_buf, uint8_t *out_buf, int size,
-                            uint64_t image_id, SpicePalette *plt)
-{
-    uint8_t      *ip = in_buf;
-    OUT_PIXEL    *out_pix_buf = (OUT_PIXEL *)out_buf;
-    OUT_PIXEL    *op = out_pix_buf;
-    OUT_PIXEL    *op_limit = out_pix_buf + size;
-
-    uint32_t ctrl = *(ip++);
-    int loop = true;
-
-    do {
-        if (ctrl >= MAX_COPY) { // reference (dictionary/RLE)
-            OUT_PIXEL *ref = op;
-            uint32_t len = ctrl >> 5;
-            uint8_t pixel_flag = (ctrl >> 4) & 0x01;
-            uint32_t pixel_ofs = (ctrl & 0x0f);
-            uint8_t image_flag;
-            uint32_t image_dist;
-
-            /* retrieving the referenced images, the offset of the first pixel,
-               and the match length */
-
-            uint8_t code;
-            //len--; // TODO: why do we do this?
-
-            if (len == 7) { // match length is bigger than 7
-                do {
-                    code = *(ip++);
-                    len += code;
-                } while (code == 255); // remaining of len
-            }
-            code = *(ip++);
-            pixel_ofs += (code << 4);
-
-            code = *(ip++);
-            image_flag = (code >> 6) & 0x03;
-            if (!pixel_flag) { // short pixel offset
-                int i;
-                image_dist = code & 0x3f;
-                for (i = 0; i < image_flag; i++) {
-                    code = *(ip++);
-                    image_dist += (code << (6 + (8 * i)));
-                }
-            } else {
-                int i;
-                pixel_flag = (code >> 5) & 0x01;
-                pixel_ofs += (code & 0x1f) << 12;
-                image_dist = 0;
-                for (i = 0; i < image_flag; i++) {
-                    code = *(ip++);
-                    image_dist += (code << 8 * i);
-                }
-
-
-                if (pixel_flag) { // very long pixel offset
-                    code = *(ip++);
-                    pixel_ofs += code << 17;
-                }
-            }
-
-#if defined(LZ_PLT) || defined(LZ_RGB_ALPHA)
-            len += 2; // length is biased by 2 (fixing bias)
-#elif defined(LZ_RGB16)
-            len += 1; // length is biased by 1  (fixing bias)
-#endif
-            if (!image_dist) {
-                pixel_ofs += 1; // offset is biased by 1 (fixing bias)
-            }
-
-#if defined(TO_RGB32)
-#if defined(PLT4_BE) || defined(PLT4_LE) || defined(PLT1_BE) || defined(PLT1_LE)
-            pixel_ofs = CAST_PLT_DISTANCE(pixel_ofs);
-            len = CAST_PLT_DISTANCE(len);
-#endif
-#endif
-
-            if (!image_dist) { // reference is inside the same image
-                ref -= pixel_ofs;
-                g_return_val_if_fail(ref + len <= op_limit, 0);
-                g_return_val_if_fail(ref >= out_pix_buf, 0);
-            } else {
-                ref = glz_decoder_window_bits(window, image_id,
-                                              image_dist, pixel_ofs);
-            }
-
-            g_return_val_if_fail(ref != NULL, 0);
-            g_return_val_if_fail(op + len <= op_limit, 0);
-
-            /* copying the match*/
-
-            if (ref == (op - 1)) { // run (this will never be called in PLT4/1_TO_RGB because the
-                                  // number of pixel copied is larger then one...
-                /* optimize copy for a run */
-                OUT_PIXEL b = *ref;
-                for (; len; --len) {
-                    COPY_PIXEL(b, op);
-                    g_return_val_if_fail(op <= op_limit, 0);
-                }
-            } else {
-                for (; len; --len) {
-                    COPY_REF_PIXEL(ref, op);
-                    g_return_val_if_fail(op <= op_limit, 0);
-                }
-            }
-        } else { // copy
-            ctrl++; // copy count is biased by 1
-#if defined(TO_RGB32) && (defined(PLT4_BE) || defined(PLT4_LE) || defined(PLT1_BE) || \
-                                                                                   defined(PLT1_LE))
-            g_return_val_if_fail(op + CAST_PLT_DISTANCE(ctrl) <= op_limit, 0);
-#else
-            g_return_val_if_fail(op + ctrl <= op_limit, 0);
-#endif
-
-#if defined(TO_RGB32) && defined(LZ_PLT)
-            g_return_val_if_fail(plt, 0);
-            COPY_COMP_PIXEL(ip, op, plt);
-#else
-            COPY_COMP_PIXEL(ip, op);
-#endif
-            g_return_val_if_fail(op <= op_limit, 0);
-
-            for (--ctrl; ctrl; ctrl--) {
-#if defined(TO_RGB32) && defined(LZ_PLT)
-                g_return_val_if_fail(plt, 0);
-                COPY_COMP_PIXEL(ip, op, plt);
-#else
-                COPY_COMP_PIXEL(ip, op);
-#endif
-                g_return_val_if_fail(op <= op_limit, 0);
-            }
-        } // END REF/COPY
-
-        if (LZ_EXPECT_CONDITIONAL(op < op_limit)) {
-            ctrl = *(ip++);
-        } else {
-            loop = false;
-        }
-    } while (LZ_EXPECT_CONDITIONAL(loop));
-
-    return (ip - in_buf);
-}
-#undef LZ_PLT
-#undef PLT8
-#undef PLT4_BE
-#undef PLT4_LE
-#undef PLT1_BE
-#undef PLT1_LE
-#undef LZ_RGB16
-#undef LZ_RGB24
-#undef LZ_RGB32
-#undef LZ_RGB_ALPHA
-#undef TO_RGB32
-#undef OUT_PIXEL
-#undef FNAME
-#undef COPY_PIXEL
-#undef COPY_REF_PIXEL
-#undef COPY_COMP_PIXEL
-#undef COPY_PLT_ENTRY
-#undef CAST_PLT_DISTANCE
diff --git a/gtk/decode-glz.c b/gtk/decode-glz.c
deleted file mode 100644
index 34a7185..0000000
--- a/gtk/decode-glz.c
+++ /dev/null
@@ -1,475 +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 <stdio.h>
-#include <stdbool.h>
-#include <inttypes.h>
-
-#include <glib.h>
-
-#include "gio-coroutine.h"
-#include "spice-util.h"
-#include "decode.h"
-
-#include "common/canvas_utils.h"
-
-struct glz_image_hdr {
-    uint64_t                id;
-    LzImageType             type;
-    uint32_t                width;
-    uint32_t                height;
-    uint32_t                gross_pixels;
-    bool                    top_down;
-    uint32_t                win_head_dist;
-};
-
-struct glz_image {
-    struct glz_image_hdr    hdr;
-    pixman_image_t          *surface;
-    uint8_t                 *data;
-};
-
-static struct glz_image *glz_image_new(struct glz_image_hdr *hdr,
-                                       int type, void *opaque)
-{
-    struct glz_image *img;
-
-    g_return_val_if_fail(type == LZ_IMAGE_TYPE_RGB32 || type == LZ_IMAGE_TYPE_RGBA, NULL);
-
-    img = g_new0(struct glz_image, 1);
-    img->hdr = *hdr;
-    img->surface = alloc_lz_image_surface
-        (opaque, type == LZ_IMAGE_TYPE_RGBA ? PIXMAN_a8r8g8b8 : PIXMAN_x8r8g8b8,
-         img->hdr.width, img->hdr.height, img->hdr.gross_pixels, img->hdr.top_down);
-    pixman_image_ref(img->surface);
-    img->data = (uint8_t *)pixman_image_get_data(img->surface);
-    if (!img->hdr.top_down) {
-        img->data = img->data - img->hdr.width * (img->hdr.height - 1) * 4;
-    }
-    return img;
-}
-
-static void glz_image_destroy(struct glz_image *img)
-{
-    if (img == NULL)
-        return;
-
-    pixman_image_unref(img->surface);
-    free(img);
-}
-
-/* ------------------------------------------------------------------ */
-
-#define INIT_IMAGES_CAPACITY 100
-#define WIN_OVERFLOW_FACTOR 1.5
-#define WIN_REALLOC_FACTOR 1.5
-
-struct SpiceGlzDecoderWindow {
-    struct glz_image        **images;
-    uint32_t                nimages;
-    uint64_t                oldest;
-    uint64_t                tail_gap;
-};
-
-static void glz_decoder_window_resize(SpiceGlzDecoderWindow *w)
-{
-    struct glz_image  **new_images;
-    int i, new_slot;
-
-    SPICE_DEBUG("%s: array resize %d -> %d", __FUNCTION__,
-                w->nimages, w->nimages * 2);
-    new_images = g_new0(struct glz_image*, w->nimages * 2);
-    for (i = 0; i < w->nimages; i++) {
-        if (w->images[i] == NULL) {
-            /*
-             * We can have empty slots when images come in out of order, this
-             * can happen when a vm has multiple displays, since each display
-             * uses its own socket there is no guarantee that images
-             * originating from different displays are received in id order.
-             */
-            continue;
-        }
-        new_slot = w->images[i]->hdr.id % (w->nimages * 2);
-        new_images[new_slot] = w->images[i];
-    }
-    free(w->images);
-    w->images = new_images;
-    w->nimages *= 2;
-}
-
-static void glz_decoder_window_add(SpiceGlzDecoderWindow *w,
-                                   struct glz_image *img)
-{
-    int slot = img->hdr.id % w->nimages;
-
-    if (w->images[slot]) {
-        /* need more space */
-        glz_decoder_window_resize(w);
-        slot = img->hdr.id % w->nimages;
-    }
-
-    w->images[slot] = img;
-
-    /* close the gap */
-    while (w->tail_gap <= img->hdr.id && w->images[w->tail_gap % w->nimages] != NULL)
-        w->tail_gap++;
-}
-
-struct wait_for_image_data {
-    SpiceGlzDecoderWindow     *window;
-    uint64_t                   id;
-};
-
-static gboolean wait_for_image(gpointer data)
-{
-    struct wait_for_image_data *wait = data;
-    int slot = wait->id % wait->window->nimages;
-    struct glz_image *image = wait->window->images[slot];
-    gboolean ready = image && image->hdr.id == wait->id;
-
-    return ready;
-}
-
-static void *glz_decoder_window_bits(SpiceGlzDecoderWindow *w, uint64_t id,
-                                     uint32_t dist, uint32_t offset)
-{
-    struct wait_for_image_data data = {
-        .window = w,
-        .id = id - dist,
-    };
-
-    if (!g_coroutine_condition_wait(g_coroutine_self(), wait_for_image, &data))
-        SPICE_DEBUG("wait for image cancelled");
-
-    int slot = (id - dist) % w->nimages;
-
-    g_return_val_if_fail(w->images[slot] != NULL, NULL);
-    g_return_val_if_fail(w->images[slot]->hdr.id == id - dist, NULL);
-    g_return_val_if_fail(w->images[slot]->hdr.gross_pixels >= offset, NULL);
-
-    return w->images[slot]->data + offset * 4;
-}
-
-static void glz_decoder_window_release(SpiceGlzDecoderWindow *w,
-                                       uint64_t oldest)
-{
-    int slot;
-
-    while (w->oldest < oldest) {
-        slot = w->oldest % w->nimages;
-        glz_image_destroy(w->images[slot]);
-        w->images[slot] = NULL;
-        w->oldest++;
-    }
-}
-
-/* ------------------------------------------------------------------ */
-
-typedef struct GlibGlzDecoder {
-    SpiceGlzDecoder         base;
-    uint8_t                 *in_start;
-    uint8_t                 *in_now;
-    SpiceGlzDecoderWindow   *window;
-    struct glz_image_hdr    image;
-} GlibGlzDecoder;
-
-/*
- * Give hints to the compiler for branch prediction optimization.
- */
-#if defined(__GNUC__) && (__GNUC__ > 2)
-#define LZ_EXPECT_CONDITIONAL(c) (__builtin_expect((c), 1))
-#define LZ_UNEXPECT_CONDITIONAL(c) (__builtin_expect((c), 0))
-#else
-#define LZ_EXPECT_CONDITIONAL(c) (c)
-#define LZ_UNEXPECT_CONDITIONAL(c) (c)
-#endif
-
-
-#ifdef __GNUC__
-#define ATTR_PACKED __attribute__ ((__packed__))
-#else
-#define ATTR_PACKED
-#pragma pack(push)
-#pragma pack(1)
-#endif
-
-/*
- * the palette images will be treated as one byte pixels. Their width
- * should be transformed accordingly.
- */
-typedef struct ATTR_PACKED one_byte_pixel_t {
-    uint8_t a;
-} one_byte_pixel_t;
-
-typedef struct ATTR_PACKED rgb32_pixel_t {
-    uint8_t b;
-    uint8_t g;
-    uint8_t r;
-    uint8_t pad;
-} rgb32_pixel_t;
-
-typedef struct ATTR_PACKED rgb24_pixel_t {
-    uint8_t b;
-    uint8_t g;
-    uint8_t r;
-} rgb24_pixel_t;
-
-typedef uint16_t rgb16_pixel_t;
-
-#ifndef __GNUC__
-#pragma pack(pop)
-#endif
-
-#undef ATTR_PACKED
-
-#define LZ_PLT
-#include "decode-glz-tmpl.c"
-
-#define LZ_PLT
-#define PLT8
-#define TO_RGB32
-#include "decode-glz-tmpl.c"
-
-#define LZ_PLT
-#define PLT4_BE
-#define TO_RGB32
-#include "decode-glz-tmpl.c"
-
-#define LZ_PLT
-#define PLT4_LE
-#define TO_RGB32
-#include "decode-glz-tmpl.c"
-
-#define LZ_PLT
-#define PLT1_BE
-#define TO_RGB32
-#include "decode-glz-tmpl.c"
-
-#define LZ_PLT
-#define PLT1_LE
-#define TO_RGB32
-#include "decode-glz-tmpl.c"
-
-
-#define LZ_RGB16
-#include "decode-glz-tmpl.c"
-#define LZ_RGB16
-#define TO_RGB32
-#include "decode-glz-tmpl.c"
-
-#define LZ_RGB24
-#include "decode-glz-tmpl.c"
-
-#define LZ_RGB32
-#include "decode-glz-tmpl.c"
-
-#define LZ_RGB_ALPHA
-#include "decode-glz-tmpl.c"
-
-#undef LZ_UNEXPECT_CONDITIONAL
-#undef LZ_EXPECT_CONDITIONAL
-
-typedef size_t (*decode_function)(SpiceGlzDecoderWindow *window,
-                                  uint8_t* in_buf, uint8_t *out_buf, int size,
-                                  uint64_t id, SpicePalette *plt);
-
-// ordered according to LZ_IMAGE_TYPE
-const decode_function DECODE_TO_RGB32[] = {
-    NULL,
-    glz_plt1_le_to_rgb32_decode,
-    glz_plt1_be_to_rgb32_decode,
-    glz_plt4_le_to_rgb32_decode,
-    glz_plt4_be_to_rgb32_decode,
-    glz_plt8_to_rgb32_decode,
-    glz_rgb16_to_rgb32_decode,
-    glz_rgb32_decode,
-    glz_rgb32_decode,
-    glz_rgb32_decode
-};
-
-const decode_function DECODE_TO_SAME[] = {
-    NULL,
-    glz_plt_decode,
-    glz_plt_decode,
-    glz_plt_decode,
-    glz_plt_decode,
-    glz_plt_decode,
-    glz_rgb16_decode,
-    glz_rgb24_decode,
-    glz_rgb32_decode,
-    glz_rgb32_decode
-};
-
-static uint32_t decode_32(GlibGlzDecoder *d)
-{
-    uint32_t word = 0;
-    word |= *(d->in_now++);
-    word <<= 8;
-    word |= *(d->in_now++);
-    word <<= 8;
-    word |= *(d->in_now++);
-    word <<= 8;
-    word |= *(d->in_now++);
-    return word;
-}
-
-static uint64_t decode_64(GlibGlzDecoder *d)
-{
-    uint64_t long_word = decode_32(d);
-    long_word <<= 32;
-    long_word |= decode_32(d);
-    return long_word;
-}
-
-static void decode_header(GlibGlzDecoder *d)
-{
-    uint32_t magic;
-    uint32_t version;
-    uint32_t stride;
-    uint8_t tmp;
-
-    magic = decode_32(d);
-    g_return_if_fail(magic == LZ_MAGIC);
-
-    version = decode_32(d);
-    g_return_if_fail(version == LZ_VERSION);
-
-    tmp = *(d->in_now++);
-
-    d->image.type = (LzImageType)(tmp & LZ_IMAGE_TYPE_MASK);
-    d->image.top_down = (tmp >> LZ_IMAGE_TYPE_LOG) ? true : false;
-    d->image.width = decode_32(d);
-    d->image.height = decode_32(d);
-    stride = decode_32(d);
-
-    if (IS_IMAGE_TYPE_PLT[d->image.type]) {
-        d->image.gross_pixels = stride * PLT_PIXELS_PER_BYTE[d->image.type]
-            * d->image.height;
-    } else {
-        d->image.gross_pixels = d->image.width * d->image.height;
-    }
-
-    d->image.id = decode_64(d);
-    d->image.win_head_dist = decode_32(d);
-
-    SPICE_DEBUG("%s: %dx%d, id %" PRId64 ", ref %" PRId64,
-            __FUNCTION__,
-            d->image.width, d->image.height, d->image.id,
-            d->image.id - d->image.win_head_dist);
-}
-
-static void decode(SpiceGlzDecoder *decoder,
-                   uint8_t *data, SpicePalette *palette,
-                   void *usr_data)
-{
-    GlibGlzDecoder *d = SPICE_CONTAINEROF(decoder, GlibGlzDecoder, base);
-    LzImageType decoded_type;
-    struct glz_image *decoded_image;
-    size_t n_in_bytes_decoded;
-
-    d->in_start = data;
-    d->in_now = data;
-
-    decode_header(d);
-
-    if (d->image.type == LZ_IMAGE_TYPE_RGBA) {
-        decoded_type = LZ_IMAGE_TYPE_RGBA;
-    } else {
-        decoded_type = LZ_IMAGE_TYPE_RGB32;
-    }
-
-    decoded_image = glz_image_new(&d->image, decoded_type, usr_data);
-
-    n_in_bytes_decoded = DECODE_TO_RGB32[d->image.type]
-        (d->window, d->in_now, decoded_image->data,
-         d->image.gross_pixels, d->image.id, palette);
-
-    d->in_now += n_in_bytes_decoded;
-
-    if (d->image.type == LZ_IMAGE_TYPE_RGBA) {
-        glz_rgb_alpha_decode(d->window, d->in_now, decoded_image->data,
-                             d->image.gross_pixels, d->image.id, palette);
-    }
-
-    glz_decoder_window_add(d->window, decoded_image);
-
-    { /* release old images from last tail_gap, only if the gap is closed  */
-        uint64_t oldest;
-        struct glz_image *image = d->window->images[(d->window->tail_gap - 1) % d->window->nimages];
-
-        g_return_if_fail(image != NULL);
-
-        oldest = image->hdr.id - image->hdr.win_head_dist;
-        glz_decoder_window_release(d->window, oldest);
-    }
-}
-
-/* ------------------------------------------------------------------ */
-
-static SpiceGlzDecoderOps glz_decoder_ops = {
-    .decode = decode,
-};
-
-void glz_decoder_window_clear(SpiceGlzDecoderWindow *w)
-{
-    int i;
-
-    g_return_if_fail(w->nimages == 0 || w->images != NULL);
-
-    for (i = 0; i < w->nimages; i++) {
-        if (w->images[i]) {
-            glz_image_destroy(w->images[i]);
-        }
-    }
-
-    w->nimages = 16;
-    g_free(w->images);
-    w->images = g_new0(struct glz_image*, w->nimages);
-    w->tail_gap = 0;
-}
-
-SpiceGlzDecoderWindow *glz_decoder_window_new(void)
-{
-    SpiceGlzDecoderWindow *w = g_new0(SpiceGlzDecoderWindow, 1);
-    glz_decoder_window_clear(w);
-    return w;
-}
-
-void glz_decoder_window_destroy(SpiceGlzDecoderWindow *w)
-{
-    if (w == NULL)
-        return;
-
-    glz_decoder_window_clear(w);
-    free(w->images);
-    free(w);
-}
-
-SpiceGlzDecoder *glz_decoder_new(SpiceGlzDecoderWindow *w)
-{
-    GlibGlzDecoder *d = g_new0(GlibGlzDecoder, 1);
-    d->base.ops = &glz_decoder_ops;
-    d->window = w;
-    return &d->base;
-}
-
-void glz_decoder_destroy(SpiceGlzDecoder *d)
-{
-    free(d);
-}
diff --git a/gtk/decode-jpeg.c b/gtk/decode-jpeg.c
deleted file mode 100644
index 697d0de..0000000
--- a/gtk/decode-jpeg.c
+++ /dev/null
@@ -1,191 +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 "decode.h"
-
-#ifdef G_OS_WIN32
-/* We need some hacks to avoid warnings from the jpeg headers, ex: */
-/* #define HAVE_BOOLEAN */
-#define XMD_H
-/* #undef FAR */
-/* but they are not compatible: uchar vs int........!@@(#$$??!@! */
-/* fix this with UGLY HACK! */
-/* #define boolean spice_jpeg_boolean */
-/* #define INT32 spice_jpeg_int32 */
-#endif
-
-#include <stdio.h>
-#include <jpeglib.h>
-
-typedef struct GlibJpegDecoder
-{
-    SpiceJpegDecoder              base;
-    struct jpeg_decompress_struct _cinfo;
-    struct jpeg_error_mgr         _jerr;
-    struct jpeg_source_mgr        _jsrc;
-
-    uint8_t* _data;
-    int      _data_size;
-    int      _width;
-    int      _height;
-} GlibJpegDecoder;
-
-static void begin_decode(SpiceJpegDecoder *decoder,
-                         uint8_t* data, int data_size,
-                         int* out_width, int* out_height)
-{
-    GlibJpegDecoder *d = SPICE_CONTAINEROF(decoder, GlibJpegDecoder, base);
-
-    g_return_if_fail(data != NULL);
-    g_return_if_fail(data_size != 0);
-
-    if (d->_data)
-        jpeg_abort_decompress(&d->_cinfo);
-
-    d->_data = data;
-    d->_data_size = data_size;
-
-    d->_cinfo.src->next_input_byte = d->_data;
-    d->_cinfo.src->bytes_in_buffer = d->_data_size;
-
-    jpeg_read_header(&d->_cinfo, TRUE);
-
-    d->_cinfo.out_color_space = JCS_RGB;
-    d->_width = d->_cinfo.image_width;
-    d->_height = d->_cinfo.image_height;
-
-    *out_width = d->_width;
-    *out_height = d->_height;
-}
-
-/* TODO: move it elsewhere and reuse it in get_pixbuf(), optimize? */
-typedef void (*converter_rgb_t)(uint8_t* src, uint8_t* dest, int width);
-
-static void convert_rgb_to_bgr(uint8_t* src, uint8_t* dest, int width)
-{
-    int x;
-
-    for (x = 0; x < width; x++) {
-        *dest++ = src[2];
-        *dest++ = src[1];
-        *dest++ = src[0];
-        src += 3;
-    }
-}
-
-static void convert_rgb_to_bgrx(uint8_t* src, uint8_t* dest, int width)
-{
-    int x;
-
-    for (x = 0; x < width; x++) {
-        *dest++ = src[2];
-        *dest++ = src[1];
-        *dest++ = src[0];
-        *dest++ = 0;
-        src += 3;
-    }
-}
-
-static void decode(SpiceJpegDecoder *decoder,
-                   uint8_t* dest, int stride, int format)
-{
-    GlibJpegDecoder *d = SPICE_CONTAINEROF(decoder, GlibJpegDecoder, base);
-    uint8_t* scan_line = g_alloca(d->_width * 3);
-    converter_rgb_t converter = NULL;
-    int row;
-
-    switch (format) {
-    case SPICE_BITMAP_FMT_24BIT:
-        converter = convert_rgb_to_bgr;
-        break;
-    case SPICE_BITMAP_FMT_32BIT:
-        converter = convert_rgb_to_bgrx;
-        break;
-    default:
-        g_warning("bad bitmap format, %d", format);
-        return;
-    }
-
-    g_return_if_fail(converter != NULL);
-
-    jpeg_start_decompress(&d->_cinfo);
-
-    for (row = 0; row < d->_height; row++) {
-        jpeg_read_scanlines(&d->_cinfo, &scan_line, 1);
-        converter(scan_line, dest, d->_width);
-        dest += stride;
-    }
-
-    jpeg_finish_decompress(&d->_cinfo);
-}
-
-static SpiceJpegDecoderOps jpeg_decoder_ops = {
-    .begin_decode = begin_decode,
-    .decode = decode,
-};
-
-static void jpeg_decoder_init_source(j_decompress_ptr cinfo)
-{
-}
-
-static boolean jpeg_decoder_fill_input_buffer(j_decompress_ptr cinfo)
-{
-    g_warning("no more data for jpeg");
-    return FALSE;
-}
-
-static void jpeg_decoder_skip_input_data(j_decompress_ptr cinfo, long num_bytes)
-{
-    g_return_if_fail(num_bytes < (long)cinfo->src->bytes_in_buffer);
-
-    cinfo->src->next_input_byte += num_bytes;
-    cinfo->src->bytes_in_buffer -= num_bytes;
-}
-
-static void jpeg_decoder_term_source (j_decompress_ptr cinfo)
-{
-    return;
-}
-
-SpiceJpegDecoder *jpeg_decoder_new(void)
-{
-    GlibJpegDecoder *d = g_new0(GlibJpegDecoder, 1);
-
-    d->_cinfo.err = jpeg_std_error(&d->_jerr);
-    jpeg_create_decompress(&d->_cinfo);
-
-    d->_cinfo.src = &d->_jsrc;
-    d->_cinfo.src->init_source = jpeg_decoder_init_source;
-    d->_cinfo.src->fill_input_buffer = jpeg_decoder_fill_input_buffer;
-    d->_cinfo.src->skip_input_data = jpeg_decoder_skip_input_data;
-    d->_cinfo.src->resync_to_restart = jpeg_resync_to_restart;
-    d->_cinfo.src->term_source = jpeg_decoder_term_source;
-
-    d->base.ops = &jpeg_decoder_ops;
-
-    return &d->base;
-}
-
-void jpeg_decoder_destroy(SpiceJpegDecoder *decoder)
-{
-    GlibJpegDecoder *d = SPICE_CONTAINEROF(decoder, GlibJpegDecoder, base);
-
-    jpeg_destroy_decompress(&d->_cinfo);
-    free(d);
-}
diff --git a/gtk/decode-zlib.c b/gtk/decode-zlib.c
deleted file mode 100644
index a5325c0..0000000
--- a/gtk/decode-zlib.c
+++ /dev/null
@@ -1,89 +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 "decode.h"
-
-#ifndef __GNUC__
-#define ZLIB_WINAPI
-#endif
-
-#include <zlib.h>
-
-typedef struct GlibZlibDecoder
-{
-    SpiceZlibDecoder         base;
-    z_stream                 _z_strm;
-} GlibZlibDecoder;
-
-static void decode(SpiceZlibDecoder *decoder,
-                   uint8_t *data, int data_size,
-                   uint8_t *dest, int dest_size)
-{
-    GlibZlibDecoder *d = SPICE_CONTAINEROF(decoder, GlibZlibDecoder, base);
-    int z_ret;
-
-    inflateReset(&d->_z_strm);
-    d->_z_strm.next_in = data;
-    d->_z_strm.avail_in = data_size;
-    d->_z_strm.next_out = dest;
-    d->_z_strm.avail_out = dest_size;
-
-    z_ret = inflate(&d->_z_strm, Z_FINISH);
-
-    if (z_ret != Z_STREAM_END) {
-        g_warning("zlib inflate failed, error %d", z_ret);
-    }
-}
-
-static SpiceZlibDecoderOps zlib_decoder_ops = {
-    .decode = decode,
-};
-
-SpiceZlibDecoder *zlib_decoder_new(void)
-{
-    GlibZlibDecoder *d = g_new0(GlibZlibDecoder, 1);
-    int z_ret;
-
-    d->_z_strm.zalloc = Z_NULL;
-    d->_z_strm.zfree = Z_NULL;
-    d->_z_strm.opaque = Z_NULL;
-    d->_z_strm.next_in = Z_NULL;
-    d->_z_strm.avail_in = 0;
-    z_ret = inflateInit(&d->_z_strm);
-    if (z_ret != Z_OK) {
-        g_warning("zlib decoder init failed, error %d", z_ret);
-        goto fail;
-    }
-
-    d->base.ops = &zlib_decoder_ops;
-
-    return &d->base;
-
-fail:
-    free(d);
-    return NULL;
-}
-
-void zlib_decoder_destroy(SpiceZlibDecoder *decoder)
-{
-    GlibZlibDecoder *d = SPICE_CONTAINEROF(decoder, GlibZlibDecoder, base);
-
-    inflateEnd(&d->_z_strm);
-    free(d);
-}
diff --git a/gtk/decode.h b/gtk/decode.h
deleted file mode 100644
index b274d67..0000000
--- a/gtk/decode.h
+++ /dev/null
@@ -1,44 +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 SPICEGTK_DECODE_H_
-# define SPICEGTK_DECODE_H_
-
-#include <glib.h>
-
-#include "client_sw_canvas.h"
-
-G_BEGIN_DECLS
-
-typedef struct SpiceGlzDecoderWindow SpiceGlzDecoderWindow;
-
-SpiceGlzDecoderWindow *glz_decoder_window_new(void);
-void glz_decoder_window_clear(SpiceGlzDecoderWindow *w);
-void glz_decoder_window_destroy(SpiceGlzDecoderWindow *w);
-
-SpiceGlzDecoder *glz_decoder_new(SpiceGlzDecoderWindow *w);
-void glz_decoder_destroy(SpiceGlzDecoder *d);
-
-SpiceZlibDecoder *zlib_decoder_new(void);
-void zlib_decoder_destroy(SpiceZlibDecoder *d);
-
-SpiceJpegDecoder *jpeg_decoder_new(void);
-void jpeg_decoder_destroy(SpiceJpegDecoder *d);
-
-G_END_DECLS
-
-#endif // SPICEGTK_DECODE_H_
diff --git a/gtk/desktop-integration.c b/gtk/desktop-integration.c
deleted file mode 100644
index 5868d48..0000000
--- a/gtk/desktop-integration.c
+++ /dev/null
@@ -1,223 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2012 Red Hat, Inc.
-
-   Red Hat Authors:
-   Hans de Goede <hdegoede at redhat.com>
-
-   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-object.h>
-
-#include "glib-compat.h"
-#include "spice-session-priv.h"
-#include "desktop-integration.h"
-
-#include <glib/gi18n.h>
-
-#define GNOME_SESSION_INHIBIT_AUTOMOUNT 16
-
-/* ------------------------------------------------------------------ */
-/* gobject glue                                                       */
-
-#define SPICE_DESKTOP_INTEGRATION_GET_PRIVATE(obj)                                  \
-    (G_TYPE_INSTANCE_GET_PRIVATE ((obj), SPICE_TYPE_DESKTOP_INTEGRATION, SpiceDesktopIntegrationPrivate))
-
-struct _SpiceDesktopIntegrationPrivate {
-#if defined(USE_GDBUS)
-    GDBusProxy *gnome_session_proxy;
-#else
-    GObject *gnome_session_proxy; /* dummy */
-#endif
-    guint gnome_automount_inhibit_cookie;
-};
-
-G_DEFINE_TYPE(SpiceDesktopIntegration, spice_desktop_integration, G_TYPE_OBJECT);
-
-/* ------------------------------------------------------------------ */
-/* Gnome specific code                                                */
-
-static void handle_dbus_call_error(const char *call, GError **_error)
-{
-    GError *error = *_error;
-    const char *message = error->message;
-
-    g_warning("Error calling '%s': %s", call, message);
-    g_clear_error(_error);
-}
-
-static gboolean gnome_integration_init(SpiceDesktopIntegration *self)
-{
-    G_GNUC_UNUSED SpiceDesktopIntegrationPrivate *priv = self->priv;
-    GError *error = NULL;
-    gboolean success = TRUE;
-
-#if defined(USE_GDBUS)
-    gchar *name_owner = NULL;
-    priv->gnome_session_proxy =
-        g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SESSION,
-                                      G_DBUS_PROXY_FLAGS_NONE,
-                                      NULL,
-                                      "org.gnome.SessionManager",
-                                      "/org/gnome/SessionManager",
-                                      "org.gnome.SessionManager",
-                                      NULL,
-                                      &error);
-    if (!error &&
-        (name_owner = g_dbus_proxy_get_name_owner(priv->gnome_session_proxy)) == NULL) {
-        g_clear_object(&priv->gnome_session_proxy);
-        success = FALSE;
-    }
-    g_free(name_owner);
-#else
-    success = FALSE;
-#endif
-
-    if (error) {
-        g_warning("Could not create org.gnome.SessionManager dbus proxy: %s",
-                  error->message);
-        g_clear_error(&error);
-        return FALSE;
-    }
-
-    return success;
-}
-
-static void gnome_integration_inhibit_automount(SpiceDesktopIntegration *self)
-{
-    SpiceDesktopIntegrationPrivate *priv = self->priv;
-    GError *error = NULL;
-    G_GNUC_UNUSED const gchar *reason =
-        _("Automounting has been inhibited for USB auto-redirecting");
-
-    if (!priv->gnome_session_proxy)
-        return;
-
-    g_return_if_fail(priv->gnome_automount_inhibit_cookie == 0);
-
-#if defined(USE_GDBUS)
-    GVariant *v = g_dbus_proxy_call_sync(priv->gnome_session_proxy,
-                "Inhibit",
-                g_variant_new("(susu)",
-                              g_get_prgname(),
-                              0,
-                              reason,
-                              GNOME_SESSION_INHIBIT_AUTOMOUNT),
-                G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
-    if (v)
-        g_variant_get(v, "(u)", &priv->gnome_automount_inhibit_cookie);
-
-    g_clear_pointer(&v, g_variant_unref);
-#endif
-    if (error)
-        handle_dbus_call_error("org.gnome.SessionManager.Inhibit", &error);
-}
-
-static void gnome_integration_uninhibit_automount(SpiceDesktopIntegration *self)
-{
-    SpiceDesktopIntegrationPrivate *priv = self->priv;
-    GError *error = NULL;
-
-    if (!priv->gnome_session_proxy)
-        return;
-
-    /* Cookie is 0 when we failed to inhibit (and when called from dispose) */
-    if (priv->gnome_automount_inhibit_cookie == 0)
-        return;
-
-#if defined(USE_GDBUS)
-    GVariant *v = g_dbus_proxy_call_sync(priv->gnome_session_proxy,
-                "Uninhibit",
-                g_variant_new("(u)",
-                              priv->gnome_automount_inhibit_cookie),
-                G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
-    g_clear_pointer(&v, g_variant_unref);
-#endif
-    if (error)
-        handle_dbus_call_error("org.gnome.SessionManager.Uninhibit", &error);
-
-    priv->gnome_automount_inhibit_cookie = 0;
-}
-
-static void gnome_integration_dispose(SpiceDesktopIntegration *self)
-{
-    SpiceDesktopIntegrationPrivate *priv = self->priv;
-
-    g_clear_object(&priv->gnome_session_proxy);
-}
-
-/* ------------------------------------------------------------------ */
-/* gobject glue                                                       */
-
-static void spice_desktop_integration_init(SpiceDesktopIntegration *self)
-{
-    SpiceDesktopIntegrationPrivate *priv;
-
-    priv = SPICE_DESKTOP_INTEGRATION_GET_PRIVATE(self);
-    self->priv = priv;
-
-    if (!gnome_integration_init(self))
-       g_warning("Warning no automount-inhibiting implementation available");
-}
-
-static void spice_desktop_integration_dispose(GObject *gobject)
-{
-    SpiceDesktopIntegration *self = SPICE_DESKTOP_INTEGRATION(gobject);
-
-    gnome_integration_dispose(self);
-
-    /* Chain up to the parent class */
-    if (G_OBJECT_CLASS(spice_desktop_integration_parent_class)->dispose)
-        G_OBJECT_CLASS(spice_desktop_integration_parent_class)->dispose(gobject);
-}
-
-static void spice_desktop_integration_class_init(SpiceDesktopIntegrationClass *klass)
-{
-    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
-
-    gobject_class->dispose      = spice_desktop_integration_dispose;
-
-    g_type_class_add_private(klass, sizeof(SpiceDesktopIntegrationPrivate));
-}
-
-SpiceDesktopIntegration *spice_desktop_integration_get(SpiceSession *session)
-{
-    SpiceDesktopIntegration *self;
-    static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
-
-    g_return_val_if_fail(session != NULL, NULL);
-
-    g_static_mutex_lock(&mutex);
-    self = g_object_get_data(G_OBJECT(session), "spice-desktop");
-    if (self == NULL) {
-        self = g_object_new(SPICE_TYPE_DESKTOP_INTEGRATION, NULL);
-        g_object_set_data_full(G_OBJECT(session), "spice-desktop", self, g_object_unref);
-    }
-    g_static_mutex_unlock(&mutex);
-
-    return self;
-}
-
-void spice_desktop_integration_inhibit_automount(SpiceDesktopIntegration *self)
-{
-    gnome_integration_inhibit_automount(self);
-}
-
-void spice_desktop_integration_uninhibit_automount(SpiceDesktopIntegration *self)
-{
-    gnome_integration_uninhibit_automount(self);
-}
diff --git a/gtk/desktop-integration.h b/gtk/desktop-integration.h
deleted file mode 100644
index 3716089..0000000
--- a/gtk/desktop-integration.h
+++ /dev/null
@@ -1,64 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2012 Red Hat, Inc.
-
-   Red Hat Authors:
-   Hans de Goede <hdegoede at redhat.com>
-
-   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_DESKTOP_INTEGRATION_H__
-#define __SPICE_DESKTOP_INTEGRATION_H__
-
-#include "spice-client.h"
-
-G_BEGIN_DECLS
-
-#define SPICE_TYPE_DESKTOP_INTEGRATION            (spice_desktop_integration_get_type ())
-#define SPICE_DESKTOP_INTEGRATION(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), SPICE_TYPE_DESKTOP_INTEGRATION, SpiceDesktopIntegration))
-#define SPICE_DESKTOP_INTEGRATION_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SPICE_TYPE_DESKTOP_INTEGRATION, SpiceDesktopIntegrationClass))
-#define SPICE_IS_DESKTOP_INTEGRATION(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SPICE_TYPE_DESKTOP_INTEGRATION))
-#define SPICE_IS_DESKTOP_INTEGRATION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SPICE_TYPE_DESKTOP_INTEGRATION))
-#define SPICE_DESKTOP_INTEGRATION_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SPICE_TYPE_DESKTOP_INTEGRATION, SpiceDesktopIntegrationClass))
-
-typedef struct _SpiceDesktopIntegration SpiceDesktopIntegration;
-typedef struct _SpiceDesktopIntegrationClass SpiceDesktopIntegrationClass;
-typedef struct _SpiceDesktopIntegrationPrivate SpiceDesktopIntegrationPrivate;
-
-/*
- * SpiceDesktopIntegration offers helper-functions to do desktop environment
- * and/or platform specific tasks like disabling automount, disabling the
- * screen-saver, etc. SpiceDesktopIntegration is for internal spice-gtk usage
- * only!
- */
-struct _SpiceDesktopIntegration
-{
-    GObject parent;
-
-    SpiceDesktopIntegrationPrivate *priv;
-};
-
-struct _SpiceDesktopIntegrationClass
-{
-    GObjectClass parent_class;
-};
-
-GType spice_desktop_integration_get_type(void);
-SpiceDesktopIntegration *spice_desktop_integration_get(SpiceSession *session);
-void spice_desktop_integration_inhibit_automount(SpiceDesktopIntegration *self);
-void spice_desktop_integration_uninhibit_automount(SpiceDesktopIntegration *self);
-
-G_END_DECLS
-
-#endif /* __SPICE_DESKTOP_INTEGRATION_H__ */
diff --git a/gtk/gio-coroutine.c b/gtk/gio-coroutine.c
deleted file mode 100644
index c866e15..0000000
--- a/gtk/gio-coroutine.c
+++ /dev/null
@@ -1,275 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2010 Red Hat, Inc.
-   Copyright (C) 2006 Anthony Liguori <anthony at codemonkey.ws>
-   Copyright (C) 2009-2010 Daniel P. Berrange <dan at berrange.com>
-
-   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.0 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, write to the Free Software
-   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
-*/
-#include "config.h"
-
-#include "gio-coroutine.h"
-
-typedef struct _GConditionWaitSource
-{
-    GCoroutine *self;
-    GSource src;
-    GConditionWaitFunc func;
-    gpointer data;
-} GConditionWaitSource;
-
-GCoroutine* g_coroutine_self(void)
-{
-    return (GCoroutine*)coroutine_self();
-}
-
-/* Main loop helper functions */
-static gboolean g_io_wait_helper(GSocket *sock G_GNUC_UNUSED,
-				 GIOCondition cond,
-				 gpointer data)
-{
-    struct coroutine *to = data;
-    coroutine_yieldto(to, &cond);
-    return FALSE;
-}
-
-GIOCondition g_coroutine_socket_wait(GCoroutine *self,
-                                     GSocket *sock,
-                                     GIOCondition cond)
-{
-    GIOCondition *ret, val = 0;
-    GSource *src;
-
-    g_return_val_if_fail(self != NULL, 0);
-    g_return_val_if_fail(self->wait_id == 0, 0);
-    g_return_val_if_fail(sock != NULL, 0);
-
-    src = g_socket_create_source(sock, cond | G_IO_HUP | G_IO_ERR | G_IO_NVAL, NULL);
-    g_source_set_callback(src, (GSourceFunc)g_io_wait_helper, self, NULL);
-    self->wait_id = g_source_attach(src, NULL);
-    ret = coroutine_yield(NULL);
-    g_source_unref(src);
-
-    if (ret != NULL)
-        val = *ret;
-    else
-        g_source_remove(self->wait_id);
-
-    self->wait_id = 0;
-    return val;
-}
-
-void g_coroutine_condition_cancel(GCoroutine *coroutine)
-{
-    g_return_if_fail(coroutine != NULL);
-
-    if (coroutine->condition_id == 0)
-        return;
-
-    g_source_remove(coroutine->condition_id);
-    coroutine->condition_id = 0;
-}
-
-void g_coroutine_wakeup(GCoroutine *coroutine)
-{
-    g_return_if_fail(coroutine != NULL);
-    g_return_if_fail(coroutine != g_coroutine_self());
-
-    if (coroutine->wait_id)
-        coroutine_yieldto(&coroutine->coroutine, NULL);
-}
-
-/*
- * Call immediately before the main loop does an iteration. Returns
- * true if the condition we're checking is ready for dispatch
- */
-static gboolean g_condition_wait_prepare(GSource *src,
-					 int *timeout) {
-    GConditionWaitSource *vsrc = (GConditionWaitSource *)src;
-    *timeout = -1;
-    return vsrc->func(vsrc->data);
-}
-
-/*
- * Call immediately after the main loop does an iteration. Returns
- * true if the condition we're checking is ready for dispatch
- */
-static gboolean g_condition_wait_check(GSource *src)
-{
-    GConditionWaitSource *vsrc = (GConditionWaitSource *)src;
-    return vsrc->func(vsrc->data);
-}
-
-static gboolean g_condition_wait_dispatch(GSource *src G_GNUC_UNUSED,
-					  GSourceFunc cb,
-					  gpointer data) {
-    return cb(data);
-}
-
-GSourceFuncs waitFuncs = {
-    .prepare = g_condition_wait_prepare,
-    .check = g_condition_wait_check,
-    .dispatch = g_condition_wait_dispatch,
-};
-
-static gboolean g_condition_wait_helper(gpointer data)
-{
-    GCoroutine *self = (GCoroutine *)data;
-    coroutine_yieldto(&self->coroutine, NULL);
-    return FALSE;
-}
-
-/*
- * g_coroutine_condition_wait:
- * @coroutine: the coroutine to wait on
- * @func: the condition callback
- * @data: the user data passed to @func callback
- *
- * This function will wait on caller coroutine until @func returns %TRUE.
- *
- * @func is called when entering the main loop from the main context (coroutine).
- *
- * The condition can be cancelled by calling g_coroutine_wakeup()
- *
- * Returns: %TRUE if condition reached, %FALSE if not and cancelled
- */
-gboolean g_coroutine_condition_wait(GCoroutine *self, GConditionWaitFunc func, gpointer data)
-{
-    GSource *src;
-    GConditionWaitSource *vsrc;
-
-    g_return_val_if_fail(self != NULL, FALSE);
-    g_return_val_if_fail(self->condition_id == 0, FALSE);
-    g_return_val_if_fail(func != NULL, FALSE);
-
-    /* Short-circuit check in case we've got it ahead of time */
-    if (func(data))
-        return TRUE;
-
-    /*
-     * Don't have it, so yield to the main loop, checking the condition
-     * on each iteration of the main loop
-     */
-    src = g_source_new(&waitFuncs, sizeof(GConditionWaitSource));
-    vsrc = (GConditionWaitSource *)src;
-
-    vsrc->func = func;
-    vsrc->data = data;
-    vsrc->self = self;
-
-    self->condition_id = g_source_attach(src, NULL);
-    g_source_set_callback(src, g_condition_wait_helper, self, NULL);
-    coroutine_yield(NULL);
-    g_source_unref(src);
-
-    /* it got woked up / cancelled? */
-    if (self->condition_id == 0)
-        return func(data);
-
-    self->condition_id = 0;
-    return TRUE;
-}
-
-struct signal_data
-{
-    gpointer instance;
-    struct coroutine *caller;
-    guint signal_id;
-    GQuark detail;
-    const gchar *propname;
-    gboolean notified;
-    va_list var_args;
-};
-
-static gboolean emit_main_context(gpointer opaque)
-{
-    struct signal_data *signal = opaque;
-
-    g_signal_emit_valist(signal->instance, signal->signal_id,
-                         signal->detail, signal->var_args);
-    signal->notified = TRUE;
-
-    coroutine_yieldto(signal->caller, NULL);
-
-    return FALSE;
-}
-
-void
-g_coroutine_signal_emit(gpointer instance, guint signal_id,
-                        GQuark detail, ...)
-{
-    struct signal_data data = {
-        .instance = instance,
-        .signal_id = signal_id,
-        .detail = detail,
-        .caller = coroutine_self(),
-    };
-
-    va_start (data.var_args, detail);
-
-    if (coroutine_self_is_main()) {
-        g_signal_emit_valist(instance, signal_id, detail, data.var_args);
-    } else {
-        g_object_ref(instance);
-        g_idle_add(emit_main_context, &data);
-        coroutine_yield(NULL);
-        g_warn_if_fail(data.notified);
-        g_object_unref(instance);
-    }
-
-    va_end (data.var_args);
-}
-
-
-static gboolean notify_main_context(gpointer opaque)
-{
-    struct signal_data *signal = opaque;
-
-    g_object_notify(signal->instance, signal->propname);
-    signal->notified = TRUE;
-
-    coroutine_yieldto(signal->caller, NULL);
-
-    return FALSE;
-}
-
-/* coroutine -> main context */
-void g_coroutine_object_notify(GObject *object,
-                               const gchar *property_name)
-{
-    struct signal_data data;
-
-    if (coroutine_self_is_main()) {
-        g_object_notify(object, property_name);
-    } else {
-
-        data.instance = g_object_ref(object);
-        data.caller = coroutine_self();
-        data.propname = (gpointer)property_name;
-        data.notified = FALSE;
-
-        g_idle_add(notify_main_context, &data);
-
-        /* This switches to the system coroutine context, lets
-         * the idle function run to dispatch the signal, and
-         * finally returns once complete. ie this is synchronous
-         * from the POV of the coroutine despite there being
-         * an idle function involved
-         */
-        coroutine_yield(NULL);
-        g_warn_if_fail(data.notified);
-        g_object_unref(object);
-    }
-}
diff --git a/gtk/gio-coroutine.h b/gtk/gio-coroutine.h
deleted file mode 100644
index b3a6d78..0000000
--- a/gtk/gio-coroutine.h
+++ /dev/null
@@ -1,66 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2010 Red Hat, Inc.
-   Copyright (C) 2006 Anthony Liguori <anthony at codemonkey.ws>
-   Copyright (C) 2009-2010 Daniel P. Berrange <dan at berrange.com>
-
-   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.0 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, write to the Free Software
-   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
-*/
-#ifndef __GIO_COROUTINE_H__
-#define __GIO_COROUTINE_H__
-
-#include <gio/gio.h>
-#include "coroutine.h"
-
-G_BEGIN_DECLS
-
-typedef struct _GCoroutine GCoroutine;
-
-struct _GCoroutine
-{
-    struct coroutine coroutine;
-    guint wait_id;
-    guint condition_id;
-};
-
-/*
- * A special GSource impl which allows us to wait on a certain
- * condition to be satisfied. This is effectively a boolean test
- * run on each iteration of the main loop. So whenever a file has
- * new I/O, or a timer occurs, etc we'll do the check. This is
- * pretty efficient compared to a normal GLib Idle func which has
- * to busy wait on a timeout, since our condition is only checked
- * when some other source's state changes
- */
-typedef gboolean (*GConditionWaitFunc)(gpointer);
-
-typedef void (*GSignalEmitMainFunc)(GObject *object, int signum, gpointer params);
-
-GCoroutine*  g_coroutine_self           (void);
-void         g_coroutine_wakeup         (GCoroutine *coroutine);
-GIOCondition g_coroutine_socket_wait    (GCoroutine *coroutine,
-                                         GSocket *sock, GIOCondition cond);
-gboolean     g_coroutine_condition_wait (GCoroutine *coroutine,
-                                         GConditionWaitFunc func, gpointer data);
-void         g_coroutine_condition_cancel(GCoroutine *coroutine);
-
-void         g_coroutine_signal_emit (gpointer instance, guint signal_id,
-                                      GQuark detail, ...);
-
-void         g_coroutine_object_notify(GObject *object, const gchar *property_name);
-
-G_END_DECLS
-
-#endif /* __GIO_COROUTINE_H__ */
diff --git a/gtk/giopipe.c b/gtk/giopipe.c
deleted file mode 100644
index d91c4d9..0000000
--- a/gtk/giopipe.c
+++ /dev/null
@@ -1,484 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-  Copyright (C) 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 <string.h>
-#include <errno.h>
-
-#include "giopipe.h"
-
-#define TYPE_PIPE_INPUT_STREAM         (pipe_input_stream_get_type ())
-#define PIPE_INPUT_STREAM(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), TYPE_PIPE_INPUT_STREAM, PipeInputStream))
-#define PIPE_INPUT_STREAM_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), TYPE_PIPE_INPUT_STREAM, PipeInputStreamClass))
-#define IS_PIPE_INPUT_STREAM(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), TYPE_PIPE_INPUT_STREAM))
-#define IS_PIPE_INPUT_STREAM_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), TYPE_PIPE_INPUT_STREAM))
-#define PIPE_INPUT_STREAM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), TYPE_PIPE_INPUT_STREAM, PipeInputStreamClass))
-
-typedef struct _PipeInputStreamClass                              PipeInputStreamClass;
-typedef struct _PipeInputStream                                   PipeInputStream;
-typedef struct _PipeOutputStream                                  PipeOutputStream;
-
-struct _PipeInputStream
-{
-    GInputStream parent_instance;
-
-    PipeOutputStream *peer;
-    gssize read;
-
-    /* GIOstream:closed is protected against pending operations, so we
-     * use an additional close flag to cancel those when the peer is
-     * closing.
-     */
-    gboolean peer_closed;
-    GList *sources;
-};
-
-struct _PipeInputStreamClass
-{
-    GInputStreamClass parent_class;
-};
-
-#define TYPE_PIPE_OUTPUT_STREAM         (pipe_output_stream_get_type ())
-#define PIPE_OUTPUT_STREAM(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), TYPE_PIPE_OUTPUT_STREAM, PipeOutputStream))
-#define PIPE_OUTPUT_STREAM_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), TYPE_PIPE_OUTPUT_STREAM, PipeOutputStreamClass))
-#define IS_PIPE_OUTPUT_STREAM(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), TYPE_PIPE_OUTPUT_STREAM))
-#define IS_PIPE_OUTPUT_STREAM_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), TYPE_PIPE_OUTPUT_STREAM))
-#define PIPE_OUTPUT_STREAM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), TYPE_PIPE_OUTPUT_STREAM, PipeOutputStreamClass))
-
-typedef struct _PipeOutputStreamClass                             PipeOutputStreamClass;
-
-struct _PipeOutputStream
-{
-    GOutputStream parent_instance;
-
-    PipeInputStream *peer;
-    const gchar *buffer;
-    gsize count;
-    gboolean peer_closed;
-    GList *sources;
-};
-
-struct _PipeOutputStreamClass
-{
-    GOutputStreamClass parent_class;
-};
-
-static void pipe_input_stream_pollable_iface_init (GPollableInputStreamInterface *iface);
-static void pipe_input_stream_check_source (PipeInputStream *self);
-static void pipe_output_stream_check_source (PipeOutputStream *self);
-
-G_DEFINE_TYPE_WITH_CODE (PipeInputStream, pipe_input_stream, G_TYPE_INPUT_STREAM,
-                         G_IMPLEMENT_INTERFACE (G_TYPE_POLLABLE_INPUT_STREAM,
-                                                pipe_input_stream_pollable_iface_init))
-
-static gssize
-pipe_input_stream_read (GInputStream  *stream,
-                        void          *buffer,
-                        gsize          count,
-                        GCancellable  *cancellable,
-                        GError       **error)
-{
-    PipeInputStream *self = PIPE_INPUT_STREAM (stream);
-
-    g_return_val_if_fail(count > 0, -1);
-
-    if (g_input_stream_is_closed (stream) || self->peer_closed) {
-        g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_CLOSED,
-                             "Stream is already closed");
-        return -1;
-    }
-
-    if (!self->peer->buffer) {
-        g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK,
-                             g_strerror(EAGAIN));
-        return -1;
-    }
-
-    count = MIN(self->peer->count, count);
-    memcpy(buffer, self->peer->buffer, count);
-    self->read = count;
-    self->peer->buffer = NULL;
-
-    //g_debug("read %p :%"G_GSIZE_FORMAT, self->peer, count);
-    /* schedule peer source */
-    pipe_output_stream_check_source(self->peer);
-
-    return count;
-}
-
-static GList *
-set_all_sources_ready (GList *sources)
-{
-    GList *it = sources;
-    while (it != NULL) {
-        GSource *s = it->data;
-        GList *next = it->next;
-
-        if (s == NULL || g_source_is_destroyed(s)) {
-            /* remove */
-            sources = g_list_delete_link(sources, it);
-            g_source_unref(s);
-        } else {
-            /* dispatch */
-            g_source_set_ready_time(s, 0);
-        }
-        it = next;
-    }
-    return sources;
-}
-
-static void
-pipe_input_stream_check_source (PipeInputStream *self)
-{
-    if (g_pollable_input_stream_is_readable(G_POLLABLE_INPUT_STREAM(self)))
-        self->sources = set_all_sources_ready(self->sources);
-}
-
-static gboolean
-pipe_input_stream_close (GInputStream  *stream,
-                         GCancellable   *cancellable,
-                         GError        **error)
-{
-    PipeInputStream *self;
-
-    self = PIPE_INPUT_STREAM(stream);
-
-    if (self->peer) {
-        /* ignore any pending errors */
-        self->peer->peer_closed = TRUE;
-        g_output_stream_close(G_OUTPUT_STREAM(self->peer), cancellable, NULL);
-        pipe_output_stream_check_source(self->peer);
-    }
-
-    return TRUE;
-}
-
-static void
-pipe_input_stream_close_async (GInputStream       *stream,
-                               int                  io_priority,
-                               GCancellable        *cancellable,
-                               GAsyncReadyCallback  callback,
-                               gpointer             data)
-{
-    GTask *task;
-
-    task = g_task_new (stream, cancellable, callback, data);
-
-    /* will always return TRUE */
-    pipe_input_stream_close (stream, cancellable, NULL);
-
-    g_task_return_boolean (task, TRUE);
-    g_object_unref (task);
-}
-
-static gboolean
-pipe_input_stream_close_finish (GInputStream  *stream,
-                                GAsyncResult   *result,
-                                GError        **error)
-{
-    g_return_val_if_fail (g_task_is_valid (result, stream), FALSE);
-
-    return g_task_propagate_boolean (G_TASK (result), error);
-}
-
-static void
-pipe_input_stream_init (PipeInputStream *self)
-{
-    self->read = -1;
-}
-
-static void
-pipe_input_stream_dispose(GObject *object)
-{
-    PipeInputStream *self;
-
-    self = PIPE_INPUT_STREAM(object);
-
-    if (self->peer) {
-        g_object_remove_weak_pointer(G_OBJECT(self->peer), (gpointer*)&self->peer);
-        self->peer = NULL;
-    }
-
-    g_list_free_full (self->sources, (GDestroyNotify) g_source_unref);
-    self->sources = NULL;
-
-    G_OBJECT_CLASS(pipe_input_stream_parent_class)->dispose (object);
-}
-
-static void
-pipe_input_stream_class_init (PipeInputStreamClass *klass)
-{
-    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
-    GInputStreamClass *istream_class = G_INPUT_STREAM_CLASS (klass);
-
-    istream_class->read_fn  = pipe_input_stream_read;
-    istream_class->close_fn = pipe_input_stream_close;
-    istream_class->close_async  = pipe_input_stream_close_async;
-    istream_class->close_finish = pipe_input_stream_close_finish;
-
-    gobject_class->dispose = pipe_input_stream_dispose;
-}
-
-static gboolean
-pipe_input_stream_is_readable (GPollableInputStream *stream)
-{
-    PipeInputStream *self = PIPE_INPUT_STREAM (stream);
-    gboolean readable;
-
-    readable = (self->peer && self->peer->buffer && self->read == -1) || self->peer_closed;
-    //g_debug("readable %p %d", self->peer, readable);
-
-    return readable;
-}
-
-static GSource *
-pipe_input_stream_create_source (GPollableInputStream *stream,
-                                 GCancellable         *cancellable)
-{
-    PipeInputStream *self = PIPE_INPUT_STREAM(stream);
-    GSource *pollable_source;
-
-    pollable_source = g_pollable_source_new_full (self, NULL, cancellable);
-    self->sources = g_list_prepend (self->sources, g_source_ref (pollable_source));
-
-    return pollable_source;
-}
-
-static void
-pipe_input_stream_pollable_iface_init (GPollableInputStreamInterface *iface)
-{
-    iface->is_readable   = pipe_input_stream_is_readable;
-    iface->create_source = pipe_input_stream_create_source;
-}
-
-static void pipe_output_stream_pollable_iface_init (GPollableOutputStreamInterface *iface);
-
-G_DEFINE_TYPE_WITH_CODE (PipeOutputStream, pipe_output_stream, G_TYPE_OUTPUT_STREAM,
-                         G_IMPLEMENT_INTERFACE (G_TYPE_POLLABLE_OUTPUT_STREAM,
-                                                pipe_output_stream_pollable_iface_init))
-
-static gssize
-pipe_output_stream_write (GOutputStream  *stream,
-                          const void     *buffer,
-                          gsize           count,
-                          GCancellable   *cancellable,
-                          GError        **error)
-{
-    PipeOutputStream *self = PIPE_OUTPUT_STREAM(stream);
-    PipeInputStream *peer = self->peer;
-
-    //g_debug("write %p :%"G_GSIZE_FORMAT, stream, count);
-    if (g_output_stream_is_closed (stream) || self->peer_closed) {
-        g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_CLOSED,
-                             "Stream is already closed");
-        return -1;
-    }
-
-    /* this abuses pollable stream, writing sync would likely lead to
-       crashes, since the buffer pointer would become invalid, a
-       generic solution would need a copy..
-    */
-    g_return_val_if_fail(self->buffer == buffer || self->buffer == NULL, -1);
-    self->buffer = buffer;
-    self->count = count;
-
-    pipe_input_stream_check_source(self->peer);
-
-    if (peer->read < 0) {
-        g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK,
-                             g_strerror (EAGAIN));
-        return -1;
-    }
-
-    g_assert(peer->read <= self->count);
-    count = peer->read;
-
-    self->buffer = NULL;
-    self->count = 0;
-    peer->read = -1;
-
-    return count;
-}
-
-static void
-pipe_output_stream_init (PipeOutputStream *stream)
-{
-}
-
-static void
-pipe_output_stream_dispose(GObject *object)
-{
-    PipeOutputStream *self;
-
-    self = PIPE_OUTPUT_STREAM(object);
-
-    if (self->peer) {
-        g_object_remove_weak_pointer(G_OBJECT(self->peer), (gpointer*)&self->peer);
-        self->peer = NULL;
-    }
-
-    g_list_free_full (self->sources, (GDestroyNotify) g_source_unref);
-    self->sources = NULL;
-
-    G_OBJECT_CLASS(pipe_output_stream_parent_class)->dispose (object);
-}
-
-static void
-pipe_output_stream_check_source (PipeOutputStream *self)
-{
-    if (g_pollable_output_stream_is_writable(G_POLLABLE_OUTPUT_STREAM(self)))
-        self->sources = set_all_sources_ready(self->sources);
-}
-
-static gboolean
-pipe_output_stream_close (GOutputStream  *stream,
-                          GCancellable   *cancellable,
-                          GError        **error)
-{
-    PipeOutputStream *self;
-
-    self = PIPE_OUTPUT_STREAM(stream);
-
-    if (self->peer) {
-        /* ignore any pending errors */
-        self->peer->peer_closed = TRUE;
-        g_input_stream_close(G_INPUT_STREAM(self->peer), cancellable, NULL);
-        pipe_input_stream_check_source(self->peer);
-    }
-
-    return TRUE;
-}
-
-static void
-pipe_output_stream_close_async (GOutputStream       *stream,
-                                int                  io_priority,
-                                GCancellable        *cancellable,
-                                GAsyncReadyCallback  callback,
-                                gpointer             data)
-{
-    GTask *task;
-
-    task = g_task_new (stream, cancellable, callback, data);
-
-    /* will always return TRUE */
-    pipe_output_stream_close (stream, cancellable, NULL);
-
-    g_task_return_boolean (task, TRUE);
-    g_object_unref (task);
-}
-
-static gboolean
-pipe_output_stream_close_finish (GOutputStream  *stream,
-                                 GAsyncResult   *result,
-                                 GError        **error)
-{
-    g_return_val_if_fail (g_task_is_valid (result, stream), FALSE);
-
-    return g_task_propagate_boolean (G_TASK (result), error);
-}
-
-
-static void
-pipe_output_stream_class_init (PipeOutputStreamClass *klass)
-{
-    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
-    GOutputStreamClass *ostream_class = G_OUTPUT_STREAM_CLASS (klass);
-
-    ostream_class->write_fn = pipe_output_stream_write;
-    ostream_class->close_fn = pipe_output_stream_close;
-    ostream_class->close_async  = pipe_output_stream_close_async;
-    ostream_class->close_finish = pipe_output_stream_close_finish;
-
-    gobject_class->dispose = pipe_output_stream_dispose;
-}
-
-static gboolean
-pipe_output_stream_is_writable (GPollableOutputStream *stream)
-{
-    PipeOutputStream *self = PIPE_OUTPUT_STREAM(stream);
-    gboolean writable;
-
-    writable = self->buffer == NULL || self->peer->read >= 0;
-    //g_debug("writable %p %d", self, writable);
-
-    return writable;
-}
-
-static GSource *
-pipe_output_stream_create_source (GPollableOutputStream *stream,
-                                  GCancellable          *cancellable)
-{
-    PipeOutputStream *self = PIPE_OUTPUT_STREAM(stream);
-    GSource *pollable_source;
-
-    pollable_source = g_pollable_source_new_full (self, NULL, cancellable);
-    self->sources = g_list_prepend (self->sources, g_source_ref (pollable_source));
-
-    return pollable_source;
-}
-
-static void
-pipe_output_stream_pollable_iface_init (GPollableOutputStreamInterface *iface)
-{
-    iface->is_writable = pipe_output_stream_is_writable;
-    iface->create_source = pipe_output_stream_create_source;
-}
-
-G_GNUC_INTERNAL void
-make_gio_pipe(GInputStream **input, GOutputStream **output)
-{
-    PipeInputStream *in;
-    PipeOutputStream *out;
-
-    g_return_if_fail(input != NULL && *input == NULL);
-    g_return_if_fail(output != NULL && *output == NULL);
-
-    in = g_object_new(TYPE_PIPE_INPUT_STREAM, NULL);
-    out = g_object_new(TYPE_PIPE_OUTPUT_STREAM, NULL);
-
-    out->peer = in;
-    g_object_add_weak_pointer(G_OBJECT(in), (gpointer*)&out->peer);
-
-    in->peer = out;
-    g_object_add_weak_pointer(G_OBJECT(out), (gpointer*)&in->peer);
-
-    *input = G_INPUT_STREAM(in);
-    *output = G_OUTPUT_STREAM(out);
-}
-
-G_GNUC_INTERNAL void
-spice_make_pipe(GIOStream **p1, GIOStream **p2)
-{
-    GInputStream *in1 = NULL, *in2 = NULL;
-    GOutputStream *out1 = NULL, *out2 = NULL;
-
-    g_return_if_fail(p1 != NULL);
-    g_return_if_fail(p2 != NULL);
-    g_return_if_fail(*p1 == NULL);
-    g_return_if_fail(*p2 == NULL);
-
-    make_gio_pipe(&in1, &out2);
-    make_gio_pipe(&in2, &out1);
-
-    *p1 = g_simple_io_stream_new(in1, out1);
-    *p2 = g_simple_io_stream_new(in2, out2);
-
-    g_object_unref(in1);
-    g_object_unref(in2);
-    g_object_unref(out1);
-    g_object_unref(out2);
-}
diff --git a/gtk/giopipe.h b/gtk/giopipe.h
deleted file mode 100644
index 46c2c9c..0000000
--- a/gtk/giopipe.h
+++ /dev/null
@@ -1,29 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-  Copyright (C) 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 __SPICE_GIO_PIPE_H__
-#define __SPICE_GIO_PIPE_H__
-
-#include <gio/gio.h>
-
-G_BEGIN_DECLS
-
-void spice_make_pipe(GIOStream **p1, GIOStream **p2);
-
-G_END_DECLS
-
-#endif /* __SPICE_GIO_PIPE_H__ */
diff --git a/gtk/glib-compat.c b/gtk/glib-compat.c
deleted file mode 100644
index 49edf73..0000000
--- a/gtk/glib-compat.c
+++ /dev/null
@@ -1,79 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2012-2014 Red Hat, Inc.
-   Copyright © 1998-2009 VLC authors and VideoLAN
-
-   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 <string.h>
-
-#include "glib-compat.h"
-
-#if !GLIB_CHECK_VERSION(2,30,0)
-G_DEFINE_BOXED_TYPE (GMainContext, spice_main_context, g_main_context_ref, g_main_context_unref)
-#endif
-
-
-#if !GLIB_CHECK_VERSION(2,32,0)
-/**
- * g_queue_free_full:
- * @queue: a pointer to a #GQueue
- * @free_func: the function to be called to free each element's data
- *
- * Convenience method, which frees all the memory used by a #GQueue,
- * and calls the specified destroy function on every element's data.
- *
- * Since: 2.32
- */
-void
-g_queue_free_full (GQueue        *queue,
-                  GDestroyNotify  free_func)
-{
-  g_queue_foreach (queue, (GFunc) free_func, NULL);
-  g_queue_free (queue);
-}
-#endif
-
-
-#ifndef HAVE_STRTOK_R
-G_GNUC_INTERNAL
-char *strtok_r(char *s, const char *delim, char **save_ptr)
-{
-    char *token;
-
-    if (s == NULL)
-        s = *save_ptr;
-
-    /* Scan leading delimiters. */
-    s += strspn (s, delim);
-    if (*s == '\0')
-        return NULL;
-
-    /* Find the end of the token. */
-    token = s;
-    s = strpbrk (token, delim);
-    if (s == NULL)
-        /* This token finishes the string. */
-        *save_ptr = strchr (token, '\0');
-    else
-    {
-        /* Terminate the token and make *SAVE_PTR point past it. */
-        *s = '\0';
-        *save_ptr = s + 1;
-    }
-    return token;
-}
-#endif
diff --git a/gtk/glib-compat.h b/gtk/glib-compat.h
deleted file mode 100644
index 5491fe4..0000000
--- a/gtk/glib-compat.h
+++ /dev/null
@@ -1,68 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2012-2014 Red Hat, Inc.
-   Copyright © 1998-2009 VLC authors and VideoLAN
-
-   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 GLIB_COMPAT_H
-#define GLIB_COMPAT_H
-
-#include "config.h"
-
-#include <glib-object.h>
-#include <gio/gio.h>
-
-
-#if !GLIB_CHECK_VERSION(2,30,0)
-#define G_TYPE_MAIN_CONTEXT (spice_main_context_get_type ())
-GType spice_main_context_get_type (void) G_GNUC_CONST;
-#endif
-
-#if !GLIB_CHECK_VERSION(2,32,0)
-# define G_SIGNAL_DEPRECATED (1 << 9)
-
-#define G_SOURCE_CONTINUE   TRUE
-#define G_SOURCE_REMOVE     FALSE
-
-void
-g_queue_free_full (GQueue        *queue,
-                   GDestroyNotify  free_func);
-#endif
-
-#ifndef g_clear_pointer
-#define g_clear_pointer(pp, destroy) \
-  G_STMT_START {                                                               \
-    G_STATIC_ASSERT (sizeof *(pp) == sizeof (gpointer));                       \
-    /* Only one access, please */                                              \
-    gpointer *_pp = (gpointer *) (pp);                                         \
-    gpointer _p;                                                               \
-    /* This assignment is needed to avoid a gcc warning */                     \
-    GDestroyNotify _destroy = (GDestroyNotify) (destroy);                      \
-                                                                               \
-    (void) (0 ? (gpointer) *(pp) : 0);                                         \
-    do                                                                         \
-      _p = g_atomic_pointer_get (_pp);                                         \
-    while G_UNLIKELY (!g_atomic_pointer_compare_and_exchange (_pp, _p, NULL)); \
-                                                                               \
-    if (_p)                                                                    \
-      _destroy (_p);                                                           \
-  } G_STMT_END
-#endif
-
-#ifndef HAVE_STRTOK_R
-char* strtok_r(char *s, const char *delim, char **save_ptr);
-#endif
-
-#endif /* GLIB_COMPAT_H */
diff --git a/gtk/gtk-compat.h b/gtk/gtk-compat.h
deleted file mode 100644
index be143b2..0000000
--- a/gtk/gtk-compat.h
+++ /dev/null
@@ -1,56 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2012-2014 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 GTK_COMPAT_H
-#define GTK_COMPAT_H
-
-#include "config.h"
-
-#include <gtk/gtk.h>
-
-#if !GTK_CHECK_VERSION (2, 91, 0)
-#define GDK_IS_X11_DISPLAY(D) TRUE
-#define gdk_window_get_display(W) gdk_drawable_get_display(GDK_DRAWABLE(W))
-#endif
-
-#if GTK_CHECK_VERSION (2, 91, 0)
-static inline void gdk_drawable_get_size(GdkWindow *w, gint *ww, gint *wh)
-{
-    *ww = gdk_window_get_width(w);
-    *wh = gdk_window_get_height(w);
-}
-#endif
-
-#if !GTK_CHECK_VERSION(2, 20, 0)
-static inline gboolean gtk_widget_get_realized(GtkWidget *widget)
-{
-    g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
-    return GTK_WIDGET_REALIZED(widget);
-}
-#endif
-
-#if !GTK_CHECK_VERSION (3, 0, 0)
-#define cairo_rectangle_int_t GdkRectangle
-#define cairo_region_t GdkRegion
-#define cairo_region_create_rectangle gdk_region_rectangle
-#define cairo_region_subtract_rectangle(_dest,_rect) { GdkRegion *_region = gdk_region_rectangle (_rect); gdk_region_subtract (_dest, _region); gdk_region_destroy (_region); }
-#define cairo_region_destroy gdk_region_destroy
-
-#define gdk_window_get_display(W) gdk_drawable_get_display(GDK_DRAWABLE(W))
-#endif
-
-#endif /* GTK_COMPAT_H */
diff --git a/gtk/keymap-gen.pl b/gtk/keymap-gen.pl
deleted file mode 100755
index 56953f8..0000000
--- a/gtk/keymap-gen.pl
+++ /dev/null
@@ -1,214 +0,0 @@
-#!/usr/bin/perl
-
-use strict;
-use warnings;
-
-use Text::CSV;
-
-my %names = (
-    linux => [],
-    osx => []
-);
-
-my %namecolumns = (
-    linux => 0,
-    osx => 2,
-    win32 => 10,
-    x11 => 14,
-    );
-
-# Base data sources:
-#
-#  linux:     Linux: linux/input.h                                  (master set)
-#    osx:      OS-X: Carbon/HIToolbox/Events.h                      (manually mapped)
-# atset1:  AT Set 1: linux/drivers/input/keyboard/atkbd.c           (atkbd_set2_keycode + atkbd_unxlate_table)
-# atset2:  AT Set 2: linux/drivers/input/keyboard/atkbd.c           (atkbd_set2_keycode)
-# atset3:  AT Set 3: linux/drivers/input/keyboard/atkbd.c           (atkbd_set3_keycode)
-#     xt:        XT: linux/drivers/input/keyboard/xt.c              (xtkbd_keycode)
-#  xtkbd: Linux RAW: linux/drivers/char/keyboard.c                  (x86_keycodes)
-#    usb:   USB HID: linux/drivers/hid/usbhid/usbkbd.c              (usb_kbd_keycode)
-#  win32:     Win32: mingw32/winuser.h                              (manually mapped)
-# xwinxt:   XWin XT: xorg-server/hw/xwin/{winkeybd.c,winkeynames.h} (xt + manually transcribed)
-# xkbdxt:   XKBD XT: xf86-input-keyboard/src/at_scancode.c
-#(xt + manually transcribed)
-#    x11: X11 keysyms: http://cgit.freedesktop.org/xorg/proto/x11proto/plain/keysymdef.h
-#
-# Derived data sources
-#
-#    xorgevdev: Xorg +  evdev: linux + an offset
-#      xorgkbd: Xorg +    kbd: xkbdxt + an offset
-#  xorgxquartz: Xorg +   OS-X: osx + an offset
-#     xorgxwin: Xorg + Cygwin: xwinxt + an offset
-#          rfb:   XT over RFB: xtkbd + special re-encoding of high bit
-
-my @basemaps = qw(linux osx atset1 atset2 atset3 xt xtkbd usb win32 xwinxt xkbdxt x11);
-my @derivedmaps = qw(xorgevdev xorgkbd xorgxquartz xorgxwin rfb);
-my @maps = (@basemaps, @derivedmaps);
-
-my %maps;
-
-foreach my $map (@maps) {
-    $maps{$map} = [ [], [] ];
-}
-my %mapcolumns = (
-    osx => 3,
-    atset1 => 4,
-    atset2 => 5,
-    atset3 => 6,
-    xt => 7,
-    xtkbd => 8,
-    usb => 9,
-    win32 => 11,
-    xwinxt => 12,
-    xkbdxt => 13,
-    x11 => 15
-    );
-
-sub help {
-    my $msg = shift;
-    print $msg;
-    print "\n";
-    print "Valid keymaps are:\n";
-    print "\n";
-    foreach my $name (sort { $a cmp $b } keys %maps) {
-	print "  $name\n";
-    }
-    print "\n";
-    exit (1);
-}
-
-if ($#ARGV != 2) {
-    help("syntax: $0 KEYMAPS SRCMAP DSTMAP\n");
-}
-
-my $keymaps = shift @ARGV;
-my $src = shift @ARGV;
-my $dst = shift @ARGV;
-
-help("$src is not a known keymap\n") unless exists $maps{$src};
-help("$dst is not a known keymap\n") unless exists $maps{$dst};
-
-
-open CSV, $keymaps
-    or die "cannot read $keymaps: $!";
-
-my $csv = Text::CSV->new();
-# Discard column headings
-$csv->getline(\*CSV);
-
-my $row;
-while ($row = $csv->getline(\*CSV)) {
-    my $linux = $row->[1];
-
-    $linux = hex($linux) if $linux =~ /0x/;
-
-    my $to = $maps{linux}->[0];
-    my $from = $maps{linux}->[1];
-    $to->[$linux] = $linux;
-    $from->[$linux] = $linux;
-
-    foreach my $name (keys %namecolumns) {
-	my $col = $namecolumns{$name};
-	my $val = $row->[$col];
-
-	$val = "" unless defined $val;
-
-	$names{$name}->[$linux] = $val;
-    }
-
-    foreach my $name (keys %mapcolumns) {
-	my $col = $mapcolumns{$name};
-	my $val = $row->[$col];
-
-	next unless defined $val && $val ne "";
-	$val = hex($val) if $val =~ /0x/;
-
-	$to = $maps{$name}->[0];
-        $from = $maps{$name}->[1];
-	$to->[$linux] = $val;
-	$from->[$val] = $linux;
-    }
-
-    # XXX there are some special cases in kbd to handle
-    # Xorg KBD driver is the Xorg KBD XT codes offset by +8
-    # The XKBD XT codes are the same as normal XT codes
-    # for values <= 83, and completely made up for extended
-    # scancodes :-(
-    ($to, $from) = @{$maps{xorgkbd}};
-    if (defined $maps{xkbdxt}->[0]->[$linux]) {
-	$to->[$linux] = $maps{xkbdxt}->[0]->[$linux] + 8;
-	$from->[$to->[$linux]] = $linux;
-    }
-
-    # Xorg evdev is simply Linux keycodes offset by +8
-    ($to, $from) = @{$maps{xorgevdev}};
-    $to->[$linux] = $linux + 8;
-    $from->[$to->[$linux]] = $linux;
-
-    # Xorg XQuartz is simply OS-X keycodes offset by +8
-    ($to, $from) = @{$maps{xorgxquartz}};
-    if (defined $maps{osx}->[0]->[$linux]) {
-	$to->[$linux] = $maps{osx}->[0]->[$linux] + 8;
-	$from->[$to->[$linux]] = $linux;
-    }
-
-    # RFB keycodes are XT kbd keycodes with a slightly
-    # different encoding of 0xe0 scan codes. RFB uses
-    # the high bit of the first byte, instead of the low
-    # bit of the second byte.
-    ($to, $from) = @{$maps{rfb}};
-    my $xtkbd = $maps{xtkbd}->[0]->[$linux];
-    if (defined $xtkbd) {
-	$to->[$linux] = $xtkbd ? (($xtkbd & 0x100)>>1) | ($xtkbd & 0x7f) : 0;
-	$from->[$to->[$linux]] = $linux;
-    }
-
-    # Xorg Cygwin is the Xorg Cygwin XT codes offset by +8
-    # The Cygwin XT codes are the same as normal XT codes
-    # for values <= 83, and completely made up for extended
-    # scancodes :-(
-    ($to, $from) = @{$maps{xorgxwin}};
-    if (defined $maps{xwinxt}->[0]->[$linux]) {
-	$to->[$linux] = $maps{xwinxt}->[0]->[$linux] + 8;
-	$from->[$to->[$linux]] = $linux;
-    }
-
-#    print $linux, "\n";
-}
-
-close CSV;
-
-my $srcmap = $maps{$src}->[1];
-my $dstmap = $maps{$dst}->[0];
-
-printf "static const guint16 keymap_%s2%s[] = {\n", $src, $dst;
-
-for (my $i = 0 ; $i <= $#{$srcmap} ; $i++) {
-    my $linux = $srcmap->[$i] || 0;
-    my $j = $dstmap->[$linux];
-    next unless $linux && $j;
-
-    my $srcname = $names{$src}->[$linux] if exists $names{$src};
-    my $dstname = $names{$dst}->[$linux] if exists $names{$dst};
-    my $vianame = $names{linux}->[$linux] unless $src eq "linux" || $dst eq "linux";
-
-    $srcname = "" unless $srcname;
-    $dstname = "" unless $dstname;
-    $vianame = "" unless $vianame;
-    $srcname = " ($srcname)" if $srcname;
-    $dstname = " ($dstname)" if $dstname;
-    $vianame = " ($vianame)" if $vianame;
-
-    my $comment;
-    if ($src ne "linux" && $dst ne "linux") {
-	$comment = sprintf "%d%s => %d%s via %d%s", $i, $srcname, $j, $dstname, $linux, $vianame;
-    } else {
-	$comment = sprintf "%d%s => %d%s", $i, $srcname, $j, $dstname;
-    }
-
-    my $data = sprintf "[0x%x] = 0x%x,", $i, $j;
-
-    printf "  %-20s /* %s */\n", $data, $comment;
-}
-
-print "};\n";
diff --git a/gtk/keymaps.csv b/gtk/keymaps.csv
deleted file mode 100644
index 9052e3b..0000000
--- a/gtk/keymaps.csv
+++ /dev/null
@@ -1,490 +0,0 @@
-"Linux Name","Linux Keycode","OS-X Name","OS-X Keycode","AT set1 keycode","AT set2 keycode","AT set3 keycode",XT,"XT KBD","USB Keycodes","Win32 Name","Win32 Keycode","Xwin XT","Xfree86 KBD XT","X11 keysym","X11 keycode"
-KEY_RESERVED,0,,,,,,,,,,,,,,
-KEY_ESC,1,Escape,0x35,1,118,8,1,1,41,VK_ESCAPE,0x1b,1,1,XK_Escape,0xff1b
-KEY_1,2,ANSI_1,0x12,2,22,22,2,2,30,VK_1,0x31,2,2,XK_1,0x0031
-KEY_2,3,ANSI_2,0x13,3,30,30,3,3,31,VK_2,0x32,3,3,XK_2,0x0032
-KEY_3,4,ANSI_3,0x14,4,38,38,4,4,32,VK_3,0x33,4,4,XK_3,0x0033
-KEY_4,5,ANSI_4,0x15,5,37,37,5,5,33,VK_4,0x34,5,5,XK_4,0x0034
-KEY_5,6,ANSI_5,0x17,6,46,46,6,6,34,VK_5,0x35,6,6,XK_5,0x0035
-KEY_6,7,ANSI_6,0x16,7,54,54,7,7,35,VK_6,0x36,7,7,XK_6,0x0036
-KEY_7,8,ANSI_7,0x1a,8,61,61,8,8,36,VK_7,0x37,8,8,XK_7,0x0037
-KEY_8,9,ANSI_8,0x1c,9,62,62,9,9,37,VK_8,0x38,9,9,XK_8,0x0038
-KEY_9,10,ANSI_9,0x19,10,70,70,10,10,38,VK_9,0x39,10,10,XK_9,0x0039
-KEY_0,11,ANSI_0,0x1d,11,69,69,11,11,39,VK_0,0x30,11,11,XK_0,0x0030
-KEY_MINUS,12,ANSI_Minus,0x1b,12,78,78,12,12,45,VK_OEM_MINUS,0xbd,12,12,XK_minus,0x002d
-KEY_EQUAL,13,ANSI_Equal,0x18,13,85,85,13,13,46,VK_OEM_PLUS,0xbb,13,13,XK_equal,0x003d
-KEY_BACKSPACE,14,Delete,0x33,14,102,102,14,14,42,VK_BACK,0x08,14,14,XK_BackSpace,0xff08
-KEY_TAB,15,Tab,0x30,15,13,13,15,15,43,VK_TAB,0x09,15,15,XK_Tab,0xff09
-KEY_Q,16,ANSI_Q,0xc,16,21,21,16,16,20,VK_Q,0x51,16,16,XK_Q,0x0051
-KEY_Q,16,ANSI_Q,0xc,16,21,21,16,16,20,VK_Q,0x51,16,16,XK_q,0x0071
-KEY_W,17,ANSI_W,0xd,17,29,29,17,17,26,VK_W,0x57,17,17,XK_W,0x0057
-KEY_W,17,ANSI_W,0xd,17,29,29,17,17,26,VK_W,0x57,17,17,XK_w,0x0077
-KEY_E,18,ANSI_E,0xe,18,36,36,18,18,8,VK_E,0x45,18,18,XK_E,0x0045
-KEY_E,18,ANSI_E,0xe,18,36,36,18,18,8,VK_E,0x45,18,18,XK_e,0x0065
-KEY_R,19,ANSI_R,0xf,19,45,45,19,19,21,VK_R,0x52,19,19,XK_R,0x0052
-KEY_R,19,ANSI_R,0xf,19,45,45,19,19,21,VK_R,0x52,19,19,XK_r,0x0072
-KEY_T,20,ANSI_T,0x11,20,44,44,20,20,23,VK_T,0x54,20,20,XK_T,0x0054
-KEY_T,20,ANSI_T,0x11,20,44,44,20,20,23,VK_T,0x54,20,20,XK_t,0x0074
-KEY_Y,21,ANSI_Y,0x10,21,53,53,21,21,28,VK_Y,0x59,21,21,XK_Y,0x0059
-KEY_Y,21,ANSI_Y,0x10,21,53,53,21,21,28,VK_Y,0x59,21,21,XK_y,0x0079
-KEY_U,22,ANSI_U,0x20,22,60,60,22,22,24,VK_U,0x55,22,22,XK_U,0x0055
-KEY_U,22,ANSI_U,0x20,22,60,60,22,22,24,VK_U,0x55,22,22,XK_u,0x0075
-KEY_I,23,ANSI_I,0x22,23,67,67,23,23,12,VK_I,0x49,23,23,XK_I,0x0049
-KEY_I,23,ANSI_I,0x22,23,67,67,23,23,12,VK_I,0x49,23,23,XK_i,0x0069
-KEY_O,24,ANSI_O,0x1f,24,68,68,24,24,18,VK_O,0x4f,24,24,XK_O,0x004f
-KEY_O,24,ANSI_O,0x1f,24,68,68,24,24,18,VK_O,0x4f,24,24,XK_o,0x006f
-KEY_P,25,ANSI_P,0x23,25,77,77,25,25,19,VK_P,0x50,25,25,XK_P,0x0050
-KEY_P,25,ANSI_P,0x23,25,77,77,25,25,19,VK_P,0x50,25,25,XK_p,0x0070
-KEY_LEFTBRACE,26,ANSI_LeftBracket,0x21,26,84,84,26,26,47,VK_OEM_4,0xdb,26,26,XK_bracketleft,0x005b
-KEY_RIGHTBRACE,27,ANSI_RightBracket,0x1e,27,91,91,27,27,48,VK_OEM_6,0xdd,27,27,XK_bracketright,0x005d
-KEY_ENTER,28,Return,0x24,28,90,90,28,28,40,VK_RETURN,0x0d,28,28,XK_Return,0xff0d
-KEY_LEFTCTRL,29,Control,0x3b,29,20,17,29,29,224,VK_LCONTROL,0xa2,29,29,XK_Control_L,0xffe3
-KEY_LEFTCTRL,29,Control,0x3b,29,20,17,29,29,224,VK_CONTROL,0x11,29,29,XK_Control_L,0xffe3
-KEY_A,30,ANSI_A,0x0,30,28,28,30,30,4,VK_A,0x41,30,30,XK_A,0x0041
-KEY_A,30,ANSI_A,0x0,30,28,28,30,30,4,VK_A,0x41,30,30,XK_a,0x0061
-KEY_S,31,ANSI_S,0x1,31,27,27,31,31,22,VK_S,0x53,31,31,XK_S,0x0053
-KEY_S,31,ANSI_S,0x1,31,27,27,31,31,22,VK_S,0x53,31,31,XK_s,0x0073
-KEY_D,32,ANSI_D,0x2,32,35,35,32,32,7,VK_D,0x44,32,32,XK_D,0x0044
-KEY_D,32,ANSI_D,0x2,32,35,35,32,32,7,VK_D,0x44,32,32,XK_d,0x0064
-KEY_F,33,ANSI_F,0x3,33,43,43,33,33,9,VK_F,0x46,33,33,XK_F,0x0046
-KEY_F,33,ANSI_F,0x3,33,43,43,33,33,9,VK_F,0x46,33,33,XK_f,0x0066
-KEY_G,34,ANSI_G,0x5,34,52,52,34,34,10,VK_G,0x47,34,34,XK_G,0x0047
-KEY_G,34,ANSI_G,0x5,34,52,52,34,34,10,VK_G,0x47,34,34,XK_g,0x0067
-KEY_H,35,ANSI_H,0x4,35,51,51,35,35,11,VK_H,0x48,35,35,XK_H,0x0048
-KEY_H,35,ANSI_H,0x4,35,51,51,35,35,11,VK_H,0x48,35,35,XK_h,0x0068
-KEY_J,36,ANSI_J,0x26,36,59,59,36,36,13,VK_J,0x4a,36,36,XK_J,0x004a
-KEY_J,36,ANSI_J,0x26,36,59,59,36,36,13,VK_J,0x4a,36,36,XK_j,0x006a
-KEY_K,37,ANSI_K,0x28,37,66,66,37,37,14,VK_K,0x4b,37,37,XK_K,0x004b
-KEY_K,37,ANSI_K,0x28,37,66,66,37,37,14,VK_K,0x4b,37,37,XK_K,0x006b
-KEY_L,38,ANSI_L,0x25,38,75,75,38,38,15,VK_L,0x4c,38,38,XK_L,0x004c
-KEY_L,38,ANSI_L,0x25,38,75,75,38,38,15,VK_L,0x4c,38,38,XK_l,0x006c
-KEY_SEMICOLON,39,ANSI_Semicolon,0x29,39,76,76,39,39,51,VK_OEM_1,0xba,39,39,XK_semicolon,0x003b
-KEY_APOSTROPHE,40,ANSI_Quote,0x27,40,82,82,40,40,52,VK_OEM_7,0xde,40,40,XK_apostrophe,0x0027
-KEY_GRAVE,41,ANSI_Grave,0x32,41,14,14,41,41,53,VK_OEM_3,0xc0,41,41,XK_grave,0x0060
-KEY_SHIFT,42,Shift,0x38,42,18,18,42,42,225,VK_SHIFT,0x10,42,42,XK_Shift_L,0xffe1
-KEY_LEFTSHIFT,42,Shift,0x38,42,18,18,42,42,225,VK_LSHIFT,0xa0,42,42,XK_Shift_L,0xffe1
-KEY_BACKSLASH,43,ANSI_Backslash,0x2a,43,93,93,43,43,50,VK_OEM_5,0xdc,43,43,XK_backslash,0x005c
-KEY_Z,44,ANSI_Z,0x6,44,26,26,44,44,29,VK_Z,0x5a,44,44,XK_Z,0x005a
-KEY_Z,44,ANSI_Z,0x6,44,26,26,44,44,29,VK_Z,0x5a,44,44,XK_z,0x007a
-KEY_X,45,ANSI_X,0x7,45,34,34,45,45,27,VK_X,0x58,45,45,XK_X,0x0058
-KEY_X,45,ANSI_X,0x7,45,34,34,45,45,27,VK_X,0x58,45,45,XK_x,0x0078
-KEY_C,46,ANSI_C,0x8,46,33,33,46,46,6,VK_C,0x43,46,46,XK_C,0x0043
-KEY_C,46,ANSI_C,0x8,46,33,33,46,46,6,VK_C,0x43,46,46,XK_c,0x0063
-KEY_V,47,ANSI_V,0x9,47,42,42,47,47,25,VK_V,0x56,47,47,XK_V,0x0056
-KEY_V,47,ANSI_V,0x9,47,42,42,47,47,25,VK_V,0x56,47,47,XK_v,0x0076
-KEY_B,48,ANSI_B,0xb,48,50,50,48,48,5,VK_B,0x42,48,48,XK_B,0x0042
-KEY_B,48,ANSI_B,0xb,48,50,50,48,48,5,VK_B,0x42,48,48,XK_b,0x0062
-KEY_N,49,ANSI_N,0x2d,49,49,49,49,49,17,VK_N,0x4e,49,49,XK_N,0x004e
-KEY_N,49,ANSI_N,0x2d,49,49,49,49,49,17,VK_N,0x4e,49,49,XK_n,0x006e
-KEY_M,50,ANSI_M,0x2e,50,58,58,50,50,16,VK_M,0x4d,50,50,XK_M,0x004d
-KEY_M,50,ANSI_M,0x2e,50,58,58,50,50,16,VK_M,0x4d,50,50,XK_m,0x006d
-KEY_COMMA,51,ANSI_Comma,0x2b,51,65,65,51,51,54,VK_OEM_COMMA,0xbc,51,51,XK_comma,0x002c
-KEY_DOT,52,ANSI_Period,0x2f,52,73,73,52,52,55,VK_OEM_PERIOD,0xbe,52,52,XK_period,0x002e
-KEY_SLASH,53,ANSI_Slash,0x2c,53,74,74,53,53,56,VK_OEM_2,0xbf,53,53,XK_slash,0x002f
-KEY_RIGHTSHIFT,54,RightShift,0x3c,54,89,89,54,54,229,VK_RSHIFT,0xa1,54,54,XK_Shift_R,0xffe2
-KEY_KPASTERISK,55,ANSI_KeypadMultiply,0x43,55,124,126,55,55,85,VK_MULTIPLY,0x6a,55,55,XK_multiply,0x00d7
-KEY_LEFTALT,56,Option,0x3a,56,17,25,56,56,226,VK_LMENU,0xa4,56,56,XK_Alt_L,0xffe9
-KEY_LEFTALT,56,Option,0x3a,56,17,25,56,56,226,VK_MENU,0x12,56,56,XK_Alt_L,0xffe9
-KEY_SPACE,57,Space,0x31,57,41,41,57,57,44,VK_SPACE,0x20,57,57,XK_space,0x0020
-KEY_CAPSLOCK,58,CapsLock,0x39,58,88,20,58,58,57,VK_CAPITAL,0x14,58,58,XK_Caps_Lock,0xffe5
-KEY_F1,59,F1,0x7a,59,5,7,59,59,58,VK_F1,0x70,59,59,XK_F1,0xffbe
-KEY_F2,60,F2,0x78,60,6,15,60,60,59,VK_F2,0x71,60,60,XK_F2,0xffbf
-KEY_F3,61,F3,0x63,61,4,23,61,61,60,VK_F3,0x72,61,61,XK_F3,0xffc0
-KEY_F4,62,F4,0x76,62,12,31,62,62,61,VK_F4,0x73,62,62,XK_F4,0xffc1
-KEY_F5,63,F5,0x60,63,3,39,63,63,62,VK_F5,0x74,63,63,XK_F5,0xffc2
-KEY_F6,64,F6,0x61,64,11,47,64,64,63,VK_F6,0x75,64,64,XK_F6,0xffc3
-KEY_F7,65,F7,0x62,65,259,55,65,65,64,VK_F7,0x76,65,65,XK_F7,0xffc4
-KEY_F8,66,F8,0x64,66,10,63,66,66,65,VK_F8,0x77,66,66,XK_F8,0xffc5
-KEY_F9,67,F9,0x65,67,1,71,67,67,66,VK_F9,0x78,67,67,XK_F9,0xffc6
-KEY_F10,68,F10,0x6d,68,9,79,68,68,67,VK_F10,0x79,68,68,XK_F10,0xffc7
-KEY_NUMLOCK,69,,,69,119,118,69,69,83,VK_NUMLOCK,0x90,69,69,XK_Num_Lock,0xff7f
-KEY_SCROLLLOCK,70,,,70,126,95,70,70,71,VK_SCROLL,0x91,70,70,XK_Scroll_Lock,0xff14
-KEY_KP7,71,ANSI_Keypad7,0x59,71,108,108,71,71,95,VK_NUMPAD7,0x67,71,71,XK_KP_7,0xffb7
-KEY_KP8,72,ANSI_Keypad8,0x5b,72,117,117,72,72,96,VK_NUMPAD8,0x68,72,72,XK_KP_8,0xffb8
-KEY_KP9,73,ANSI_Keypad9,0x5c,73,125,125,73,73,97,VK_NUMPAD9,0x69,73,73,XK_KP_9,0xffb9
-KEY_KPMINUS,74,ANSI_KeypadMinus,0x4e,74,123,132,74,74,86,VK_SUBTRACT,0x6d,74,74,XK_KP_Subtract,0xffad
-KEY_KP4,75,ANSI_Keypad4,0x56,75,107,107,75,75,92,VK_NUMPAD4,0x64,75,75,XK_KP_4,0xffb4
-KEY_KP5,76,ANSI_Keypad5,0x57,76,115,115,76,76,93,VK_NUMPAD5,0x65,76,76,XK_KP_5,0xffb5
-KEY_KP6,77,ANSI_Keypad6,0x58,77,116,116,77,77,94,VK_NUMPAD6,0x66,77,77,XK_KP_6,0xffb6
-KEY_KPPLUS,78,ANSI_KeypadPlus,0x45,78,121,124,78,78,87,VK_ADD,0x6b,78,78,XK_KP_Add,0xffab
-KEY_KP1,79,ANSI_Keypad1,0x53,79,105,105,79,79,89,VK_NUMPAD1,0x61,79,79,XK_KP_1,0xffb1
-KEY_KP2,80,ANSI_Keypad2,0x54,80,114,114,80,80,90,VK_NUMPAD2,0x62,80,80,XK_KP_2,0xffb2
-KEY_KP3,81,ANSI_Keypad3,0x55,81,122,122,81,81,91,VK_NUMPAD3,0x63,81,81,XK_KP_3,0xffb3
-KEY_KP0,82,ANSI_Keypad0,0x52,82,112,112,82,82,98,VK_NUMPAD0,0x60,82,82,XK_KP_0,0xffb0
-KEY_KPDOT,83,ANSI_KeypadDecimal,0x41,83,113,113,83,83,99,VK_DECIMAL,0x6e,83,83,XK_KP_Decimal,0xffae
-,84,,,,,,,84,,,,,
-KEY_ZENKAKUHANKAKU,85,,,118,95,,,118,148,,,,
-KEY_102ND,86,,,86,97,19,,86,100,VK_OEM_102,0xe1,,
-KEY_F11,87,F11,0x67,87,120,86,101,87,68,VK_F11,0x7a,,
-KEY_F12,88,F12,0x6f,88,7,94,102,88,69,VK_F12,0x7b,,
-KEY_RO,89,,,115,81,,,115,135,,,,
-KEY_KATAKANA,90,JIS_Kana????,0x68,120,99,,,120,146,VK_KANA,0x15,,
-KEY_HIRAGANA,91,,,119,98,,,119,147,,,,
-KEY_HENKAN,92,,,121,100,134,,121,138,,,,
-KEY_KATAKANAHIRAGANA,93,,,112,19,135,,112,136,,,0xc8,0xc8
-KEY_MUHENKAN,94,,,123,103,133,,123,139,,,,
-KEY_KPJPCOMMA,95,JIS_KeypadComma,0x5f,92,39,,,92,140,,,,,XK_KP_Separator,0xffac
-KEY_KPENTER,96,ANSI_KeypadEnter,0x4c,,158,121,,284,88,,,0x64,0x64,XK_KP_Enter,0xff8d
-KEY_RIGHTCTRL,97,RightControl,0x3e,,,88,,285,228,VK_RCONTROL,0xa3,0x65,0x65,XK_Control_R,0xffe4
-KEY_KPSLASH,98,ANSI_KeypadDivide,0x4b,,181,119,,309,84,VK_DIVIDE,0x6f,0x68,0x68,XK_KP_Divide,0xffaf
-KEY_SYSRQ,99,,,84,260,87,,84,70,"VK_SNAPSHOT ???",0x2c,0x67,0x67,XK_Sys_Req,0xff15
-KEY_RIGHTALT,100,RightOption,0x3d,,,57,,312,230,VK_RMENU,0xa5,0x69,0x69,XK_Alt_R,0xffea
-KEY_LINEFEED,101,,,,,,,91,,,,,
-KEY_HOME,102,Home,0x73,,224,110,,327,74,VK_HOME,0x24,0x59,0x59,XK_Home,0xff50
-KEY_UP,103,UpArrow,0x7e,,236,99,109,328,82,VK_UP,0x26,0x5a,0x5a,XK_Up,0xff52
-KEY_PAGEUP,104,PageUp,0x74,,201,111,,329,75,VK_PRIOR,0x21,0x5b,0x5b,XK_Page_Up,0xff55
-KEY_LEFT,105,LeftArrow,0x7b,,203,97,111,331,80,VK_LEFT,0x25,0x5c,0x5c,XK_Left,0xff51
-KEY_RIGHT,106,RightArrow,0x7c,,205,106,112,333,79,VK_RIGHT,0x27,0x5e,0x5e,XK_Right,0xff53
-KEY_END,107,End,0x77,,225,101,,335,77,VK_END,0x23,0x5f,0x5f,XK_End,0xff57
-KEY_DOWN,108,DownArrow,0x7d,,254,96,110,336,81,VK_DOWN,0x28,0x60,0x60,XK_Down,0xff54
-KEY_PAGEDOWN,109,PageDown,0x79,,243,109,,337,78,VK_NEXT,0x22,0x61,0x61,XK_Page_Down,0xff56
-KEY_INSERT,110,,,,210,103,107,338,73,VK_INSERT,0x2d,0x62,0x62,XK_Insert,0xff63
-KEY_DELETE,111,ForwardDelete,0x75,,244,100,108,339,76,VK_DELETE,0x2e,0x63,0x63,XK_Delete,0xffff
-KEY_MACRO,112,,,,239,142,,367,,,,,
-KEY_MUTE,113,Mute,0x4a,,251,156,,288,239,VK_VOLUME_MUTE,0xad,,,
-KEY_VOLUMEDOWN,114,VolumeDown,0x49,,,157,,302,238,VK_VOLUME_DOWN,0xae,,
-KEY_VOLUMEUP,115,VolumeUp,0x48,,233,149,,304,237,VK_VOLUME_UP,0xaf,,
-KEY_POWER,116,,,,,,,350,102,,,,
-KEY_KPEQUAL,117,ANSI_KeypadEquals,0x51,89,15,,,89,103,,,0x76,0x76,XK_KP_Equal,0xffbd
-KEY_KPPLUSMINUS,118,,,,206,,,334,,,,,
-KEY_PAUSE,119,,,,198,98,,326,72,VK_PAUSE,0x013,0x66,0x66,XK_Pause,0xff13
-KEY_SCALE,120,,,,,,,267,,,,,
-KEY_KPCOMMA,121,ANSI_KeypadClear????,0x47,126,109,,,126,133,VK_SEPARATOR??,0x6c,,
-KEY_HANGEUL,122,,,,,,,,144,VK_HANGEUL,0x15,,
-KEY_HANJA,123,,,,,,,269,145,VK_HANJA,0x19,,
-KEY_YEN,124,JIS_Yen,0x5d,125,106,,,125,137,,,0x7d,0x7d
-KEY_LEFTMETA,125,Command,0x37,,,139,,347,227,VK_LWIN,0x5b,0x6b,0x6b,XK_Meta_L,0xffe7
-KEY_RIGHTMETA,126,,,,,140,,348,231,VK_RWIN,0x5c,0x6c,0x6c,XK_Meta_R,0xffe8
-KEY_COMPOSE,127,Function,0x3f,,,141,,349,101,VK_APPS,0x5d,0x6d,0x6d
-KEY_STOP,128,,,,,10,,360,243,VK_BROWSER_STOP,0xa9,,
-KEY_AGAIN,129,,,,,11,,261,121,,,,
-KEY_PROPS,130,,,,,12,,262,118,,,,
-KEY_UNDO,131,,,,,16,,263,122,,,,
-KEY_FRONT,132,,,,,,,268,119,,,,
-KEY_COPY,133,,,,,24,,376,124,,,,
-KEY_OPEN,134,,,,,32,,100,116,,,,
-KEY_PASTE,135,,,,,40,,101,125,,,,
-KEY_FIND,136,,,,,48,,321,244,,,,
-KEY_CUT,137,,,,,56,,316,123,,,,
-KEY_HELP,138,,,,,9,,373,117,VK_HELP,0x2f,,,XK_Help,0xff6a
-KEY_MENU,139,,,,,145,,286,,,,,
-KEY_CALC,140,,,,174,163,,289,251,,,,
-KEY_SETUP,141,,,,,,,102,,,,,
-KEY_SLEEP,142,,,,,,,351,248,VK_SLEEP,0x5f,,
-KEY_WAKEUP,143,,,,,,,355,,,,,
-KEY_FILE,144,,,,,,,103,,,,,
-KEY_SENDFILE,145,,,,,,,104,,,,,
-KEY_DELETEFILE,146,,,,,,,105,,,,,
-KEY_XFER,147,,,,,162,,275,,,,,
-KEY_PROG1,148,,,,,160,,287,,,,,
-KEY_PROG2,149,,,,,161,,279,,,,,
-KEY_WWW,150,,,,,,,258,240,,,,
-KEY_MSDOS,151,,,,,,,106,,,,,
-KEY_SCREENLOCK,152,,,,,150,,274,249,,,,
-KEY_DIRECTION,153,,,,,,,107,,,,,
-KEY_CYCLEWINDOWS,154,,,,,155,,294,,,,,
-KEY_MAIL,155,,,,,,,364,,,,,
-KEY_BOOKMARKS,156,,,,,,,358,,,,,
-KEY_COMPUTER,157,,,,,,,363,,,,,
-KEY_BACK,158,,,,,,,362,241,VK_BROWSER_BACK,0xa6,,
-KEY_FORWARD,159,,,,,,,361,242,VK_BROWSER_FORWARD,0xa7,,
-KEY_CLOSECD,160,,,,,154,,291,,,,,
-KEY_EJECTCD,161,,,,,,,108,236,,,,
-KEY_EJECTCLOSECD,162,,,,,,,381,,,,,
-KEY_NEXTSONG,163,,,,241,147,,281,235,VK_MEDIA_NEXT_TRACK,0xb0,,
-KEY_PLAYPAUSE,164,,,,173,,,290,232,VK_MEDIA_PLAY_PAUSE,0xb3,,
-KEY_PREVIOUSSONG,165,,,,250,148,,272,234,VK_MEDIA_PREV_TRACK,0xb1,,
-KEY_STOPCD,166,,,,164,152,,292,233,VK_MEDIA_STOP,0xb2,,
-KEY_RECORD,167,,,,,158,,305,,,,,
-KEY_REWIND,168,,,,,159,,280,,,,,
-KEY_PHONE,169,,,,,,,99,,,,,
-KEY_ISO,170,ISO_Section,0xa,,,,,112,,,,,
-KEY_CONFIG,171,,,,,,,257,,,,,
-KEY_HOMEPAGE,172,,,,178,151,,306,,VK_BROWSER_HOME,0xac,,
-KEY_REFRESH,173,,,,,,,359,250,VK_BROWSER_REFRESH,0xa8,,
-KEY_EXIT,174,,,,,,,113,,,,,
-KEY_MOVE,175,,,,,,,114,,,,,
-KEY_EDIT,176,,,,,,,264,247,,,,
-KEY_SCROLLUP,177,,,,,,,117,245,,,,
-KEY_SCROLLDOWN,178,,,,,,,271,246,,,,
-KEY_KPLEFTPAREN,179,,,,,,,374,182,,,,
-KEY_KPRIGHTPAREN,180,,,,,,,379,183,,,,
-KEY_NEW,181,,,,,,,265,,,,,
-KEY_REDO,182,,,,,,,266,,,,,
-KEY_F13,183,F13,0x69,93,47,127,,93,104,VK_F13,0x7c,0x6e,0x6e
-KEY_F14,184,F14,0x6b,94,55,128,,94,105,VK_F14,0x7d,0x6f,0x6f
-KEY_F15,185,F15,0x71,95,63,129,,95,106,VK_F15,0x7e,0x70,0x70
-KEY_F16,186,F16,0x6a,,,130,,85,107,VK_F16,0x7f,0x71,0x71
-KEY_F17,187,F17,0x40,,,131,,259,108,VK_F17,0x80,0x72,0x72
-KEY_F18,188,F18,0x4f,,,,,375,109,VK_F18,0x81,,
-KEY_F19,189,F19,0x50,,,,,260,110,VK_F19,0x82,,
-KEY_F20,190,F20,0x5a,,,,,90,111,VK_F20,0x83,,
-KEY_F21,191,,,,,,,116,112,VK_F21,0x84,,
-KEY_F22,192,,,,,,,377,113,VK_F22,0x85,,
-KEY_F23,193,,,,,,,109,114,VK_F23,0x86,,
-KEY_F24,194,,,,,,,111,115,VK_F24,0x87,,
-,195,,,,,,,277,,,,,
-,196,,,,,,,278,,,,,
-,197,,,,,,,282,,,,,
-,198,,,,,,,283,,,,,
-,199,,,,,,,295,,,,,
-KEY_PLAYCD,200,,,,,,,296,,,,,
-KEY_PAUSECD,201,,,,,,,297,,,,,
-KEY_PROG3,202,,,,,,,299,,,,,
-KEY_PROG4,203,,,,,,,300,,,,,
-KEY_DASHBOARD,204,,,,,,,301,,,,,
-KEY_SUSPEND,205,,,,,,,293,,,,,
-KEY_CLOSE,206,,,,,,,303,,,,,
-KEY_PLAY,207,,,,,,,307,,VK_PLAY,0xfa,,
-KEY_FASTFORWARD,208,,,,,,,308,,,,,
-KEY_BASSBOOST,209,,,,,,,310,,,,,
-KEY_PRINT,210,,,,,,,313,,VK_PRINT,0x2a,,
-KEY_HP,211,,,,,,,314,,,,,
-KEY_CAMERA,212,,,,,,,315,,,,,
-KEY_SOUND,213,,,,,,,317,,,,,
-KEY_QUESTION,214,,,,,,,318,,,,,
-KEY_EMAIL,215,,,,,,,319,,VK_LAUNCH_MAIL,0xb4,,
-KEY_CHAT,216,,,,,,,320,,,,,
-KEY_SEARCH,217,,,,,,,357,,VK_BROWSER_SEARCH,0xaa,,
-KEY_CONNECT,218,,,,,,,322,,,,,
-KEY_FINANCE,219,,,,,,,323,,,,,
-KEY_SPORT,220,,,,,,,324,,,,,
-KEY_SHOP,221,,,,,,,325,,,,,
-KEY_ALTERASE,222,,,,,,,276,,,,,
-KEY_CANCEL,223,,,,,,,330,,,,,
-KEY_BRIGHTNESSDOWN,224,,,,,,,332,,,,,
-KEY_BRIGHTNESSUP,225,,,,,,,340,,,,,
-KEY_MEDIA,226,,,,,,,365,,,,,
-KEY_SWITCHVIDEOMODE,227,,,,,,,342,,,,,
-KEY_KBDILLUMTOGGLE,228,,,,,,,343,,,,,
-KEY_KBDILLUMDOWN,229,,,,,,,344,,,,,
-KEY_KBDILLUMUP,230,,,,,,,345,,,,,
-KEY_SEND,231,,,,,,,346,,,,,
-KEY_REPLY,232,,,,,,,356,,,,,
-KEY_FORWARDMAIL,233,,,,,,,270,,,,,
-KEY_SAVE,234,,,,,,,341,,,,,
-KEY_DOCUMENTS,235,,,,,,,368,,,,,
-KEY_BATTERY,236,,,,,,,369,,,,,
-KEY_BLUETOOTH,237,,,,,,,370,,,,,
-KEY_WLAN,238,,,,,,,371,,,,,
-KEY_UWB,239,,,,,,,372,,,,,
-KEY_UNKNOWN,240,,,,,,,,,,,,
-KEY_VIDEO_NEXT,241,,,,,,,,,,,,
-KEY_VIDEO_PREV,242,,,,,,,,,,,,
-KEY_BRIGHTNESS_CYCLE,243,,,,,,,,,,,,
-KEY_BRIGHTNESS_ZERO,244,,,,,,,,,,,,
-KEY_DISPLAY_OFF,245,,,,,,,,,,,,
-KEY_WIMAX,246,,,,,,,,,,,,
-,247,,,,,,,,,,,,
-,248,,,,,,,,,,,,
-,249,,,,,,,,,,,,
-,250,,,,,,,,,,,,
-,251,,,,,,,,,,,,
-,252,,,,,,,,,,,,
-,253,,,,,,,,,,,,
-,254,,,,,,,,,,,,
-,255,,,,182,,,,,,,,
-BTN_MISC,0x100,,,,,,,,,,,,
-BTN_0,0x100,,,,,,,,,VK_LBUTTON,0x01,,
-BTN_1,0x101,,,,,,,,,VK_RBUTTON,0x02,,
-BTN_2,0x102,,,,,,,,,VK_MBUTTON,0x04,,
-BTN_3,0x103,,,,,,,,,VK_XBUTTON1,0x05,,
-BTN_4,0x104,,,,,,,,,VK_XBUTTON2,0x06,,
-BTN_5,0x105,,,,,,,,,,,,
-BTN_6,0x106,,,,,,,,,,,,
-BTN_7,0x107,,,,,,,,,,,,
-BTN_8,0x108,,,,,,,,,,,,
-BTN_9,0x109,,,,,,,,,,,,
-BTN_MOUSE,0x110,,,,,,,,,,,,
-BTN_LEFT,0x110,,,,,,,,,,,,
-BTN_RIGHT,0x111,,,,,,,,,,,,
-BTN_MIDDLE,0x112,,,,,,,,,,,,
-BTN_SIDE,0x113,,,,,,,,,,,,
-BTN_EXTRA,0x114,,,,,,,,,,,,
-BTN_FORWARD,0x115,,,,,,,,,,,,
-BTN_BACK,0x116,,,,,,,,,,,,
-BTN_TASK,0x117,,,,,,,,,,,,
-BTN_JOYSTICK,0x120,,,,,,,,,,,,
-BTN_TRIGGER,0x120,,,,,,,,,,,,
-BTN_THUMB,0x121,,,,,,,,,,,,
-BTN_THUMB2,0x122,,,,,,,,,,,,
-BTN_TOP,0x123,,,,,,,,,,,,
-BTN_TOP2,0x124,,,,,,,,,,,,
-BTN_PINKIE,0x125,,,,,,,,,,,,
-BTN_BASE,0x126,,,,,,,,,,,,
-BTN_BASE2,0x127,,,,,,,,,,,,
-BTN_BASE3,0x128,,,,,,,,,,,,
-BTN_BASE4,0x129,,,,,,,,,,,,
-BTN_BASE5,0x12a,,,,,,,,,,,,
-BTN_BASE6,0x12b,,,,,,,,,,,,
-BTN_DEAD,0x12f,,,,,,,,,,,,
-BTN_GAMEPAD,0x130,,,,,,,,,,,,
-BTN_A,0x130,,,,,,,,,,,,
-BTN_B,0x131,,,,,,,,,,,,
-BTN_C,0x132,,,,,,,,,,,,
-BTN_X,0x133,,,,,,,,,,,,
-BTN_Y,0x134,,,,,,,,,,,,
-BTN_Z,0x135,,,,,,,,,,,,
-BTN_TL,0x136,,,,,,,,,,,,
-BTN_TR,0x137,,,,,,,,,,,,
-BTN_TL2,0x138,,,,,,,,,,,,
-BTN_TR2,0x139,,,,,,,,,,,,
-BTN_SELECT,0x13a,,,,,,,,,,,,
-BTN_START,0x13b,,,,,,,,,,,,
-BTN_MODE,0x13c,,,,,,,,,,,,
-BTN_THUMBL,0x13d,,,,,,,,,,,,
-BTN_THUMBR,0x13e,,,,,,,,,,,,
-BTN_DIGI,0x140,,,,,,,,,,,,
-BTN_TOOL_PEN,0x140,,,,,,,,,,,,
-BTN_TOOL_RUBBER,0x141,,,,,,,,,,,,
-BTN_TOOL_BRUSH,0x142,,,,,,,,,,,,
-BTN_TOOL_PENCIL,0x143,,,,,,,,,,,,
-BTN_TOOL_AIRBRUSH,0x144,,,,,,,,,,,,
-BTN_TOOL_FINGER,0x145,,,,,,,,,,,,
-BTN_TOOL_MOUSE,0x146,,,,,,,,,,,,
-BTN_TOOL_LENS,0x147,,,,,,,,,,,,
-BTN_TOUCH,0x14a,,,,,,,,,,,,
-BTN_STYLUS,0x14b,,,,,,,,,,,,
-BTN_STYLUS2,0x14c,,,,,,,,,,,,
-BTN_TOOL_DOUBLETAP,0x14d,,,,,,,,,,,,
-BTN_TOOL_TRIPLETAP,0x14e,,,,,,,,,,,,
-BTN_TOOL_QUADTAP,0x14f,,,,,,,,,,,,
-BTN_WHEEL,0x150,,,,,,,,,,,,
-BTN_GEAR_DOWN,0x150,,,,,,,,,,,,
-BTN_GEAR_UP,0x151,,,,,,,,,,,,
-KEY_OK,0x160,,,,,,,,,,,,
-KEY_SELECT,0x161,,,,,,,,,VK_SELECT,0x29,,,XK_Select,0xff60
-KEY_GOTO,0x162,,,,,,,,,,,,
-KEY_CLEAR,0x163,,,,,,,,,,,,
-KEY_POWER2,0x164,,,,,,,,,,,,
-KEY_OPTION,0x165,,,,,,,,,,,,
-KEY_INFO,0x166,,,,,,,,,,,,
-KEY_TIME,0x167,,,,,,,,,,,,
-KEY_VENDOR,0x168,,,,,,,,,,,,
-KEY_ARCHIVE,0x169,,,,,,,,,,,,
-KEY_PROGRAM,0x16a,,,,,,,,,,,,
-KEY_CHANNEL,0x16b,,,,,,,,,,,,
-KEY_FAVORITES,0x16c,,,,,,,,,VK_BROWSER_FAVOURITES,0xab,,
-KEY_EPG,0x16d,,,,,,,,,,,,
-KEY_PVR,0x16e,,,,,,,,,,,,
-KEY_MHP,0x16f,,,,,,,,,,,,
-KEY_LANGUAGE,0x170,,,,,,,,,,,,
-KEY_TITLE,0x171,,,,,,,,,,,,
-KEY_SUBTITLE,0x172,,,,,,,,,,,,
-KEY_ANGLE,0x173,,,,,,,,,,,,
-KEY_ZOOM,0x174,,,,,,,,,VK_ZOOM,0xfb,,
-KEY_MODE,0x175,,,,,,,,,,,,
-KEY_KEYBOARD,0x176,,,,,,,,,,,,
-KEY_SCREEN,0x177,,,,,,,,,,,,
-KEY_PC,0x178,,,,,,,,,,,,
-KEY_TV,0x179,,,,,,,,,,,,
-KEY_TV2,0x17a,,,,,,,,,,,,
-KEY_VCR,0x17b,,,,,,,,,,,,
-KEY_VCR2,0x17c,,,,,,,,,,,,
-KEY_SAT,0x17d,,,,,,,,,,,,
-KEY_SAT2,0x17e,,,,,,,,,,,,
-KEY_CD,0x17f,,,,,,,,,,,,
-KEY_TAPE,0x180,,,,,,,,,,,,
-KEY_RADIO,0x181,,,,,,,,,,,,
-KEY_TUNER,0x182,,,,,,,,,,,,
-KEY_PLAYER,0x183,,,,,,,,,,,,
-KEY_TEXT,0x184,,,,,,,,,,,,
-KEY_DVD,0x185,,,,,,,,,,,,
-KEY_AUX,0x186,,,,,,,,,,,,
-KEY_MP3,0x187,,,,,,,,,,,,
-KEY_AUDIO,0x188,,,,,,,,,,,,
-KEY_VIDEO,0x189,,,,,,,,,,,,
-KEY_DIRECTORY,0x18a,,,,,,,,,,,,
-KEY_LIST,0x18b,,,,,,,,,,,,
-KEY_MEMO,0x18c,,,,,,,,,,,,
-KEY_CALENDAR,0x18d,,,,,,,,,,,,
-KEY_RED,0x18e,,,,,,,,,,,,
-KEY_GREEN,0x18f,,,,,,,,,,,,
-KEY_YELLOW,0x190,,,,,,,,,,,,
-KEY_BLUE,0x191,,,,,,,,,,,,
-KEY_CHANNELUP,0x192,,,,,,,,,,,,
-KEY_CHANNELDOWN,0x193,,,,,,,,,,,,
-KEY_FIRST,0x194,,,,,,,,,,,,
-KEY_LAST,0x195,,,,,,,,,,,,
-KEY_AB,0x196,,,,,,,,,,,,
-KEY_NEXT,0x197,,,,,,,,,,,,
-KEY_RESTART,0x198,,,,,,,,,,,,
-KEY_SLOW,0x199,,,,,,,,,,,,
-KEY_SHUFFLE,0x19a,,,,,,,,,,,,
-KEY_BREAK,0x19b,,,,,,,,,,,,
-KEY_PREVIOUS,0x19c,,,,,,,,,,,,
-KEY_DIGITS,0x19d,,,,,,,,,,,,
-KEY_TEEN,0x19e,,,,,,,,,,,,
-KEY_TWEN,0x19f,,,,,,,,,,,,
-KEY_VIDEOPHONE,0x1a0,,,,,,,,,,,,
-KEY_GAMES,0x1a1,,,,,,,,,,,,
-KEY_ZOOMIN,0x1a2,,,,,,,,,,,,
-KEY_ZOOMOUT,0x1a3,,,,,,,,,,,,
-KEY_ZOOMRESET,0x1a4,,,,,,,,,,,,
-KEY_WORDPROCESSOR,0x1a5,,,,,,,,,,,,
-KEY_EDITOR,0x1a6,,,,,,,,,,,,
-KEY_SPREADSHEET,0x1a7,,,,,,,,,,,,
-KEY_GRAPHICSEDITOR,0x1a8,,,,,,,,,,,,
-KEY_PRESENTATION,0x1a9,,,,,,,,,,,,
-KEY_DATABASE,0x1aa,,,,,,,,,,,,
-KEY_NEWS,0x1ab,,,,,,,,,,,,
-KEY_VOICEMAIL,0x1ac,,,,,,,,,,,,
-KEY_ADDRESSBOOK,0x1ad,,,,,,,,,,,,
-KEY_MESSENGER,0x1ae,,,,,,,,,,,,
-KEY_DISPLAYTOGGLE,0x1af,,,,,,,,,,,,
-KEY_SPELLCHECK,0x1b0,,,,,,,,,,,,
-KEY_LOGOFF,0x1b1,,,,,,,,,,,,
-KEY_DOLLAR,0x1b2,,,,,,,,,,,,
-KEY_EURO,0x1b3,,,,,,,,,,,,
-KEY_FRAMEBACK,0x1b4,,,,,,,,,,,,
-KEY_FRAMEFORWARD,0x1b5,,,,,,,,,,,,
-KEY_CONTEXT_MENU,0x1b6,,,,,,,,,,,,
-KEY_MEDIA_REPEAT,0x1b7,,,,,,,,,,,,
-KEY_DEL_EOL,0x1c0,,,,,,,,,,,,
-KEY_DEL_EOS,0x1c1,,,,,,,,,,,,
-KEY_INS_LINE,0x1c2,,,,,,,,,,,,
-KEY_DEL_LINE,0x1c3,,,,,,,,,,,,
-KEY_FN,0x1d0,,,,,,,,,,,,
-KEY_FN_ESC,0x1d1,,,,,,,,,,,,
-KEY_FN_F1,0x1d2,,,,,,,,,,,,
-KEY_FN_F2,0x1d3,,,,,,,,,,,,
-KEY_FN_F3,0x1d4,,,,,,,,,,,,
-KEY_FN_F4,0x1d5,,,,,,,,,,,,
-KEY_FN_F5,0x1d6,,,,,,,,,,,,
-KEY_FN_F6,0x1d7,,,,,,,,,,,,
-KEY_FN_F7,0x1d8,,,,,,,,,,,,
-KEY_FN_F8,0x1d9,,,,,,,,,,,,
-KEY_FN_F9,0x1da,,,,,,,,,,,,
-KEY_FN_F10,0x1db,,,,,,,,,,,,
-KEY_FN_F11,0x1dc,,,,,,,,,,,,
-KEY_FN_F12,0x1dd,,,,,,,,,,,,
-KEY_FN_1,0x1de,,,,,,,,,,,,
-KEY_FN_2,0x1df,,,,,,,,,,,,
-KEY_FN_D,0x1e0,,,,,,,,,,,,
-KEY_FN_E,0x1e1,,,,,,,,,,,,
-KEY_FN_F,0x1e2,,,,,,,,,,,,
-KEY_FN_S,0x1e3,,,,,,,,,,,,
-KEY_FN_B,0x1e4,,,,,,,,,,,,
-KEY_BRL_DOT1,0x1f1,,,,,,,,,,,,
-KEY_BRL_DOT2,0x1f2,,,,,,,,,,,,
-KEY_BRL_DOT3,0x1f3,,,,,,,,,,,,
-KEY_BRL_DOT4,0x1f4,,,,,,,,,,,,
-KEY_BRL_DOT5,0x1f5,,,,,,,,,,,,
-KEY_BRL_DOT6,0x1f6,,,,,,,,,,,,
-KEY_BRL_DOT7,0x1f7,,,,,,,,,,,,
-KEY_BRL_DOT8,0x1f8,,,,,,,,,,,,
-KEY_BRL_DOT9,0x1f9,,,,,,,,,,,,
-KEY_BRL_DOT10,0x1fa,,,,,,,,,,,,
-KEY_NUMERIC_0,0x200,,,,,,,,,,,,
-KEY_NUMERIC_1,0x201,,,,,,,,,,,,
-KEY_NUMERIC_2,0x202,,,,,,,,,,,,
-KEY_NUMERIC_3,0x203,,,,,,,,,,,,
-KEY_NUMERIC_4,0x204,,,,,,,,,,,,
-KEY_NUMERIC_5,0x205,,,,,,,,,,,,
-KEY_NUMERIC_6,0x206,,,,,,,,,,,,
-KEY_NUMERIC_7,0x207,,,,,,,,,,,,
-KEY_NUMERIC_8,0x208,,,,,,,,,,,,
-KEY_NUMERIC_9,0x209,,,,,,,,,,,,
-KEY_NUMERIC_STAR,0x20a,,,,,,,,,,,,
-KEY_NUMERIC_POUND,0x20b,,,,,,,,,,,,
-KEY_RFKILL,0x20c,,,,,,,,,,,,
diff --git a/gtk/map-file b/gtk/map-file
deleted file mode 100644
index d5a073f..0000000
--- a/gtk/map-file
+++ /dev/null
@@ -1,139 +0,0 @@
-SPICEGTK_1 {
-global:
-spice_audio_get;
-spice_audio_get_type;
-spice_audio_new;
-spice_channel_connect;
-spice_channel_destroy;
-spice_channel_disconnect;
-spice_channel_event_get_type;
-spice_channel_flush_async;
-spice_channel_flush_finish;
-spice_channel_get_error;
-spice_channel_get_type;
-spice_channel_new;
-spice_channel_open_fd;
-spice_channel_set_capability;
-spice_channel_string_to_type;
-spice_channel_test_capability;
-spice_channel_test_common_capability;
-spice_channel_type_to_string;
-spice_client_error_quark;
-spice_cursor_channel_get_type;
-spice_display_channel_get_type;
-spice_display_copy_to_guest;
-spice_display_get_grab_keys;
-spice_display_get_pixbuf;
-spice_display_get_primary;
-spice_display_get_type;
-spice_display_key_event_get_type;
-spice_display_mouse_ungrab;
-spice_display_new;
-spice_display_new_with_monitor;
-spice_display_paste_from_guest;
-spice_display_send_keys;
-spice_display_set_grab_keys;
-spice_get_option_group;
-spice_grab_sequence_as_string;
-spice_grab_sequence_copy;
-spice_grab_sequence_free;
-spice_grab_sequence_get_type;
-spice_grab_sequence_new;
-spice_grab_sequence_new_from_string;
-spice_g_signal_connect_object;
-spice_gtk_session_copy_to_guest;
-spice_gtk_session_get;
-spice_gtk_session_get_type;
-spice_gtk_session_paste_from_guest;
-spice_inputs_button_press;
-spice_inputs_button_release;
-spice_inputs_channel_get_type;
-spice_inputs_key_press;
-spice_inputs_key_press_and_release;
-spice_inputs_key_release;
-spice_inputs_lock_get_type;
-spice_inputs_motion;
-spice_inputs_position;
-spice_inputs_set_key_locks;
-spice_main_agent_test_capability;
-spice_main_channel_get_type;
-spice_main_clipboard_grab;
-spice_main_clipboard_notify;
-spice_main_clipboard_release;
-spice_main_clipboard_request;
-spice_main_clipboard_selection_grab;
-spice_main_clipboard_selection_notify;
-spice_main_clipboard_selection_release;
-spice_main_clipboard_selection_request;
-spice_main_file_copy_async;
-spice_main_file_copy_finish;
-spice_main_send_monitor_config;
-spice_main_set_display;
-spice_main_set_display_enabled;
-spice_main_update_display;
-spice_playback_channel_get_type;
-spice_playback_channel_set_delay;
-spice_port_channel_get_type;
-spice_port_event;
-spice_port_write_async;
-spice_port_write_finish;
-spice_record_channel_get_type;
-spice_record_send_data;
-spice_session_connect;
-spice_session_disconnect;
-spice_session_get_channels;
-spice_session_get_proxy_uri;
-spice_session_get_read_only;
-spice_session_get_type;
-spice_session_has_channel_type;
-spice_session_is_for_migration;
-spice_session_migration_get_type;
-spice_session_new;
-spice_session_open_fd;
-spice_session_verify_get_type;
-spice_set_session_option;
-spice_smartcard_channel_get_type;
-spice_smartcard_manager_get;
-spice_smartcard_manager_get_readers;
-spice_smartcard_manager_get_type;
-spice_smartcard_manager_insert_card;
-spice_smartcard_manager_remove_card;
-spice_smartcard_reader_get_type;
-spice_smartcard_reader_insert_card;
-spice_smartcard_reader_is_software;
-spice_smartcard_reader_remove_card;
-spice_uri_get_hostname;
-spice_uri_get_password;
-spice_uri_get_port;
-spice_uri_get_scheme;
-spice_uri_get_type;
-spice_uri_get_user;
-spice_uri_set_hostname;
-spice_uri_set_password;
-spice_uri_set_port;
-spice_uri_set_scheme;
-spice_uri_set_user;
-spice_uri_to_string;
-spice_usb_device_get_description;
-spice_usb_device_get_libusb_device;
-spice_usb_device_get_type;
-spice_usb_device_manager_can_redirect_device;
-spice_usb_device_manager_connect_device_async;
-spice_usb_device_manager_connect_device_finish;
-spice_usb_device_manager_disconnect_device;
-spice_usb_device_manager_get;
-spice_usb_device_manager_get_devices;
-spice_usb_device_manager_get_devices_with_filter;
-spice_usb_device_manager_get_type;
-spice_usb_device_manager_is_device_connected;
-spice_usb_device_widget_get_type;
-spice_usb_device_widget_new;
-spice_usbredir_channel_get_type;
-spice_util_get_debug;
-spice_util_get_version_string;
-spice_util_set_debug;
-spice_uuid_to_string;
-spice_webdav_channel_get_type;
-local:
-*;
-};
diff --git a/gtk/smartcard-manager-priv.h b/gtk/smartcard-manager-priv.h
deleted file mode 100644
index 409c1c5..0000000
--- a/gtk/smartcard-manager-priv.h
+++ /dev/null
@@ -1,37 +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 __SMARTCARD_MANAGER_PRIV_H__
-#define __SMARTCARD_MANAGER_PRIV_H__
-
-#include "config.h"
-#include <gio/gio.h>
-#include "spice-session.h"
-
-G_BEGIN_DECLS
-
-void spice_smartcard_manager_init_async(SpiceSession *session,
-                                        GCancellable *cancellable,
-                                        GAsyncReadyCallback callback,
-                                        gpointer opaque);
-gboolean spice_smartcard_manager_init_finish(SpiceSession *session,
-                                             GAsyncResult *result,
-                                             GError **err);
-
-G_END_DECLS
-
-#endif /* __SMARTCARD_MANAGER_PRIV_H__ */
diff --git a/gtk/smartcard-manager.c b/gtk/smartcard-manager.c
deleted file mode 100644
index 9e228e9..0000000
--- a/gtk/smartcard-manager.c
+++ /dev/null
@@ -1,737 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 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-object.h>
-#include <string.h>
-
-#include "glib-compat.h"
-
-#ifdef USE_SMARTCARD
-#include <vcard_emul.h>
-#include <vevent.h>
-#include <vreader.h>
-#endif
-
-#include "spice-client.h"
-#include "smartcard-manager.h"
-#include "smartcard-manager-priv.h"
-#include "spice-marshal.h"
-
-/**
- * SECTION:smartcard-manager
- * @short_description: smartcard management
- * @title: Spice Smartcard Manager
- * @section_id:
- * @see_also:
- * @stability: Stable
- * @include: smartcard-manager.h
- *
- * #SpiceSmartcardManager monitors smartcard reader plugging/unplugging,
- * and smartcard insertions/removals. It also provides methods to handle
- * software smartcards (to emulate a smartcard reader/smartcard on the
- * guest using 3 certificates available to the client).
- */
-
-/* ------------------------------------------------------------------ */
-/* gobject glue                                                       */
-
-#define SPICE_SMARTCARD_MANAGER_GET_PRIVATE(obj)                                  \
-    (G_TYPE_INSTANCE_GET_PRIVATE ((obj), SPICE_TYPE_SMARTCARD_MANAGER, SpiceSmartcardManagerPrivate))
-
-struct _SpiceSmartcardManagerPrivate {
-    guint monitor_id;
-
-    /* software smartcard reader, the certificates to use for this reader
-     * were given at the channel creation time. This reader has no physical
-     * existence, it's all controlled by explicit software
-     * insertion/removal of cards
-     */
-#ifdef USE_SMARTCARD
-    VReader *software_reader;
-#endif
-};
-
-G_DEFINE_TYPE(SpiceSmartcardManager, spice_smartcard_manager, G_TYPE_OBJECT)
-#ifdef USE_SMARTCARD
-G_DEFINE_BOXED_TYPE(VReader, spice_smartcard_reader, vreader_reference, vreader_free)
-#else
-typedef GObject VReader;
-G_DEFINE_BOXED_TYPE(VReader, spice_smartcard_reader, g_object_ref, g_object_unref)
-#endif
-
-/* Properties */
-enum {
-    PROP_0,
-};
-
-/* Signals */
-enum {
-    SPICE_SMARTCARD_MANAGER_READER_ADDED,
-    SPICE_SMARTCARD_MANAGER_READER_REMOVED,
-    SPICE_SMARTCARD_MANAGER_CARD_INSERTED,
-    SPICE_SMARTCARD_MANAGER_CARD_REMOVED,
-
-    SPICE_SMARTCARD_MANAGER_LAST_SIGNAL,
-};
-
-static guint signals[SPICE_SMARTCARD_MANAGER_LAST_SIGNAL];
-
-#ifdef USE_SMARTCARD
-typedef gboolean (*SmartcardSourceFunc)(VEvent *event, gpointer user_data);
-static gboolean smartcard_monitor_dispatch(VEvent *event, gpointer user_data);
-#endif
-
-/* ------------------------------------------------------------------ */
-
-static void spice_smartcard_manager_init(SpiceSmartcardManager *smartcard_manager)
-{
-    SpiceSmartcardManagerPrivate *priv;
-
-    priv = SPICE_SMARTCARD_MANAGER_GET_PRIVATE(smartcard_manager);
-    smartcard_manager->priv = priv;
-}
-
-static void spice_smartcard_manager_dispose(GObject *gobject)
-{
-    /* Chain up to the parent class */
-    if (G_OBJECT_CLASS(spice_smartcard_manager_parent_class)->dispose)
-        G_OBJECT_CLASS(spice_smartcard_manager_parent_class)->dispose(gobject);
-}
-
-static void spice_smartcard_manager_finalize(GObject *gobject)
-{
-    SpiceSmartcardManager *manager = SPICE_SMARTCARD_MANAGER(gobject);
-    SpiceSmartcardManagerPrivate *priv = manager->priv;
-
-    if (priv->monitor_id != 0) {
-        g_source_remove(priv->monitor_id);
-        priv->monitor_id = 0;
-    }
-
-#ifdef USE_SMARTCARD
-    if (priv->software_reader != NULL) {
-        vreader_free(priv->software_reader);
-        priv->software_reader = NULL;
-    }
-#endif
-
-    /* Chain up to the parent class */
-    if (G_OBJECT_CLASS(spice_smartcard_manager_parent_class)->finalize)
-        G_OBJECT_CLASS(spice_smartcard_manager_parent_class)->finalize(gobject);
-}
-
-static void spice_smartcard_manager_class_init(SpiceSmartcardManagerClass *klass)
-{
-    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
-
-    /**
-     * SpiceSmartcardManager::reader-added:
-     * @manager: the #SpiceSmartcardManager that emitted the signal
-     * @vreader: #VReader boxed object corresponding to the added reader
-     *
-     * The #SpiceSmartcardManager::reader-added signal is emitted whenever
-     * a new smartcard reader (software or hardware) has been plugged in.
-     **/
-    signals[SPICE_SMARTCARD_MANAGER_READER_ADDED] =
-        g_signal_new("reader-added",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_FIRST,
-                     G_STRUCT_OFFSET(SpiceSmartcardManagerClass, reader_added),
-                     NULL, NULL,
-                     g_cclosure_marshal_VOID__BOXED,
-                     G_TYPE_NONE,
-                     1,
-                     SPICE_TYPE_SMARTCARD_READER);
-
-    /**
-     * SpiceSmartcardManager::reader-removed:
-     * @manager: the #SpiceSmartcardManager that emitted the signal
-     * @vreader: #VReader boxed object corresponding to the removed reader
-     *
-     * The #SpiceSmartcardManager::reader-removed signal is emitted whenever
-     * a smartcard reader (software or hardware) has been removed.
-     **/
-    signals[SPICE_SMARTCARD_MANAGER_READER_REMOVED] =
-        g_signal_new("reader-removed",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_FIRST,
-                     G_STRUCT_OFFSET(SpiceSmartcardManagerClass, reader_removed),
-                     NULL, NULL,
-                     g_cclosure_marshal_VOID__BOXED,
-                     G_TYPE_NONE,
-                     1,
-                     SPICE_TYPE_SMARTCARD_READER);
-
-    /**
-     * SpiceSmartcardManager::card-inserted:
-     * @manager: the #SpiceSmartcardManager that emitted the signal
-     * @vreader: #VReader boxed object corresponding to the reader a new
-     * card was inserted in
-     *
-     * The #SpiceSmartcardManager::card-inserted signal is emitted whenever
-     * a smartcard is inserted in a reader
-     **/
-    signals[SPICE_SMARTCARD_MANAGER_CARD_INSERTED] =
-        g_signal_new("card-inserted",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_FIRST,
-                     G_STRUCT_OFFSET(SpiceSmartcardManagerClass, card_inserted),
-                     NULL, NULL,
-                     g_cclosure_marshal_VOID__BOXED,
-                     G_TYPE_NONE,
-                     1,
-                     SPICE_TYPE_SMARTCARD_READER);
-
-    /**
-     * SpiceSmartcardManager::card-removed:
-     * @manager: the #SpiceSmartcardManager that emitted the signal
-     * @vreader: #VReader boxed object corresponding to the reader a card
-     * was removed from
-     *
-     * The #SpiceSmartcardManager::card-removed signal is emitted whenever
-     * a smartcard was removed from a reader.
-     **/
-    signals[SPICE_SMARTCARD_MANAGER_CARD_REMOVED] =
-        g_signal_new("card-removed",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_FIRST,
-                     G_STRUCT_OFFSET(SpiceSmartcardManagerClass, card_removed),
-                     NULL, NULL,
-                     g_cclosure_marshal_VOID__BOXED,
-                     G_TYPE_NONE,
-                     1,
-                     SPICE_TYPE_SMARTCARD_READER);
-    gobject_class->dispose      = spice_smartcard_manager_dispose;
-    gobject_class->finalize     = spice_smartcard_manager_finalize;
-
-    g_type_class_add_private(klass, sizeof(SpiceSmartcardManagerPrivate));
-}
-
-/* ------------------------------------------------------------------ */
-/* private api                                                        */
-
-static SpiceSmartcardManager *spice_smartcard_manager_new(void)
-{
-    return g_object_new(SPICE_TYPE_SMARTCARD_MANAGER, NULL);
-}
-
-/* ------------------------------------------------------------------ */
-/* public api                                                         */
-
-/**
- * spice_smartcard_manager_get:
- *
- * #SpiceSmartcardManager is a singleton, use this function to get a pointer
- * to it. A new SpiceSmartcardManager instance will be created the first
- * time this function is called
- *
- * Returns: (transfer none): a weak reference to the #SpiceSmartcardManager
- */
-SpiceSmartcardManager *spice_smartcard_manager_get(void)
-{
-    static GOnce manager_singleton_once = G_ONCE_INIT;
-
-    return g_once(&manager_singleton_once,
-                  (GThreadFunc)spice_smartcard_manager_new,
-                  NULL);
-}
-
-#ifdef USE_SMARTCARD
-static gboolean smartcard_monitor_dispatch(VEvent *event, gpointer user_data)
-{
-    g_return_val_if_fail(event != NULL, TRUE);
-    SpiceSmartcardManager *manager = SPICE_SMARTCARD_MANAGER(user_data);
-
-    switch (event->type) {
-        case VEVENT_READER_INSERT:
-            if (spice_smartcard_reader_is_software((SpiceSmartcardReader*)event->reader)) {
-                g_warn_if_fail(manager->priv->software_reader == NULL);
-                manager->priv->software_reader = vreader_reference(event->reader);
-            }
-            SPICE_DEBUG("smartcard: reader-added");
-            g_signal_emit(G_OBJECT(user_data),
-                          signals[SPICE_SMARTCARD_MANAGER_READER_ADDED],
-                          0, event->reader);
-            break;
-
-        case VEVENT_READER_REMOVE:
-            if (spice_smartcard_reader_is_software((SpiceSmartcardReader*)event->reader)) {
-                g_warn_if_fail(manager->priv->software_reader != NULL);
-                vreader_free(manager->priv->software_reader);
-                manager->priv->software_reader = NULL;
-            }
-            SPICE_DEBUG("smartcard: reader-removed");
-            g_signal_emit(G_OBJECT(user_data),
-                          signals[SPICE_SMARTCARD_MANAGER_READER_REMOVED],
-                          0, event->reader);
-            break;
-
-        case VEVENT_CARD_INSERT:
-            SPICE_DEBUG("smartcard: card-inserted");
-            g_signal_emit(G_OBJECT(user_data),
-                          signals[SPICE_SMARTCARD_MANAGER_CARD_INSERTED],
-                          0, event->reader);
-            break;
-        case VEVENT_CARD_REMOVE:
-            SPICE_DEBUG("smartcard: card-removed");
-            g_signal_emit(G_OBJECT(user_data),
-                          signals[SPICE_SMARTCARD_MANAGER_CARD_REMOVED],
-                          0, event->reader);
-            break;
-        case VEVENT_LAST:
-            break;
-    }
-
-    return TRUE;
-}
-
-/* ------------------------------------------------------------------ */
-/* smartcard monitoring GSource                                       */
-struct _SmartcardSource {
-    GSource parent_source;
-    VEvent *pending_event;
-};
-typedef struct _SmartcardSource SmartcardSource;
-
-static gboolean smartcard_source_prepare(GSource *source, gint *timeout)
-{
-    SmartcardSource *smartcard_source = (SmartcardSource *)source;
-
-    if (smartcard_source->pending_event == NULL)
-        smartcard_source->pending_event = vevent_get_next_vevent();
-
-    if (timeout != NULL)
-        *timeout = -1;
-
-    return (smartcard_source->pending_event != NULL);
-}
-
-static gboolean smartcard_source_check(GSource *source)
-{
-    return smartcard_source_prepare(source, NULL);
-}
-
-static gboolean smartcard_source_dispatch(GSource *source,
-                                          GSourceFunc callback,
-                                          gpointer user_data)
-{
-    SmartcardSource *smartcard_source = (SmartcardSource *)source;
-    SmartcardSourceFunc smartcard_callback = (SmartcardSourceFunc)callback;
-
-    g_return_val_if_fail(smartcard_source->pending_event != NULL, FALSE);
-
-    if (callback) {
-        gboolean event_consumed;
-        event_consumed = smartcard_callback(smartcard_source->pending_event,
-                                            user_data);
-        if (event_consumed) {
-            vevent_delete(smartcard_source->pending_event);
-            smartcard_source->pending_event = NULL;
-        }
-    }
-
-    return TRUE;
-}
-
-static void smartcard_source_finalize(GSource *source)
-{
-    SmartcardSource *smartcard_source = (SmartcardSource *)source;
-
-    if (smartcard_source->pending_event) {
-        vevent_delete(smartcard_source->pending_event);
-        smartcard_source->pending_event = NULL;
-    }
-}
-
-static GSource *smartcard_monitor_source_new(void)
-{
-    static GSourceFuncs source_funcs = {
-        .prepare = smartcard_source_prepare,
-        .check = smartcard_source_check,
-        .dispatch = smartcard_source_dispatch,
-        .finalize = smartcard_source_finalize
-    };
-    GSource *source;
-
-    source = g_source_new(&source_funcs, sizeof(SmartcardSource));
-    g_source_set_name(source, "Smartcard event source");
-    return source;
-}
-
-static guint smartcard_monitor_add(SmartcardSourceFunc callback,
-                                   gpointer user_data)
-{
-    GSource *source;
-    guint id;
-
-    source = smartcard_monitor_source_new();
-    g_source_set_callback(source, (GSourceFunc)callback, user_data, NULL);
-    id = g_source_attach(source, NULL);
-    g_source_unref(source);
-
-    return id;
-}
-
-static void
-spice_smartcard_manager_update_monitor(void)
-{
-    SpiceSmartcardManager *self = spice_smartcard_manager_get();
-    SpiceSmartcardManagerPrivate *priv = self->priv;
-
-    if (priv->monitor_id != 0)
-        return;
-
-    priv->monitor_id = smartcard_monitor_add(smartcard_monitor_dispatch, self);
-}
-
-#define SPICE_SOFTWARE_READER_NAME "Spice Software Smartcard"
-
-typedef struct {
-    SpiceSession *session;
-    GCancellable *cancellable;
-    GError *err;
-} SmartcardManagerInitArgs;
-
-static gboolean smartcard_manager_init(SmartcardManagerInitArgs *args)
-{
-    gchar *emul_args = NULL;
-    VCardEmulOptions *options = NULL;
-    VCardEmulError emul_init_status;
-    gchar *dbname = NULL;
-    GStrv certificates = NULL;
-    gboolean retval = FALSE;
-
-    SPICE_DEBUG("smartcard_manager_init");
-    g_return_val_if_fail(SPICE_IS_SESSION(args->session), FALSE);
-    g_object_get(G_OBJECT(args->session),
-                 "smartcard-db", &dbname,
-                 "smartcard-certificates", &certificates,
-                 NULL);
-
-    if ((certificates == NULL) || (g_strv_length(certificates) != 3))
-        goto init;
-
-    if (dbname) {
-        emul_args = g_strdup_printf("db=\"%s\" use_hw=no "
-                                    "soft=(,%s,CAC,,%s,%s,%s)",
-                                    dbname, SPICE_SOFTWARE_READER_NAME,
-                                    certificates[0], certificates[1],
-                                    certificates[2]);
-    } else {
-        emul_args = g_strdup_printf("use_hw=no soft=(,%s,CAC,,%s,%s,%s)",
-                                    SPICE_SOFTWARE_READER_NAME,
-                                    certificates[0], certificates[1],
-                                    certificates[2]);
-    }
-
-    options = vcard_emul_options(emul_args);
-    if (options == NULL) {
-        args->err = g_error_new(SPICE_CLIENT_ERROR,
-                                SPICE_CLIENT_ERROR_FAILED,
-                                "vcard_emul_options() failed!");
-        goto end;
-    }
-
-    if (g_cancellable_set_error_if_cancelled(args->cancellable, &args->err))
-        goto end;
-
-init:
-    SPICE_DEBUG("vcard_emul_init");
-    emul_init_status = vcard_emul_init(options);
-    if ((emul_init_status != VCARD_EMUL_OK)
-            && (emul_init_status != VCARD_EMUL_INIT_ALREADY_INITED)) {
-        args->err = g_error_new(SPICE_CLIENT_ERROR,
-                                SPICE_CLIENT_ERROR_FAILED,
-                                "Failed to initialize smartcard");
-        goto end;
-    }
-
-    retval = TRUE;
-
-end:
-    SPICE_DEBUG("smartcard_manager_init end: %d", retval);
-    g_free(emul_args);
-    g_free(dbname);
-    g_strfreev(certificates);
-    return retval;
-}
-
-static void smartcard_manager_init_helper(GSimpleAsyncResult *res,
-                                          GObject *object,
-                                          GCancellable *cancellable)
-{
-    static GOnce smartcard_manager_once = G_ONCE_INIT;
-    SmartcardManagerInitArgs args;
-
-    args.session = SPICE_SESSION(object);
-    args.cancellable = cancellable;
-    args.err = NULL;
-
-
-    g_once(&smartcard_manager_once,
-           (GThreadFunc)smartcard_manager_init,
-           &args);
-    if (args.err != NULL) {
-        g_simple_async_result_set_from_error(res, args.err);
-        g_error_free(args.err);
-    }
-}
-
-
-G_GNUC_INTERNAL
-void spice_smartcard_manager_init_async(SpiceSession *session,
-                                        GCancellable *cancellable,
-                                        GAsyncReadyCallback callback,
-                                        gpointer opaque)
-{
-    GSimpleAsyncResult *res;
-
-    res = g_simple_async_result_new(G_OBJECT(session),
-                                    callback,
-                                    opaque,
-                                    spice_smartcard_manager_init);
-    g_simple_async_result_run_in_thread(res,
-                                        smartcard_manager_init_helper,
-                                        G_PRIORITY_DEFAULT,
-                                        cancellable);
-    g_object_unref(res);
-}
-
-G_GNUC_INTERNAL
-gboolean spice_smartcard_manager_init_finish(SpiceSession *session,
-                                             GAsyncResult *result,
-                                             GError **err)
-{
-    GSimpleAsyncResult *simple;
-
-    g_return_val_if_fail(SPICE_IS_SESSION(session), FALSE);
-    g_return_val_if_fail(G_IS_SIMPLE_ASYNC_RESULT(result), FALSE);
-
-    SPICE_DEBUG("smartcard_manager_finish");
-
-    simple = G_SIMPLE_ASYNC_RESULT(result);
-    g_return_val_if_fail(g_simple_async_result_get_source_tag(simple) == spice_smartcard_manager_init, FALSE);
-    if (g_simple_async_result_propagate_error(simple, err))
-        return FALSE;
-
-    spice_smartcard_manager_update_monitor();
-
-    return TRUE;
-}
-
-/**
- * spice_smartcard_reader_is_software:
- * @reader: a #SpiceSmartcardReader
- *
- * Tests if @reader is a software (emulated) smartcard reader.
- *
- * Returns: TRUE if @reader is a software (emulated) smartcard reader,
- * FALSE otherwise
- */
-gboolean spice_smartcard_reader_is_software(SpiceSmartcardReader *reader)
-{
-    g_return_val_if_fail(reader != NULL, FALSE);
-    return (strcmp(vreader_get_name((VReader*)reader), SPICE_SOFTWARE_READER_NAME) == 0);
-}
-
-/**
- * spice_smartcard_reader_insert_card:
- * @reader: a #SpiceSmartcardReader
- *
- * Simulates insertion of a smartcard in the software smartcard reader
- * @reader. If @reader is not a software smartcard reader, FALSE will be
- * returned.
- *
- * Returns: TRUE if insertion of a card was successfully simulated, FALSE
- * otherwise
- */
-gboolean spice_smartcard_reader_insert_card(SpiceSmartcardReader *reader)
-{
-    VCardEmulError status;
-
-    g_return_val_if_fail(spice_smartcard_reader_is_software(reader), FALSE);
-
-    status = vcard_emul_force_card_insert((VReader *)reader);
-
-    return (status == VCARD_EMUL_OK);
-}
-
-/**
- * spice_smartcard_reader_remove_card:
- * @reader: a #SpiceSmartcardReader
- *
- * Simulates removal of a smartcard from the software smartcard reader
- * @reader. If @reader is not a software smartcard reader, FALSE will be
- * returned.
- *
- * Returns: TRUE if removal of a card was successfully simulated, FALSE
- * otherwise
- */
-gboolean spice_smartcard_reader_remove_card(SpiceSmartcardReader *reader)
-{
-    VCardEmulError status;
-
-    g_return_val_if_fail(spice_smartcard_reader_is_software(reader), FALSE);
-
-    status = vcard_emul_force_card_remove((VReader *)reader);
-
-    return (status == VCARD_EMUL_OK);
-}
-
-/**
- * spice_smartcard_manager_get_readers:
- *
- * manager: a #SpiceSmartcardManager
- *
- * Gets the list of smartcard readers that are currently available, they
- * can be either software (emulated) readers, or hardware ones.
- *
- * Returns: (element-type SpiceSmartcardReader) (transfer full): a newly
- * allocated list of SpiceSmartcardReader instances, or NULL if none were
- * found. When no longer needed, the list must be freed after unreferencing
- * its elements with g_boxed_free()
- *
- * Since: 0.20
- */
-GList *spice_smartcard_manager_get_readers(SpiceSmartcardManager *manager)
-{
-
-    GList *readers = NULL;
-    VReaderList *vreader_list;
-    VReaderListEntry *entry;
-
-    vreader_list = vreader_get_reader_list();
-
-    if (vreader_list == NULL)
-        return NULL;
-
-    for (entry = vreader_list_get_first(vreader_list);
-         entry != NULL;
-         entry = vreader_list_get_next(entry)) {
-        VReader *reader;
-
-        reader = vreader_list_get_reader(entry);
-        g_warn_if_fail(reader != NULL);
-        readers = g_list_prepend(readers, vreader_reference(reader));
-    }
-    vreader_list_delete(vreader_list);
-
-    return g_list_reverse(readers);
-}
-
-/**
- * spice_smartcard_manager_insert_card:
- * @manager: a #SpiceSmartcardManager
- *
- * Simulates the insertion of a smartcard in the guest. Valid certificates
- * must have been set in #SpiceSession:smartcard-certificates for software
- * smartcard support to work. At the moment, only one software smartcard
- * reader is supported, that's why there is no parameter to indicate which
- * reader to insert the card in.
- *
- * Returns: TRUE if smartcard insertion was successfully simulated, FALSE
- * if this failed, or if software smartcard support isn't enabled.
- *
- * Since: 0.20
- */
-gboolean spice_smartcard_manager_insert_card(SpiceSmartcardManager *manager)
-{
-    SpiceSmartcardReader *reader;
-
-    g_return_val_if_fail (manager->priv->software_reader != NULL, FALSE);
-
-    reader = (SpiceSmartcardReader *)manager->priv->software_reader;
-
-    return spice_smartcard_reader_insert_card(reader);
-}
-
-/**
- * spice_smartcard_manager_remove_card:
- * @manager: a #SpiceSmartcardManager
- *
- * Simulates the removal of a smartcard in the guest. At the moment, only
- * one software smartcard reader is supported, that's why there is no
- * parameter to indicate which reader to insert the card in.
- *
- * Returns: TRUE if smartcard removal was successfully simulated, FALSE
- * if this failed, or if software smartcard support isn't enabled.
- *
- * Since: 0.20
- */
-gboolean spice_smartcard_manager_remove_card(SpiceSmartcardManager *manager)
-{
-    SpiceSmartcardReader *reader;
-
-    g_return_val_if_fail (manager->priv->software_reader != NULL, FALSE);
-
-    reader = (SpiceSmartcardReader *)manager->priv->software_reader;
-
-    return spice_smartcard_reader_remove_card(reader);
-}
-#else
-gboolean spice_smartcard_reader_is_software(SpiceSmartcardReader *reader)
-{
-    return TRUE;
-}
-
-G_GNUC_INTERNAL
-void spice_smartcard_manager_init_async(SpiceSession *session,
-                                        GCancellable *cancellable,
-                                        GAsyncReadyCallback callback,
-                                        gpointer opaque)
-{
-    SPICE_DEBUG("using fake smartcard backend");
-}
-
-G_GNUC_INTERNAL
-gboolean spice_smartcard_manager_init_finish(SpiceSession *session,
-                                             GAsyncResult *result,
-                                             GError **err)
-{
-    g_return_val_if_fail(SPICE_IS_SESSION(session), FALSE);
-
-    return TRUE;
-}
-
-gboolean spice_smartcard_manager_insert_card(SpiceSmartcardManager *manager)
-{
-    return FALSE;
-}
-
-gboolean spice_smartcard_manager_remove_card(SpiceSmartcardManager *manager)
-{
-    return FALSE;
-}
-
-gboolean spice_smartcard_reader_insert_card(SpiceSmartcardReader *reader)
-{
-    return FALSE;
-}
-
-gboolean spice_smartcard_reader_remove_card(SpiceSmartcardReader *reader)
-{
-    return FALSE;
-}
-
-GList *spice_smartcard_manager_get_readers(SpiceSmartcardManager *manager)
-{
-    return NULL;
-}
-
-#endif /* USE_SMARTCARD */
diff --git a/gtk/smartcard-manager.h b/gtk/smartcard-manager.h
deleted file mode 100644
index 4811083..0000000
--- a/gtk/smartcard-manager.h
+++ /dev/null
@@ -1,80 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 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/>.
-*/
-#ifndef __SPICE_SMARTCARD_MANAGER_H__
-#define __SPICE_SMARTCARD_MANAGER_H__
-
-G_BEGIN_DECLS
-
-#include "spice-types.h"
-#include "spice-util.h"
-
-#define SPICE_TYPE_SMARTCARD_MANAGER            (spice_smartcard_manager_get_type ())
-#define SPICE_SMARTCARD_MANAGER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), SPICE_TYPE_SMARTCARD_MANAGER, SpiceSmartcardManager))
-#define SPICE_SMARTCARD_MANAGER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SPICE_TYPE_SMARTCARD_MANAGER, SpiceSmartcardManagerClass))
-#define SPICE_IS_SMARTCARD_MANAGER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SPICE_TYPE_SMARTCARD_MANAGER))
-#define SPICE_IS_SMARTCARD_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SPICE_TYPE_SMARTCARD_MANAGER))
-#define SPICE_SMARTCARD_MANAGER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SPICE_TYPE_SMARTCARD_MANAGER, SpiceSmartcardManagerClass))
-
-#define SPICE_TYPE_SMARTCARD_READER (spice_smartcard_reader_get_type())
-
-typedef struct _SpiceSmartcardManager SpiceSmartcardManager;
-typedef struct _SpiceSmartcardManagerClass SpiceSmartcardManagerClass;
-typedef struct _SpiceSmartcardManagerPrivate SpiceSmartcardManagerPrivate;
-typedef struct _SpiceSmartcardReader SpiceSmartcardReader;
-
-struct _SpiceSmartcardManager
-{
-    GObject parent;
-
-    /*< private >*/
-    SpiceSmartcardManagerPrivate *priv;
-    /* Do not add fields to this struct */
-};
-
-struct _SpiceSmartcardManagerClass
-{
-    GObjectClass parent_class;
-    /*< public >*/
-    /* signals */
-    void (*reader_added)(SpiceSmartcardManager *manager, SpiceSmartcardReader *reader);
-    void (*reader_removed)(SpiceSmartcardManager *manager, SpiceSmartcardReader *reader);
-    void (*card_inserted)(SpiceSmartcardManager *manager, SpiceSmartcardReader *reader);
-    void (*card_removed)(SpiceSmartcardManager *manager, SpiceSmartcardReader *reader );
-
-    /*< private >*/
-    /*
-     * If adding fields to this struct, remove corresponding
-     * amount of padding to avoid changing overall struct size
-     */
-    gchar _spice_reserved[SPICE_RESERVED_PADDING];
-};
-
-GType spice_smartcard_manager_get_type(void);
-GType spice_smartcard_reader_get_type(void);
-
-SpiceSmartcardManager *spice_smartcard_manager_get(void);
-gboolean spice_smartcard_manager_insert_card(SpiceSmartcardManager *manager);
-gboolean spice_smartcard_manager_remove_card(SpiceSmartcardManager *manager);
-gboolean spice_smartcard_reader_is_software(SpiceSmartcardReader *reader);
-gboolean spice_smartcard_reader_insert_card(SpiceSmartcardReader *reader);
-gboolean spice_smartcard_reader_remove_card(SpiceSmartcardReader *reader);
-GList *spice_smartcard_manager_get_readers(SpiceSmartcardManager *manager);
-
-G_END_DECLS
-
-#endif /* __SPICE_SMARTCARD_MANAGER_H__ */
diff --git a/gtk/spice-audio-priv.h b/gtk/spice-audio-priv.h
deleted file mode 100644
index f108059..0000000
--- a/gtk/spice-audio-priv.h
+++ /dev/null
@@ -1,42 +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_AUDIO_PRIVATE_H__
-#define __SPICE_AUDIO_PRIVATE_H__
-
-#include <glib.h>
-#include <gio/gio.h>
-#include "spice-session.h"
-
-G_BEGIN_DECLS
-
-struct _SpiceAudioPrivate {
-    SpiceSession            *session;
-    GMainContext            *main_context;
-};
-
-void spice_audio_get_playback_volume_info_async(SpiceAudio *audio, GCancellable *cancellable,
-        SpiceMainChannel *main_channel, GAsyncReadyCallback callback, gpointer user_data);
-gboolean spice_audio_get_playback_volume_info_finish(SpiceAudio *audio, GAsyncResult *res,
-        gboolean *mute, guint8 *nchannels, guint16 **volume, GError **error);
-void spice_audio_get_record_volume_info_async(SpiceAudio *audio, GCancellable *cancellable,
-        SpiceMainChannel *main_channel, GAsyncReadyCallback callback, gpointer user_data);
-gboolean spice_audio_get_record_volume_info_finish(SpiceAudio *audio, GAsyncResult *res,
-        gboolean *mute, guint8 *nchannels, guint16 **volume, GError **error);
-G_END_DECLS
-
-#endif /* __SPICE_AUDIO_PRIVATE_H__ */
diff --git a/gtk/spice-audio.c b/gtk/spice-audio.c
deleted file mode 100644
index ce191e1..0000000
--- a/gtk/spice-audio.c
+++ /dev/null
@@ -1,274 +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/>.
-*/
-/*
- * simple audio init dispatcher
- */
-
-/**
- * SECTION:spice-audio
- * @short_description: a helper to play and to record audio channels
- * @title: Spice Audio
- * @section_id:
- * @see_also: #SpiceRecordChannel, and #SpicePlaybackChannel
- * @stability: Stable
- * @include: spice-audio.h
- *
- * A class that handles the playback and record channels for your
- * application, and connect them to the default sound system.
- */
-
-#include "config.h"
-
-#include "spice-client.h"
-#include "spice-common.h"
-
-#include "spice-audio.h"
-#include "spice-session-priv.h"
-#include "spice-channel-priv.h"
-#include "spice-audio-priv.h"
-
-#ifdef WITH_PULSE
-#include "spice-pulse.h"
-#endif
-#if defined(WITH_GSTAUDIO)
-#include "spice-gstaudio.h"
-#endif
-
-#include "glib-compat.h"
-
-#define SPICE_AUDIO_GET_PRIVATE(obj)                                  \
-    (G_TYPE_INSTANCE_GET_PRIVATE ((obj), SPICE_TYPE_AUDIO, SpiceAudioPrivate))
-
-G_DEFINE_ABSTRACT_TYPE(SpiceAudio, spice_audio, G_TYPE_OBJECT)
-
-enum {
-    PROP_0,
-    PROP_SESSION,
-    PROP_MAIN_CONTEXT,
-};
-
-static void spice_audio_finalize(GObject *gobject)
-{
-    SpiceAudio *self = SPICE_AUDIO(gobject);
-    SpiceAudioPrivate *priv = self->priv;
-
-    if (priv->main_context) {
-        g_main_context_unref(priv->main_context);
-        priv->main_context = NULL;
-    }
-
-    if (G_OBJECT_CLASS(spice_audio_parent_class)->finalize)
-        G_OBJECT_CLASS(spice_audio_parent_class)->finalize(gobject);
-}
-
-static void spice_audio_get_property(GObject *gobject,
-                                     guint prop_id,
-                                     GValue *value,
-                                     GParamSpec *pspec)
-{
-    SpiceAudio *self = SPICE_AUDIO(gobject);
-    SpiceAudioPrivate *priv = self->priv;
-
-    switch (prop_id) {
-    case PROP_SESSION:
-        g_value_set_object(value, priv->session);
-        break;
-    case PROP_MAIN_CONTEXT:
-        g_value_set_boxed(value, priv->main_context);
-        break;
-    default:
-        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
-        break;
-    }
-}
-
-static void spice_audio_set_property(GObject *gobject,
-                                     guint prop_id,
-                                     const GValue *value,
-                                     GParamSpec *pspec)
-{
-    SpiceAudio *self = SPICE_AUDIO(gobject);
-    SpiceAudioPrivate *priv = self->priv;
-
-    switch (prop_id) {
-    case PROP_SESSION:
-        priv->session = g_value_get_object(value);
-        break;
-    case PROP_MAIN_CONTEXT:
-        priv->main_context = g_value_dup_boxed(value);
-        break;
-    default:
-        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
-        break;
-    }
-}
-
-static void spice_audio_class_init(SpiceAudioClass *klass)
-{
-    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
-    GParamSpec *pspec;
-
-    gobject_class->finalize     = spice_audio_finalize;
-    gobject_class->get_property = spice_audio_get_property;
-    gobject_class->set_property = spice_audio_set_property;
-
-    /**
-     * SpiceAudio:session:
-     *
-     * #SpiceSession this #SpiceAudio is associated with
-     *
-     **/
-    pspec = g_param_spec_object("session", "Session", "SpiceSession",
-                                SPICE_TYPE_SESSION,
-                                G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
-    g_object_class_install_property(gobject_class, PROP_SESSION, pspec);
-
-    /**
-     * SpiceAudio:main-context:
-     */
-    pspec = g_param_spec_boxed("main-context", "Main Context",
-                               "GMainContext to use for the event source",
-                               G_TYPE_MAIN_CONTEXT,
-                               G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
-    g_object_class_install_property(gobject_class, PROP_MAIN_CONTEXT, pspec);
-
-    g_type_class_add_private(klass, sizeof(SpiceAudioPrivate));
-}
-
-static void spice_audio_init(SpiceAudio *self)
-{
-    self->priv = SPICE_AUDIO_GET_PRIVATE(self);
-}
-
-static void connect_channel(SpiceAudio *self, SpiceChannel *channel)
-{
-    if (channel->priv->state != SPICE_CHANNEL_STATE_UNCONNECTED)
-        return;
-
-    if (SPICE_AUDIO_GET_CLASS(self)->connect_channel(self, channel))
-        spice_channel_connect(channel);
-}
-
-static void update_audio_channels(SpiceAudio *self, SpiceSession *session)
-{
-    GList *list, *tmp;
-
-    if (!spice_session_get_audio_enabled(session)) {
-        g_debug("FIXME: disconnect audio channels");
-        return;
-    }
-
-    list = spice_session_get_channels(session);
-    for (tmp = g_list_first(list); tmp != NULL; tmp = g_list_next(tmp)) {
-        connect_channel(self, tmp->data);
-    }
-    g_list_free(list);
-}
-
-static void channel_new(SpiceSession *session, SpiceChannel *channel, SpiceAudio *self)
-{
-    connect_channel(self, channel);
-}
-
-static void session_enable_audio(GObject *gobject, GParamSpec *pspec,
-                                 gpointer user_data)
-{
-    update_audio_channels(SPICE_AUDIO(user_data), SPICE_SESSION(gobject));
-}
-
-void spice_audio_get_playback_volume_info_async(SpiceAudio *audio,
-                                                GCancellable *cancellable,
-                                                SpiceMainChannel *main_channel,
-                                                GAsyncReadyCallback callback,
-                                                gpointer user_data)
-{
-    SPICE_AUDIO_GET_CLASS(audio)->get_playback_volume_info_async(audio,
-            cancellable, main_channel, callback, user_data);
-}
-
-gboolean spice_audio_get_playback_volume_info_finish(SpiceAudio *audio,
-                                                     GAsyncResult *res,
-                                                     gboolean *mute,
-                                                     guint8 *nchannels,
-                                                     guint16 **volume,
-                                                     GError **error)
-{
-    return SPICE_AUDIO_GET_CLASS(audio)->get_playback_volume_info_finish(audio,
-            res, mute, nchannels, volume, error);
-}
-
-void spice_audio_get_record_volume_info_async(SpiceAudio *audio,
-                                              GCancellable *cancellable,
-                                              SpiceMainChannel *main_channel,
-                                              GAsyncReadyCallback callback,
-                                              gpointer user_data)
-{
-    SPICE_AUDIO_GET_CLASS(audio)->get_record_volume_info_async(audio,
-            cancellable, main_channel, callback, user_data);
-}
-
-gboolean spice_audio_get_record_volume_info_finish(SpiceAudio *audio,
-                                                   GAsyncResult *res,
-                                                   gboolean *mute,
-                                                   guint8 *nchannels,
-                                                   guint16 **volume,
-                                                   GError **error)
-{
-    return SPICE_AUDIO_GET_CLASS(audio)->get_record_volume_info_finish(audio,
-            res, mute, nchannels, volume, error);
-}
-
-/**
- * spice_audio_new:
- * @session: the #SpiceSession to connect to
- * @context: (allow-none): a #GMainContext to attach to (or %NULL for
- * default).
- * @name: (allow-none): a name for the audio channels (or %NULL for
- * application name).
- *
- * Once instantiated, #SpiceAudio will handle the playback and record
- * channels to stream to your local audio system.
- *
- * Returns: a new #SpiceAudio instance or %NULL if no backend or failed.
- * Deprecated: 0.8: Use spice_audio_get() instead
- **/
-SpiceAudio *spice_audio_new(SpiceSession *session, GMainContext *context,
-                            const char *name)
-{
-    SpiceAudio *self = NULL;
-
-    if (context == NULL)
-        context = g_main_context_default();
-    if (name == NULL)
-        name = g_get_application_name();
-
-#ifdef WITH_PULSE
-    self = SPICE_AUDIO(spice_pulse_new(session, context, name));
-#endif
-#if defined(WITH_GSTAUDIO)
-    self = SPICE_AUDIO(spice_gstaudio_new(session, context, name));
-#endif
-    if (!self)
-        return NULL;
-
-    spice_g_signal_connect_object(session, "notify::enable-audio", G_CALLBACK(session_enable_audio), self, 0);
-    spice_g_signal_connect_object(session, "channel-new", G_CALLBACK(channel_new), self, G_CONNECT_AFTER);
-    update_audio_channels(self, session);
-
-    return self;
-}
diff --git a/gtk/spice-audio.h b/gtk/spice-audio.h
deleted file mode 100644
index 0bf625b..0000000
--- a/gtk/spice-audio.h
+++ /dev/null
@@ -1,109 +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_CLIENT_AUDIO_H__
-#define __SPICE_CLIENT_AUDIO_H__
-
-#include <glib-object.h>
-#include <gio/gio.h>
-#include "spice-util.h"
-#include "spice-session.h"
-#include "channel-main.h"
-
-G_BEGIN_DECLS
-
-#define SPICE_TYPE_AUDIO spice_audio_get_type()
-
-#define SPICE_AUDIO(obj)					\
-    (G_TYPE_CHECK_INSTANCE_CAST ((obj), SPICE_TYPE_AUDIO, SpiceAudio))
-
-#define SPICE_AUDIO_CLASS(klass)				\
-    (G_TYPE_CHECK_CLASS_CAST ((klass), SPICE_TYPE_AUDIO, SpiceAudioClass))
-
-#define SPICE_IS_AUDIO(obj)                                     \
-    (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SPICE_TYPE_AUDIO))
-
-#define SPICE_IS_AUDIO_CLASS(klass)                             \
-    (G_TYPE_CHECK_CLASS_TYPE ((klass), SPICE_TYPE_AUDIO))
-
-#define SPICE_AUDIO_GET_CLASS(obj)				\
-    (G_TYPE_INSTANCE_GET_CLASS ((obj), SPICE_TYPE_AUDIO, SpiceAudioClass))
-
-typedef struct _SpiceAudio SpiceAudio;
-typedef struct _SpiceAudioClass SpiceAudioClass;
-typedef struct _SpiceAudioPrivate SpiceAudioPrivate;
-
-/**
- * SpiceAudio:
- *
- * The #SpiceAudio struct is opaque and should not be accessed directly.
- */
-struct _SpiceAudio {
-    GObject parent;
-
-    SpiceAudioPrivate *priv;
-};
-
-/**
- * SpiceAudioClass:
- * @parent_class: Parent class.
- *
- * Class structure for #SpiceAudio.
- */
-struct _SpiceAudioClass {
-    GObjectClass parent_class;
-
-    /*< private >*/
-    gboolean (*connect_channel)(SpiceAudio *audio, SpiceChannel *channel);
-    void (*get_playback_volume_info_async)(SpiceAudio *audio,
-                                           GCancellable *cancellable,
-                                           SpiceMainChannel *main_channel,
-                                           GAsyncReadyCallback callback,
-                                           gpointer user_data);
-    gboolean (*get_playback_volume_info_finish)(SpiceAudio *audio,
-                                                GAsyncResult *res,
-                                                gboolean *mute,
-                                                guint8 *nchannels,
-                                                guint16 **volume,
-                                                GError **error);
-    void (*get_record_volume_info_async)(SpiceAudio *audio,
-                                         GCancellable *cancellable,
-                                         SpiceMainChannel *main_channel,
-                                         GAsyncReadyCallback callback,
-                                         gpointer user_data);
-    gboolean (*get_record_volume_info_finish)(SpiceAudio *audio,
-                                              GAsyncResult *res,
-                                              gboolean *mute,
-                                              guint8 *nchannels,
-                                              guint16 **volume,
-                                              GError **error);
-
-    gchar _spice_reserved[SPICE_RESERVED_PADDING - 4 * sizeof(void *)];
-};
-
-GType spice_audio_get_type(void);
-
-SpiceAudio* spice_audio_get(SpiceSession *session, GMainContext *context);
-
-#ifndef SPICE_DISABLE_DEPRECATED
-SPICE_DEPRECATED_FOR(spice_audio_get)
-SpiceAudio* spice_audio_new(SpiceSession *session, GMainContext *context, const char *name);
-#endif
-
-G_END_DECLS
-
-#endif /* __SPICE_CLIENT_AUDIO_H__ */
diff --git a/gtk/spice-channel-cache.h b/gtk/spice-channel-cache.h
deleted file mode 100644
index 17775e6..0000000
--- a/gtk/spice-channel-cache.h
+++ /dev/null
@@ -1,106 +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_CHANNEL_CACHE_H_
-# define SPICE_CHANNEL_CACHE_H_
-
-#include <inttypes.h> /* For PRIx64 */
-#include "common/mem.h"
-#include "common/ring.h"
-
-G_BEGIN_DECLS
-
-typedef struct display_cache_item {
-    guint64                     id;
-    gboolean                    lossy;
-} display_cache_item;
-
-typedef GHashTable display_cache;
-
-static inline display_cache_item* cache_item_new(guint64 id, gboolean lossy)
-{
-    display_cache_item *self = g_slice_new(display_cache_item);
-    self->id = id;
-    self->lossy = lossy;
-    return self;
-}
-
-static inline void cache_item_free(display_cache_item *self)
-{
-    g_slice_free(display_cache_item, self);
-}
-
-static inline display_cache* cache_new(GDestroyNotify value_destroy)
-{
-    GHashTable* self;
-
-    self = g_hash_table_new_full(g_int64_hash, g_int64_equal,
-                                 (GDestroyNotify)cache_item_free,
-                                 value_destroy);
-
-    return self;
-}
-
-static inline gpointer cache_find(display_cache *cache, uint64_t id)
-{
-    return g_hash_table_lookup(cache, &id);
-}
-
-static inline gpointer cache_find_lossy(display_cache *cache, uint64_t id, gboolean *lossy)
-{
-    gpointer value;
-    display_cache_item *item;
-
-    if (!g_hash_table_lookup_extended(cache, &id, (gpointer*)&item, &value))
-        return NULL;
-
-    *lossy = item->lossy;
-
-    return value;
-}
-
-static inline void cache_add_lossy(display_cache *cache, uint64_t id,
-                                   gpointer value, gboolean lossy)
-{
-    display_cache_item *item = cache_item_new(id, lossy);
-
-    g_hash_table_replace(cache, item, value);
-}
-
-static inline void cache_add(display_cache *cache, uint64_t id, gpointer value)
-{
-    cache_add_lossy(cache, id, value, FALSE);
-}
-
-static inline gboolean cache_remove(display_cache *cache, uint64_t id)
-{
-    return g_hash_table_remove(cache, &id);
-}
-
-static inline void cache_clear(display_cache *cache)
-{
-    g_hash_table_remove_all(cache);
-}
-
-static inline void cache_unref(display_cache *cache)
-{
-    g_hash_table_unref(cache);
-}
-
-G_END_DECLS
-
-#endif // SPICE_CHANNEL_CACHE_H_
diff --git a/gtk/spice-channel-enums.h b/gtk/spice-channel-enums.h
deleted file mode 100644
index 02df762..0000000
--- a/gtk/spice-channel-enums.h
+++ /dev/null
@@ -1,7 +0,0 @@
-#ifndef SPICE_CHANNEL_ENUMS_H
-#define SPICE_CHANNEL_ENUMS_H
-
-#warning "deprecated: please include spice-glib-enums.h"
-#include "spice-glib-enums.h"
-
-#endif /* SPICE_CHANNEL_ENUMS_H */
diff --git a/gtk/spice-channel-priv.h b/gtk/spice-channel-priv.h
deleted file mode 100644
index d70cf86..0000000
--- a/gtk/spice-channel-priv.h
+++ /dev/null
@@ -1,203 +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_CLIENT_CHANNEL_PRIV_H__
-#define __SPICE_CLIENT_CHANNEL_PRIV_H__
-
-#include "config.h"
-
-#include <openssl/ssl.h>
-#include <gio/gio.h>
-
-#if HAVE_SASL
-#include <sasl/sasl.h>
-#endif
-
-#include "spice-channel.h"
-#include "spice-util-priv.h"
-#include "coroutine.h"
-#include "gio-coroutine.h"
-
-#include "common/client_marshallers.h"
-#include "common/client_demarshallers.h"
-#include "common/ssl_verify.h"
-
-G_BEGIN_DECLS
-
-#define MAX_SPICE_DATA_HEADER_SIZE sizeof(SpiceDataHeader)
-
-#define CHANNEL_DEBUG(channel, fmt, ...) \
-    SPICE_DEBUG("%s: " fmt, SPICE_CHANNEL(channel)->priv->name, ## __VA_ARGS__)
-
-struct _SpiceMsgOut {
-    int                   refcount;
-    SpiceChannel          *channel;
-    SpiceMessageMarshallers *marshallers;
-    SpiceMarshaller       *marshaller;
-    uint8_t               *header;
-    gboolean              ro_check;
-};
-
-struct _SpiceMsgIn {
-    int                   refcount;
-    SpiceChannel          *channel;
-    uint8_t               header[MAX_SPICE_DATA_HEADER_SIZE];
-    uint8_t               *data;
-    int                   dpos;
-    uint8_t               *parsed;
-    size_t                psize;
-    message_destructor_t  pfree;
-    SpiceMsgIn            *parent;
-};
-
-enum spice_channel_state {
-    SPICE_CHANNEL_STATE_UNCONNECTED = 0,
-    SPICE_CHANNEL_STATE_RECONNECTING,
-    SPICE_CHANNEL_STATE_CONNECTING,
-    SPICE_CHANNEL_STATE_READY,
-    SPICE_CHANNEL_STATE_SWITCHING,
-    SPICE_CHANNEL_STATE_MIGRATING,
-    SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE,
-};
-
-struct _SpiceChannelPrivate {
-    /* swapped on migration */
-    SSL_CTX                     *ctx;
-    SSL                         *ssl;
-    SpiceOpenSSLVerify          *sslverify;
-    GSocket                     *sock;
-    GSocketConnection           *conn;
-    GInputStream                *in;
-    GOutputStream               *out;
-
-#if HAVE_SASL
-    sasl_conn_t                 *sasl_conn;
-    const char                  *sasl_decoded;
-    unsigned int                sasl_decoded_length;
-    unsigned int                sasl_decoded_offset;
-#endif
-
-    gboolean                    use_mini_header;
-    uint64_t                    out_serial;
-    uint64_t                    in_serial;
-
-    /* not swapped */
-    SpiceSession                *session;
-    GCoroutine                  coroutine;
-    int                         fd;
-    gboolean                    has_error;
-    guint                       connect_delayed_id;
-
-    GQueue                      xmit_queue;
-    gboolean                    xmit_queue_blocked;
-    STATIC_MUTEX                xmit_queue_lock;
-    guint                       xmit_queue_wakeup_id;
-
-    char                        name[16];
-    enum spice_channel_state    state;
-    SpiceChannelEvent           event;
-
-    spice_parse_channel_func_t  parser;
-    SpiceMessageMarshallers     *marshallers;
-    guint                       channel_watch;
-    int                         tls;
-
-    int                         channel_id;
-    int                         channel_type;
-    SpiceLinkHeader             link_hdr;
-    SpiceLinkMess               link_msg;
-    SpiceLinkHeader             peer_hdr;
-    SpiceLinkReply*             peer_msg;
-    int                         peer_pos;
-
-    int                         message_ack_window;
-    int                         message_ack_count;
-
-    GArray                      *caps;
-    GArray                      *common_caps;
-    GArray                      *remote_caps;
-    GArray                      *remote_common_caps;
-
-    gsize                       total_read_bytes;
-    uint64_t                    last_message_serial;
-    GSList                      *flushing;
-
-    gboolean                    disable_channel_msg;
-    gboolean                    auth_needs_username_and_password;
-    GError                      *error;
-};
-
-SpiceMsgIn *spice_msg_in_new(SpiceChannel *channel);
-SpiceMsgIn *spice_msg_in_sub_new(SpiceChannel *channel, SpiceMsgIn *parent,
-                                   SpiceSubMessage *sub);
-void spice_msg_in_ref(SpiceMsgIn *in);
-void spice_msg_in_unref(SpiceMsgIn *in);
-int spice_msg_in_type(SpiceMsgIn *in);
-void *spice_msg_in_parsed(SpiceMsgIn *in);
-void *spice_msg_in_raw(SpiceMsgIn *in, int *len);
-void spice_msg_in_hexdump(SpiceMsgIn *in);
-
-SpiceMsgOut *spice_msg_out_new(SpiceChannel *channel, int type);
-void spice_msg_out_ref(SpiceMsgOut *out);
-void spice_msg_out_unref(SpiceMsgOut *out);
-void spice_msg_out_send(SpiceMsgOut *out);
-void spice_msg_out_send_internal(SpiceMsgOut *out);
-void spice_msg_out_hexdump(SpiceMsgOut *out, unsigned char *data, int len);
-
-uint16_t spice_header_get_msg_type(uint8_t *header, gboolean is_mini_header);
-uint32_t spice_header_get_msg_size(uint8_t *header, gboolean is_mini_header);
-
-void spice_channel_up(SpiceChannel *channel);
-void spice_channel_wakeup(SpiceChannel *channel, gboolean cancel);
-
-SpiceSession* spice_channel_get_session(SpiceChannel *channel);
-enum spice_channel_state spice_channel_get_state(SpiceChannel *channel);
-
-/* coroutine context */
-typedef void (*handler_msg_in)(SpiceChannel *channel, SpiceMsgIn *msg, gpointer data);
-void spice_channel_recv_msg(SpiceChannel *channel, handler_msg_in handler, gpointer data);
-
-/* channel-base.c */
-void spice_channel_set_handlers(SpiceChannelClass *klass,
-                                const spice_msg_handler* handlers, const int n);
-void spice_channel_handle_wait_for_channels(SpiceChannel *channel, SpiceMsgIn *in);
-
-gint spice_channel_get_channel_id(SpiceChannel *channel);
-gint spice_channel_get_channel_type(SpiceChannel *channel);
-void spice_channel_swap(SpiceChannel *channel, SpiceChannel *swap, gboolean swap_msgs);
-gboolean spice_channel_get_read_only(SpiceChannel *channel);
-void spice_channel_reset(SpiceChannel *channel, gboolean migrating);
-
-void spice_caps_set(GArray *caps, guint32 cap, const gchar *desc);
-#define spice_channel_set_common_capability(channel, cap)               \
-    spice_caps_set(SPICE_CHANNEL(channel)->priv->common_caps, cap, #cap)
-#define spice_channel_set_capability(channel, cap)                      \
-    spice_caps_set(SPICE_CHANNEL(channel)->priv->caps, cap, #cap)
-
-gchar *spice_channel_supported_string(void);
-
-void spice_vmc_write_async(SpiceChannel *self,
-                           const void *buffer, gsize count,
-                           GCancellable *cancellable,
-                           GAsyncReadyCallback callback,
-                           gpointer user_data);
-gssize spice_vmc_write_finish(SpiceChannel *self,
-                              GAsyncResult *result, GError **error);
-
-G_END_DECLS
-
-#endif /* __SPICE_CLIENT_CHANNEL_PRIV_H__ */
diff --git a/gtk/spice-channel.c b/gtk/spice-channel.c
deleted file mode 100644
index 4e7d8b7..0000000
--- a/gtk/spice-channel.c
+++ /dev/null
@@ -1,2960 +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 "glib-compat.h"
-
-#include "spice-channel-priv.h"
-#include "spice-session-priv.h"
-#include "spice-marshal.h"
-#include "bio-gio.h"
-
-#include <glib/gi18n.h>
-
-#include <openssl/rsa.h>
-#include <openssl/evp.h>
-#include <openssl/x509.h>
-#include <openssl/ssl.h>
-#include <openssl/err.h>
-#include <openssl/x509v3.h>
-#ifdef HAVE_SYS_SOCKET_H
-#include <sys/socket.h>
-#endif
-#ifdef HAVE_NETINET_IN_H
-#include <netinet/in.h>
-#include <netinet/tcp.h> // TCP_NODELAY
-#endif
-#ifdef HAVE_ARPA_INET_H
-#include <arpa/inet.h>
-#endif
-#include <ctype.h>
-
-#include "gio-coroutine.h"
-
-static void spice_channel_handle_msg(SpiceChannel *channel, SpiceMsgIn *msg);
-static void spice_channel_write_msg(SpiceChannel *channel, SpiceMsgOut *out);
-static void spice_channel_send_link(SpiceChannel *channel);
-static void channel_reset(SpiceChannel *channel, gboolean migrating);
-static void spice_channel_reset_capabilities(SpiceChannel *channel);
-static void spice_channel_send_migration_handshake(SpiceChannel *channel);
-static gboolean channel_connect(SpiceChannel *channel, gboolean tls);
-
-/**
- * SECTION:spice-channel
- * @short_description: the base channel class
- * @title: Spice Channel
- * @section_id:
- * @see_also: #SpiceSession, #SpiceMainChannel and other channels
- * @stability: Stable
- * @include: spice-channel.h
- *
- * #SpiceChannel is the base class for the different kind of Spice
- * channel connections, such as #SpiceMainChannel, or
- * #SpiceInputsChannel.
- */
-
-/* ------------------------------------------------------------------ */
-/* gobject glue                                                       */
-
-#define SPICE_CHANNEL_GET_PRIVATE(obj)                                  \
-    (G_TYPE_INSTANCE_GET_PRIVATE ((obj), SPICE_TYPE_CHANNEL, SpiceChannelPrivate))
-
-G_DEFINE_TYPE(SpiceChannel, spice_channel, G_TYPE_OBJECT);
-
-/* Properties */
-enum {
-    PROP_0,
-    PROP_SESSION,
-    PROP_CHANNEL_TYPE,
-    PROP_CHANNEL_ID,
-    PROP_TOTAL_READ_BYTES,
-};
-
-/* Signals */
-enum {
-    SPICE_CHANNEL_EVENT,
-    SPICE_CHANNEL_OPEN_FD,
-
-    SPICE_CHANNEL_LAST_SIGNAL,
-};
-
-static guint signals[SPICE_CHANNEL_LAST_SIGNAL];
-
-static void spice_channel_iterate_write(SpiceChannel *channel);
-static void spice_channel_iterate_read(SpiceChannel *channel);
-
-static void spice_channel_init(SpiceChannel *channel)
-{
-    SpiceChannelPrivate *c;
-
-    c = channel->priv = SPICE_CHANNEL_GET_PRIVATE(channel);
-
-    c->out_serial = 1;
-    c->in_serial = 1;
-    c->fd = -1;
-    c->auth_needs_username_and_password = FALSE;
-    strcpy(c->name, "?");
-    c->caps = g_array_new(FALSE, TRUE, sizeof(guint32));
-    c->common_caps = g_array_new(FALSE, TRUE, sizeof(guint32));
-    c->remote_caps = g_array_new(FALSE, TRUE, sizeof(guint32));
-    c->remote_common_caps = g_array_new(FALSE, TRUE, sizeof(guint32));
-    spice_channel_set_common_capability(channel, SPICE_COMMON_CAP_PROTOCOL_AUTH_SELECTION);
-    spice_channel_set_common_capability(channel, SPICE_COMMON_CAP_MINI_HEADER);
-#if HAVE_SASL
-    spice_channel_set_common_capability(channel, SPICE_COMMON_CAP_AUTH_SASL);
-#endif
-    g_queue_init(&c->xmit_queue);
-    STATIC_MUTEX_INIT(c->xmit_queue_lock);
-}
-
-static void spice_channel_constructed(GObject *gobject)
-{
-    SpiceChannel *channel = SPICE_CHANNEL(gobject);
-    SpiceChannelPrivate *c = channel->priv;
-    const char *desc = spice_channel_type_to_string(c->channel_type);
-
-    snprintf(c->name, sizeof(c->name), "%s-%d:%d",
-             desc, c->channel_type, c->channel_id);
-    CHANNEL_DEBUG(channel, "%s", __FUNCTION__);
-
-    const char *disabled  = g_getenv("SPICE_DISABLE_CHANNELS");
-    if (disabled && strstr(disabled, desc))
-        c->disable_channel_msg = TRUE;
-
-    spice_session_channel_new(c->session, channel);
-
-    /* Chain up to the parent class */
-    if (G_OBJECT_CLASS(spice_channel_parent_class)->constructed)
-        G_OBJECT_CLASS(spice_channel_parent_class)->constructed(gobject);
-}
-
-static void spice_channel_dispose(GObject *gobject)
-{
-    SpiceChannel *channel = SPICE_CHANNEL(gobject);
-    SpiceChannelPrivate *c = channel->priv;
-
-    CHANNEL_DEBUG(channel, "%s %p", __FUNCTION__, gobject);
-
-    spice_channel_disconnect(channel, SPICE_CHANNEL_CLOSED);
-
-    if (c->session) {
-         g_object_unref(c->session);
-         c->session = NULL;
-    }
-
-    g_clear_error(&c->error);
-
-    /* Chain up to the parent class */
-    if (G_OBJECT_CLASS(spice_channel_parent_class)->dispose)
-        G_OBJECT_CLASS(spice_channel_parent_class)->dispose(gobject);
-}
-
-static void spice_channel_finalize(GObject *gobject)
-{
-    SpiceChannel *channel = SPICE_CHANNEL(gobject);
-    SpiceChannelPrivate *c = channel->priv;
-
-    CHANNEL_DEBUG(channel, "%s %p", __FUNCTION__, gobject);
-
-    g_idle_remove_by_data(gobject);
-
-    STATIC_MUTEX_CLEAR(c->xmit_queue_lock);
-
-    if (c->caps)
-        g_array_free(c->caps, TRUE);
-
-    if (c->common_caps)
-        g_array_free(c->common_caps, TRUE);
-
-    if (c->remote_caps)
-        g_array_free(c->remote_caps, TRUE);
-
-    if (c->remote_common_caps)
-        g_array_free(c->remote_common_caps, TRUE);
-
-    /* Chain up to the parent class */
-    if (G_OBJECT_CLASS(spice_channel_parent_class)->finalize)
-        G_OBJECT_CLASS(spice_channel_parent_class)->finalize(gobject);
-}
-
-static void spice_channel_get_property(GObject    *gobject,
-                                       guint       prop_id,
-                                       GValue     *value,
-                                       GParamSpec *pspec)
-{
-    SpiceChannel *channel = SPICE_CHANNEL(gobject);
-    SpiceChannelPrivate *c = channel->priv;
-
-    switch (prop_id) {
-    case PROP_SESSION:
-        g_value_set_object(value, c->session);
-        break;
-    case PROP_CHANNEL_TYPE:
-        g_value_set_int(value, c->channel_type);
-        break;
-    case PROP_CHANNEL_ID:
-        g_value_set_int(value, c->channel_id);
-        break;
-    case PROP_TOTAL_READ_BYTES:
-        g_value_set_ulong(value, c->total_read_bytes);
-        break;
-    default:
-        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
-        break;
-    }
-}
-
-G_GNUC_INTERNAL
-gint spice_channel_get_channel_id(SpiceChannel *channel)
-{
-    SpiceChannelPrivate *c = channel->priv;
-
-    g_return_val_if_fail(c != NULL, 0);
-    return c->channel_id;
-}
-
-G_GNUC_INTERNAL
-gint spice_channel_get_channel_type(SpiceChannel *channel)
-{
-    SpiceChannelPrivate *c = channel->priv;
-
-    g_return_val_if_fail(c != NULL, 0);
-    return c->channel_type;
-}
-
-static void spice_channel_set_property(GObject      *gobject,
-                                       guint         prop_id,
-                                       const GValue *value,
-                                       GParamSpec   *pspec)
-{
-    SpiceChannel *channel = SPICE_CHANNEL(gobject);
-    SpiceChannelPrivate *c = channel->priv;
-
-    switch (prop_id) {
-    case PROP_SESSION:
-        c->session = g_value_dup_object(value);
-        break;
-    case PROP_CHANNEL_TYPE:
-        c->channel_type = g_value_get_int(value);
-        break;
-    case PROP_CHANNEL_ID:
-        c->channel_id = g_value_get_int(value);
-        break;
-    default:
-        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
-        break;
-    }
-}
-
-static void spice_channel_class_init(SpiceChannelClass *klass)
-{
-    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
-
-    klass->iterate_write = spice_channel_iterate_write;
-    klass->iterate_read  = spice_channel_iterate_read;
-    klass->channel_reset = channel_reset;
-
-    gobject_class->constructed  = spice_channel_constructed;
-    gobject_class->dispose      = spice_channel_dispose;
-    gobject_class->finalize     = spice_channel_finalize;
-    gobject_class->get_property = spice_channel_get_property;
-    gobject_class->set_property = spice_channel_set_property;
-    klass->handle_msg           = spice_channel_handle_msg;
-
-    g_object_class_install_property
-        (gobject_class, PROP_SESSION,
-         g_param_spec_object("spice-session",
-                             "Spice session",
-                             "",
-                             SPICE_TYPE_SESSION,
-                             G_PARAM_READWRITE |
-                             G_PARAM_CONSTRUCT_ONLY |
-                             G_PARAM_STATIC_STRINGS));
-
-    g_object_class_install_property
-        (gobject_class, PROP_CHANNEL_TYPE,
-         g_param_spec_int("channel-type",
-                          "Channel type",
-                          "",
-                          -1, INT_MAX, -1,
-                          G_PARAM_READWRITE |
-                          G_PARAM_CONSTRUCT_ONLY |
-                          G_PARAM_STATIC_STRINGS));
-
-    g_object_class_install_property
-        (gobject_class, PROP_CHANNEL_ID,
-         g_param_spec_int("channel-id",
-                          "Channel ID",
-                          "",
-                          -1, INT_MAX, -1,
-                          G_PARAM_READWRITE |
-                          G_PARAM_CONSTRUCT_ONLY |
-                          G_PARAM_STATIC_STRINGS));
-
-    g_object_class_install_property
-        (gobject_class, PROP_TOTAL_READ_BYTES,
-         g_param_spec_ulong("total-read-bytes",
-                            "Total read bytes",
-                            "",
-                            0, G_MAXULONG, 0,
-                            G_PARAM_READABLE |
-                            G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceChannel::channel-event:
-     * @channel: the channel that emitted the signal
-     * @event: a #SpiceChannelEvent
-     *
-     * The #SpiceChannel::channel-event signal is emitted when the
-     * state of the connection is changed. In case of errors,
-     * spice_channel_get_error() may provide additional informations
-     * on the source of the error.
-     **/
-    signals[SPICE_CHANNEL_EVENT] =
-        g_signal_new("channel-event",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_FIRST,
-                     G_STRUCT_OFFSET(SpiceChannelClass, channel_event),
-                     NULL, NULL,
-                     g_cclosure_marshal_VOID__ENUM,
-                     G_TYPE_NONE,
-                     1,
-                     SPICE_TYPE_CHANNEL_EVENT);
-
-    /**
-     * SpiceChannel::open-fd:
-     * @channel: the channel that emitted the signal
-     * @with_tls: wether TLS connection is requested
-     *
-     * The #SpiceChannel::open-fd signal is emitted when a new
-     * connection is requested. This signal is emitted when the
-     * connection is made with spice_session_open_fd().
-     **/
-    signals[SPICE_CHANNEL_OPEN_FD] =
-        g_signal_new("open-fd",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_FIRST,
-                     G_STRUCT_OFFSET(SpiceChannelClass, open_fd),
-                     NULL, NULL,
-                     g_cclosure_marshal_VOID__INT,
-                     G_TYPE_NONE,
-                     1,
-                     G_TYPE_INT);
-
-    g_type_class_add_private(klass, sizeof(SpiceChannelPrivate));
-
-    SSL_library_init();
-    SSL_load_error_strings();
-}
-
-/* ---------------------------------------------------------------- */
-/* private header api                                               */
-
-static inline void spice_header_set_msg_type(uint8_t *header, gboolean is_mini_header,
-                                             uint16_t type)
-{
-    if (is_mini_header) {
-        ((SpiceMiniDataHeader *)header)->type = type;
-    } else {
-        ((SpiceDataHeader *)header)->type = type;
-    }
-}
-
-static inline void spice_header_set_msg_size(uint8_t *header, gboolean is_mini_header,
-                                             uint32_t size)
-{
-    if (is_mini_header) {
-        ((SpiceMiniDataHeader *)header)->size = size;
-    } else {
-        ((SpiceDataHeader *)header)->size = size;
-    }
-}
-
-G_GNUC_INTERNAL
-uint16_t spice_header_get_msg_type(uint8_t *header, gboolean is_mini_header)
-{
-    if (is_mini_header) {
-        return ((SpiceMiniDataHeader *)header)->type;
-    } else {
-        return ((SpiceDataHeader *)header)->type;
-    }
-}
-
-G_GNUC_INTERNAL
-uint32_t spice_header_get_msg_size(uint8_t *header, gboolean is_mini_header)
-{
-    if (is_mini_header) {
-        return ((SpiceMiniDataHeader *)header)->size;
-    } else {
-        return ((SpiceDataHeader *)header)->size;
-    }
-}
-
-static inline int spice_header_get_header_size(gboolean is_mini_header)
-{
-    return is_mini_header ? sizeof(SpiceMiniDataHeader) : sizeof(SpiceDataHeader);
-}
-
-static inline void spice_header_set_msg_serial(uint8_t *header, gboolean is_mini_header,
-                                               uint64_t serial)
-{
-    if (!is_mini_header) {
-        ((SpiceDataHeader *)header)->serial = serial;
-    }
-}
-
-static inline void spice_header_reset_msg_sub_list(uint8_t *header, gboolean is_mini_header)
-{
-    if (!is_mini_header) {
-        ((SpiceDataHeader *)header)->sub_list = 0;
-    }
-}
-
-static inline uint64_t spice_header_get_in_msg_serial(SpiceMsgIn *in)
-{
-    SpiceChannelPrivate *c = in->channel->priv;
-    uint8_t *header = in->header;
-
-    if (c->use_mini_header) {
-        return c->in_serial;
-    } else {
-        return ((SpiceDataHeader *)header)->serial;
-    }
-}
-
-static inline uint64_t spice_header_get_out_msg_serial(SpiceMsgOut *out)
-{
-    SpiceChannelPrivate *c = out->channel->priv;
-
-    if (c->use_mini_header) {
-        return c->out_serial;
-    } else {
-        return ((SpiceDataHeader *)out->header)->serial;
-    }
-}
-
-static inline uint32_t spice_header_get_msg_sub_list(uint8_t *header, gboolean is_mini_header)
-{
-    if (is_mini_header) {
-        return 0;
-    } else {
-        return ((SpiceDataHeader *)header)->sub_list;
-    }
-}
-
-/* ---------------------------------------------------------------- */
-/* private msg api                                                  */
-
-G_GNUC_INTERNAL
-SpiceMsgIn *spice_msg_in_new(SpiceChannel *channel)
-{
-    SpiceMsgIn *in;
-
-    g_return_val_if_fail(channel != NULL, NULL);
-
-    in = g_slice_new0(SpiceMsgIn);
-    in->refcount = 1;
-    in->channel  = channel;
-
-    return in;
-}
-
-G_GNUC_INTERNAL
-SpiceMsgIn *spice_msg_in_sub_new(SpiceChannel *channel, SpiceMsgIn *parent,
-                                   SpiceSubMessage *sub)
-{
-    SpiceMsgIn *in;
-
-    g_return_val_if_fail(channel != NULL, NULL);
-
-    in = spice_msg_in_new(channel);
-    spice_header_set_msg_type(in->header, channel->priv->use_mini_header, sub->type);
-    spice_header_set_msg_size(in->header, channel->priv->use_mini_header, sub->size);
-    in->data = (uint8_t*)(sub+1);
-    in->dpos = sub->size;
-    in->parent = parent;
-    spice_msg_in_ref(parent);
-    return in;
-}
-
-G_GNUC_INTERNAL
-void spice_msg_in_ref(SpiceMsgIn *in)
-{
-    g_return_if_fail(in != NULL);
-
-    in->refcount++;
-}
-
-G_GNUC_INTERNAL
-void spice_msg_in_unref(SpiceMsgIn *in)
-{
-    g_return_if_fail(in != NULL);
-
-    in->refcount--;
-    if (in->refcount > 0)
-        return;
-    if (in->parsed)
-        in->pfree(in->parsed);
-    if (in->parent) {
-        spice_msg_in_unref(in->parent);
-    } else {
-        g_free(in->data);
-    }
-    g_slice_free(SpiceMsgIn, in);
-}
-
-G_GNUC_INTERNAL
-int spice_msg_in_type(SpiceMsgIn *in)
-{
-    g_return_val_if_fail(in != NULL, -1);
-
-    return spice_header_get_msg_type(in->header, in->channel->priv->use_mini_header);
-}
-
-G_GNUC_INTERNAL
-void *spice_msg_in_parsed(SpiceMsgIn *in)
-{
-    g_return_val_if_fail(in != NULL, NULL);
-
-    return in->parsed;
-}
-
-G_GNUC_INTERNAL
-void *spice_msg_in_raw(SpiceMsgIn *in, int *len)
-{
-    g_return_val_if_fail(in != NULL, NULL);
-    g_return_val_if_fail(len != NULL, NULL);
-
-    *len = in->dpos;
-    return in->data;
-}
-
-static void hexdump(const char *prefix, unsigned char *data, int len)
-{
-    int i;
-
-    for (i = 0; i < len; i++) {
-        if (i % 16 == 0)
-            fprintf(stderr, "%s:", prefix);
-        if (i % 4 == 0)
-            fprintf(stderr, " ");
-        fprintf(stderr, " %02x", data[i]);
-        if (i % 16 == 15)
-            fprintf(stderr, "\n");
-    }
-    if (i % 16 != 0)
-        fprintf(stderr, "\n");
-}
-
-G_GNUC_INTERNAL
-void spice_msg_in_hexdump(SpiceMsgIn *in)
-{
-    SpiceChannelPrivate *c = in->channel->priv;
-
-    fprintf(stderr, "--\n<< hdr: %s serial %" PRIu64 " type %d size %d sub-list %d\n",
-            c->name, spice_header_get_in_msg_serial(in),
-            spice_header_get_msg_type(in->header, c->use_mini_header),
-            spice_header_get_msg_size(in->header, c->use_mini_header),
-            spice_header_get_msg_sub_list(in->header, c->use_mini_header));
-    hexdump("<< msg", in->data, in->dpos);
-}
-
-G_GNUC_INTERNAL
-void spice_msg_out_hexdump(SpiceMsgOut *out, unsigned char *data, int len)
-{
-    SpiceChannelPrivate *c = out->channel->priv;
-
-    fprintf(stderr, "--\n>> hdr: %s serial %" PRIu64 " type %d size %d sub-list %d\n",
-            c->name,
-            spice_header_get_out_msg_serial(out),
-            spice_header_get_msg_type(out->header, c->use_mini_header),
-            spice_header_get_msg_size(out->header, c->use_mini_header),
-            spice_header_get_msg_sub_list(out->header, c->use_mini_header));
-    hexdump(">> msg", data, len);
-}
-
-static gboolean msg_check_read_only (int channel_type, int msg_type)
-{
-    if (msg_type < 100) // those are the common messages
-        return FALSE;
-
-    switch (channel_type) {
-    /* messages allowed to be sent in read-only mode */
-    case SPICE_CHANNEL_MAIN:
-        switch (msg_type) {
-        case SPICE_MSGC_MAIN_CLIENT_INFO:
-        case SPICE_MSGC_MAIN_MIGRATE_CONNECTED:
-        case SPICE_MSGC_MAIN_MIGRATE_CONNECT_ERROR:
-        case SPICE_MSGC_MAIN_ATTACH_CHANNELS:
-        case SPICE_MSGC_MAIN_MIGRATE_END:
-            return FALSE;
-        }
-        break;
-    case SPICE_CHANNEL_DISPLAY:
-        return FALSE;
-    }
-
-    return TRUE;
-}
-
-G_GNUC_INTERNAL
-SpiceMsgOut *spice_msg_out_new(SpiceChannel *channel, int type)
-{
-    SpiceChannelPrivate *c = channel->priv;
-    SpiceMsgOut *out;
-
-    g_return_val_if_fail(c != NULL, NULL);
-
-    out = g_slice_new0(SpiceMsgOut);
-    out->refcount = 1;
-    out->channel  = channel;
-    out->ro_check = msg_check_read_only(c->channel_type, type);
-
-    out->marshallers = c->marshallers;
-    out->marshaller = spice_marshaller_new();
-
-    out->header = spice_marshaller_reserve_space(out->marshaller,
-                                                 spice_header_get_header_size(c->use_mini_header));
-    spice_marshaller_set_base(out->marshaller, spice_header_get_header_size(c->use_mini_header));
-    spice_header_set_msg_type(out->header, c->use_mini_header, type);
-    spice_header_set_msg_serial(out->header, c->use_mini_header, c->out_serial);
-    spice_header_reset_msg_sub_list(out->header, c->use_mini_header);
-
-    c->out_serial++;
-    return out;
-}
-
-G_GNUC_INTERNAL
-void spice_msg_out_ref(SpiceMsgOut *out)
-{
-    g_return_if_fail(out != NULL);
-
-    out->refcount++;
-}
-
-G_GNUC_INTERNAL
-void spice_msg_out_unref(SpiceMsgOut *out)
-{
-    g_return_if_fail(out != NULL);
-
-    out->refcount--;
-    if (out->refcount > 0)
-        return;
-    spice_marshaller_destroy(out->marshaller);
-    g_slice_free(SpiceMsgOut, out);
-}
-
-/* system context */
-static gboolean spice_channel_idle_wakeup(gpointer user_data)
-{
-    SpiceChannel *channel = SPICE_CHANNEL(user_data);
-    SpiceChannelPrivate *c = channel->priv;
-
-    /*
-     * Note:
-     *
-     * - This must be done before the wakeup as that may eventually
-     *   call channel_reset() which checks this.
-     * - The lock calls are really necessary, this fixes the following race:
-     *   1) usb-event-thread calls spice_msg_out_send()
-     *   2) spice_msg_out_send calls g_timeout_add_full(...)
-     *   3) we run, set xmit_queue_wakeup_id to 0
-     *   4) spice_msg_out_send stores the result of g_timeout_add_full() in
-     *      xmit_queue_wakeup_id, overwriting the 0 we just stored
-     *   5) xmit_queue_wakeup_id now says there is a wakeup pending which is
-     *      false
-     */
-    STATIC_MUTEX_LOCK(c->xmit_queue_lock);
-    c->xmit_queue_wakeup_id = 0;
-    STATIC_MUTEX_UNLOCK(c->xmit_queue_lock);
-
-    spice_channel_wakeup(channel, FALSE);
-
-    return FALSE;
-}
-
-/* any context (system/co-routine/usb-event-thread) */
-G_GNUC_INTERNAL
-void spice_msg_out_send(SpiceMsgOut *out)
-{
-    SpiceChannelPrivate *c;
-    gboolean was_empty;
-
-    g_return_if_fail(out != NULL);
-    g_return_if_fail(out->channel != NULL);
-    c = out->channel->priv;
-
-    STATIC_MUTEX_LOCK(c->xmit_queue_lock);
-    if (c->xmit_queue_blocked) {
-        g_warning("message queue is blocked, dropping message");
-        goto end;
-    }
-
-    was_empty = g_queue_is_empty(&c->xmit_queue);
-    g_queue_push_tail(&c->xmit_queue, out);
-
-    /* One wakeup is enough to empty the entire queue -> only do a wakeup
-       if the queue was empty, and there isn't one pending already. */
-    if (was_empty && !c->xmit_queue_wakeup_id) {
-        c->xmit_queue_wakeup_id =
-            /* Use g_timeout_add_full so that can specify the priority */
-            g_timeout_add_full(G_PRIORITY_HIGH, 0,
-                               spice_channel_idle_wakeup,
-                               out->channel, NULL);
-    }
-
-end:
-    STATIC_MUTEX_UNLOCK(c->xmit_queue_lock);
-}
-
-/* coroutine context */
-G_GNUC_INTERNAL
-void spice_msg_out_send_internal(SpiceMsgOut *out)
-{
-    g_return_if_fail(out != NULL);
-
-    spice_channel_write_msg(out->channel, out);
-}
-
-/*
- * Write all 'data' of length 'datalen' bytes out to
- * the wire
- */
-/* coroutine context */
-static void spice_channel_flush_wire(SpiceChannel *channel,
-                                     const void *data,
-                                     size_t datalen)
-{
-    SpiceChannelPrivate *c = channel->priv;
-    const char *ptr = data;
-    size_t offset = 0;
-    GIOCondition cond;
-
-    while (offset < datalen) {
-        gssize ret;
-        GError *error = NULL;
-
-        if (c->has_error) return;
-
-        cond = 0;
-        if (c->tls) {
-            ret = SSL_write(c->ssl, ptr+offset, datalen-offset);
-            if (ret < 0) {
-                ret = SSL_get_error(c->ssl, ret);
-                if (ret == SSL_ERROR_WANT_READ)
-                    cond |= G_IO_IN;
-                if (ret == SSL_ERROR_WANT_WRITE)
-                    cond |= G_IO_OUT;
-                ret = -1;
-            }
-        } else {
-            ret = g_pollable_output_stream_write_nonblocking(G_POLLABLE_OUTPUT_STREAM(c->out),
-                                                             ptr+offset, datalen-offset, NULL, &error);
-            if (ret < 0) {
-                if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
-                    cond = G_IO_OUT;
-                } else {
-                    CHANNEL_DEBUG(channel, "Send error %s", error->message);
-                }
-                g_clear_error(&error);
-                ret = -1;
-            }
-        }
-        if (ret == -1) {
-            if (cond != 0) {
-                // TODO: should use g_pollable_input/output_stream_create_source() in 2.28 ?
-                g_coroutine_socket_wait(&c->coroutine, c->sock, cond);
-                continue;
-            } else {
-                CHANNEL_DEBUG(channel, "Closing the channel: spice_channel_flush %d", errno);
-                c->has_error = TRUE;
-                return;
-            }
-        }
-        if (ret == 0) {
-            CHANNEL_DEBUG(channel, "Closing the connection: spice_channel_flush");
-            c->has_error = TRUE;
-            return;
-        }
-        offset += ret;
-    }
-}
-
-#if HAVE_SASL
-/*
- * Encode all buffered data, write all encrypted data out
- * to the wire
- */
-static void spice_channel_flush_sasl(SpiceChannel *channel, const void *data, size_t len)
-{
-    SpiceChannelPrivate *c = channel->priv;
-    const char *output;
-    unsigned int outputlen;
-    int err;
-
-    err = sasl_encode(c->sasl_conn, data, len, &output, &outputlen);
-    if (err != SASL_OK) {
-        g_warning ("Failed to encode SASL data %s",
-                   sasl_errstring(err, NULL, NULL));
-        c->has_error = TRUE;
-        return;
-    }
-
-    //CHANNEL_DEBUG(channel, "Flush SASL %d: %p %d", len, output, outputlen);
-    spice_channel_flush_wire(channel, output, outputlen);
-}
-#endif
-
-/* coroutine context */
-static void spice_channel_write(SpiceChannel *channel, const void *data, size_t len)
-{
-#if HAVE_SASL
-    SpiceChannelPrivate *c = channel->priv;
-
-    if (c->sasl_conn)
-        spice_channel_flush_sasl(channel, data, len);
-    else
-#endif
-        spice_channel_flush_wire(channel, data, len);
-}
-
-/* coroutine context */
-static void spice_channel_write_msg(SpiceChannel *channel, SpiceMsgOut *out)
-{
-    uint8_t *data;
-    int free_data;
-    size_t len;
-    uint32_t msg_size;
-
-    g_return_if_fail(channel != NULL);
-    g_return_if_fail(out != NULL);
-    g_return_if_fail(channel == out->channel);
-
-    if (out->ro_check &&
-        spice_channel_get_read_only(channel)) {
-        g_warning("Try to send message while read-only. Please report a bug.");
-        return;
-    }
-
-    msg_size = spice_marshaller_get_total_size(out->marshaller) -
-               spice_header_get_header_size(channel->priv->use_mini_header);
-    spice_header_set_msg_size(out->header, channel->priv->use_mini_header, msg_size);
-    data = spice_marshaller_linearize(out->marshaller, 0, &len, &free_data);
-    /* spice_msg_out_hexdump(out, data, len); */
-    spice_channel_write(channel, data, len);
-
-    if (free_data)
-        g_free(data);
-
-    spice_msg_out_unref(out);
-}
-
-/*
- * Read at least 1 more byte of data straight off the wire
- * into the requested buffer.
- */
-/* coroutine context */
-static int spice_channel_read_wire(SpiceChannel *channel, void *data, size_t len)
-{
-    SpiceChannelPrivate *c = channel->priv;
-    gssize ret;
-    GIOCondition cond;
-
-reread:
-
-    if (c->has_error) return 0; /* has_error is set by disconnect(), return no error */
-
-    cond = 0;
-    if (c->tls) {
-        ret = SSL_read(c->ssl, data, len);
-        if (ret < 0) {
-            ret = SSL_get_error(c->ssl, ret);
-            if (ret == SSL_ERROR_WANT_READ)
-                cond |= G_IO_IN;
-            if (ret == SSL_ERROR_WANT_WRITE)
-                cond |= G_IO_OUT;
-            ret = -1;
-        }
-    } else {
-        GError *error = NULL;
-        ret = g_pollable_input_stream_read_nonblocking(G_POLLABLE_INPUT_STREAM(c->in),
-                                                       data, len, NULL, &error);
-        if (ret < 0) {
-            if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
-                cond = G_IO_IN;
-            } else {
-                CHANNEL_DEBUG(channel, "Read error %s", error->message);
-            }
-            g_clear_error(&error);
-            ret = -1;
-        }
-    }
-
-    if (ret == -1) {
-        if (cond != 0) {
-            // TODO: should use g_pollable_input/output_stream_create_source() ?
-            g_coroutine_socket_wait(&c->coroutine, c->sock, cond);
-            goto reread;
-        } else {
-            c->has_error = TRUE;
-            return -errno;
-        }
-    }
-    if (ret == 0) {
-        CHANNEL_DEBUG(channel, "Closing the connection: spice_channel_read() - ret=0");
-        c->has_error = TRUE;
-        return 0;
-    }
-
-    return ret;
-}
-
-#if HAVE_SASL
-/*
- * Read at least 1 more byte of data out of the SASL decrypted
- * data buffer, into the internal read buffer
- */
-static int spice_channel_read_sasl(SpiceChannel *channel, void *data, size_t len)
-{
-    SpiceChannelPrivate *c = channel->priv;
-
-    /* CHANNEL_DEBUG(channel, "Read %lu SASL %p size %d offset %d", len, c->sasl_decoded, */
-    /*             c->sasl_decoded_length, c->sasl_decoded_offset); */
-
-    if (c->sasl_decoded == NULL || c->sasl_decoded_length == 0) {
-        char encoded[8192]; /* should stay lower than maxbufsize */
-        int err, ret;
-
-        g_warn_if_fail(c->sasl_decoded_offset == 0);
-
-        ret = spice_channel_read_wire(channel, encoded, sizeof(encoded));
-        if (ret < 0)
-            return ret;
-
-        err = sasl_decode(c->sasl_conn, encoded, ret,
-                          &c->sasl_decoded, &c->sasl_decoded_length);
-        if (err != SASL_OK) {
-            g_warning("Failed to decode SASL data %s",
-                      sasl_errstring(err, NULL, NULL));
-            c->has_error = TRUE;
-            return -EINVAL;
-        }
-        c->sasl_decoded_offset = 0;
-    }
-
-    if (c->sasl_decoded_length == 0)
-        return 0;
-
-    len = MIN(c->sasl_decoded_length - c->sasl_decoded_offset, len);
-    memcpy(data, c->sasl_decoded + c->sasl_decoded_offset, len);
-    c->sasl_decoded_offset += len;
-
-    if (c->sasl_decoded_offset == c->sasl_decoded_length) {
-        c->sasl_decoded_length = c->sasl_decoded_offset = 0;
-        c->sasl_decoded = NULL;
-    }
-
-    return len;
-}
-#endif
-
-/*
- * Fill the 'data' buffer up with exactly 'len' bytes worth of data
- */
-/* coroutine context */
-static int spice_channel_read(SpiceChannel *channel, void *data, size_t length)
-{
-    SpiceChannelPrivate *c = channel->priv;
-    gsize len = length;
-    int ret;
-
-    while (len > 0) {
-        if (c->has_error) return 0; /* has_error is set by disconnect(), return no error */
-
-#if HAVE_SASL
-        if (c->sasl_conn)
-            ret = spice_channel_read_sasl(channel, data, len);
-        else
-#endif
-            ret = spice_channel_read_wire(channel, data, len);
-        if (ret < 0)
-            return ret;
-        g_assert(ret <= len);
-        len -= ret;
-        data = ((char*)data) + ret;
-#if DEBUG
-        if (len > 0)
-            CHANNEL_DEBUG(channel, "still needs %" G_GSIZE_FORMAT, len);
-#endif
-    }
-    c->total_read_bytes += length;
-
-    return length;
-}
-
-/* coroutine context */
-static void spice_channel_send_spice_ticket(SpiceChannel *channel)
-{
-    SpiceChannelPrivate *c = channel->priv;
-    EVP_PKEY *pubkey;
-    int nRSASize;
-    BIO *bioKey;
-    RSA *rsa;
-    char *password;
-    uint8_t *encrypted;
-    int rc;
-
-    bioKey = BIO_new(BIO_s_mem());
-    g_return_if_fail(bioKey != NULL);
-
-    BIO_write(bioKey, c->peer_msg->pub_key, SPICE_TICKET_PUBKEY_BYTES);
-    pubkey = d2i_PUBKEY_bio(bioKey, NULL);
-    g_return_if_fail(pubkey != NULL);
-
-    rsa = pubkey->pkey.rsa;
-    nRSASize = RSA_size(rsa);
-
-    encrypted = g_alloca(nRSASize);
-    /*
-      The use of RSA encryption limit the potential maximum password length.
-      for RSA_PKCS1_OAEP_PADDING it is RSA_size(rsa) - 41.
-    */
-    g_object_get(c->session, "password", &password, NULL);
-    if (password == NULL)
-        password = g_strdup("");
-    rc = RSA_public_encrypt(strlen(password) + 1, (uint8_t*)password,
-                            encrypted, rsa, RSA_PKCS1_OAEP_PADDING);
-    g_warn_if_fail(rc > 0);
-
-    spice_channel_write(channel, encrypted, nRSASize);
-    memset(encrypted, 0, nRSASize);
-    EVP_PKEY_free(pubkey);
-    BIO_free(bioKey);
-    g_free(password);
-}
-
-/* coroutine context */
-static void spice_channel_failed_authentication(SpiceChannel *channel)
-{
-    SpiceChannelPrivate *c = channel->priv;
-
-    if (c->auth_needs_username_and_password)
-        g_set_error_literal(&c->error,
-                            SPICE_CLIENT_ERROR,
-                            SPICE_CLIENT_ERROR_AUTH_NEEDS_PASSWORD_AND_USERNAME,
-                            _("Authentication failed: password and username are required"));
-    else
-        g_set_error_literal(&c->error,
-                            SPICE_CLIENT_ERROR,
-                            SPICE_CLIENT_ERROR_AUTH_NEEDS_PASSWORD,
-                            _("Authentication failed: password is required"));
-
-    c->event = SPICE_CHANNEL_ERROR_AUTH;
-
-    c->has_error = TRUE; /* force disconnect */
-}
-
-/* coroutine context */
-static gboolean spice_channel_recv_auth(SpiceChannel *channel)
-{
-    SpiceChannelPrivate *c = channel->priv;
-    uint32_t link_res;
-    int rc;
-
-    rc = spice_channel_read(channel, &link_res, sizeof(link_res));
-    if (rc != sizeof(link_res)) {
-        CHANNEL_DEBUG(channel, "incomplete auth reply (%d/%" G_GSIZE_FORMAT ")",
-                    rc, sizeof(link_res));
-        c->event = SPICE_CHANNEL_ERROR_LINK;
-        return FALSE;
-    }
-
-    if (link_res != SPICE_LINK_ERR_OK) {
-        CHANNEL_DEBUG(channel, "link result: reply %d", link_res);
-        spice_channel_failed_authentication(channel);
-        return FALSE;
-    }
-
-    c->state = SPICE_CHANNEL_STATE_READY;
-
-    g_coroutine_signal_emit(channel, signals[SPICE_CHANNEL_EVENT], 0, SPICE_CHANNEL_OPENED);
-
-    if (c->state == SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE) {
-        spice_channel_send_migration_handshake(channel);
-    }
-
-    if (c->state != SPICE_CHANNEL_STATE_MIGRATING)
-        spice_channel_up(channel);
-
-    return TRUE;
-}
-
-G_GNUC_INTERNAL
-void spice_channel_up(SpiceChannel *channel)
-{
-    SpiceChannelPrivate *c = channel->priv;
-
-    CHANNEL_DEBUG(channel, "channel up, state %d", c->state);
-
-    if (SPICE_CHANNEL_GET_CLASS(channel)->channel_up)
-        SPICE_CHANNEL_GET_CLASS(channel)->channel_up(channel);
-}
-
-/* coroutine context */
-static void spice_channel_send_link(SpiceChannel *channel)
-{
-    SpiceChannelPrivate *c = channel->priv;
-    uint8_t *buffer, *p;
-    int protocol, i;
-
-    c->link_hdr.magic = SPICE_MAGIC;
-    c->link_hdr.size = sizeof(c->link_msg);
-
-    g_object_get(c->session, "protocol", &protocol, NULL);
-    switch (protocol) {
-    case 1: /* protocol 1 == major 1, old 0.4 protocol, last active minor */
-        c->link_hdr.major_version = 1;
-        c->link_hdr.minor_version = 3;
-        c->parser = spice_get_server_channel_parser1(c->channel_type, NULL);
-        c->marshallers = spice_message_marshallers_get1();
-        break;
-    case SPICE_VERSION_MAJOR: /* protocol 2 == current */
-        c->link_hdr.major_version = SPICE_VERSION_MAJOR;
-        c->link_hdr.minor_version = SPICE_VERSION_MINOR;
-        c->parser = spice_get_server_channel_parser(c->channel_type, NULL);
-        c->marshallers = spice_message_marshallers_get();
-        break;
-    default:
-        g_critical("unknown major %d", protocol);
-        return;
-    }
-
-    c->link_msg.connection_id = spice_session_get_connection_id(c->session);
-    c->link_msg.channel_type  = c->channel_type;
-    c->link_msg.channel_id    = c->channel_id;
-    c->link_msg.caps_offset   = sizeof(c->link_msg);
-
-    c->link_msg.num_common_caps = c->common_caps->len;
-    c->link_msg.num_channel_caps = c->caps->len;
-    c->link_hdr.size += (c->link_msg.num_common_caps +
-                         c->link_msg.num_channel_caps) * sizeof(uint32_t);
-
-    buffer = g_malloc0(sizeof(c->link_hdr) + c->link_hdr.size);
-    p = buffer;
-
-    memcpy(p, &c->link_hdr, sizeof(c->link_hdr)); p += sizeof(c->link_hdr);
-    memcpy(p, &c->link_msg, sizeof(c->link_msg)); p += sizeof(c->link_msg);
-
-    for (i = 0; i < c->common_caps->len; i++) {
-        *(uint32_t *)p = g_array_index(c->common_caps, uint32_t, i);
-        p += sizeof(uint32_t);
-    }
-    for (i = 0; i < c->caps->len; i++) {
-        *(uint32_t *)p = g_array_index(c->caps, uint32_t, i);
-        p += sizeof(uint32_t);
-    }
-    CHANNEL_DEBUG(channel, "channel type %d id %d num common caps %d num caps %d",
-                  c->link_msg.channel_type,
-                  c->link_msg.channel_id,
-                  c->link_msg.num_common_caps,
-                  c->link_msg.num_channel_caps);
-    spice_channel_write(channel, buffer, p - buffer);
-    g_free(buffer);
-}
-
-/* coroutine context */
-static gboolean spice_channel_recv_link_hdr(SpiceChannel *channel)
-{
-    SpiceChannelPrivate *c = channel->priv;
-    int rc;
-
-    rc = spice_channel_read(channel, &c->peer_hdr, sizeof(c->peer_hdr));
-    if (rc != sizeof(c->peer_hdr)) {
-        g_warning("incomplete link header (%d/%" G_GSIZE_FORMAT ")",
-                  rc, sizeof(c->peer_hdr));
-        goto error;
-    }
-    if (c->peer_hdr.magic != SPICE_MAGIC) {
-        g_warning("invalid SPICE_MAGIC!");
-        goto error;
-    }
-
-    CHANNEL_DEBUG(channel, "Peer version: %d:%d", c->peer_hdr.major_version, c->peer_hdr.minor_version);
-    if (c->peer_hdr.major_version != c->link_hdr.major_version) {
-        g_warning("major mismatch (got %d, expected %d)",
-                  c->peer_hdr.major_version, c->link_hdr.major_version);
-        goto error;
-    }
-
-    c->peer_msg = g_malloc0(c->peer_hdr.size);
-    if (c->peer_msg == NULL) {
-        g_warning("invalid peer header size: %u", c->peer_hdr.size);
-        goto error;
-    }
-
-    return TRUE;
-
-error:
-    /* Windows socket seems to give early CONNRESET errors. The server
-       does not linger when closing the socket if the protocol is
-       incompatible. Try with the oldest protocol in this case: */
-    if (c->link_hdr.major_version != 1) {
-        SPICE_DEBUG("%s: error, switching to protocol 1 (spice 0.4)", c->name);
-        c->state = SPICE_CHANNEL_STATE_RECONNECTING;
-        g_object_set(c->session, "protocol", 1, NULL);
-        return FALSE;
-    }
-
-    c->event = SPICE_CHANNEL_ERROR_LINK;
-    return FALSE;
-}
-
-#if HAVE_SASL
-/*
- * NB, keep in sync with similar method in spice/server/reds.c
- */
-static gchar *addr_to_string(GSocketAddress *addr)
-{
-    GInetSocketAddress *iaddr = G_INET_SOCKET_ADDRESS(addr);
-    guint16 port;
-    GInetAddress *host;
-    gchar *hoststr;
-    gchar *ret;
-
-    host = g_inet_socket_address_get_address(iaddr);
-    port = g_inet_socket_address_get_port(iaddr);
-    hoststr = g_inet_address_to_string(host);
-
-    ret = g_strdup_printf("%s;%hu", hoststr, port);
-    g_free(hoststr);
-
-    return ret;
-}
-
-static gboolean
-spice_channel_gather_sasl_credentials(SpiceChannel *channel,
-				       sasl_interact_t *interact)
-{
-    SpiceChannelPrivate *c;
-    int ninteract;
-    gboolean ret = TRUE;
-
-    g_return_val_if_fail(channel != NULL, FALSE);
-    g_return_val_if_fail(channel->priv != NULL, FALSE);
-
-    c = channel->priv;
-
-    /* FIXME: we could keep connection open and ask connection details if missing */
-
-    for (ninteract = 0 ; interact[ninteract].id != 0 ; ninteract++) {
-        switch (interact[ninteract].id) {
-        case SASL_CB_AUTHNAME:
-        case SASL_CB_USER:
-            c->auth_needs_username_and_password = TRUE;
-            if (spice_session_get_username(c->session) == NULL)
-                return FALSE;
-
-            interact[ninteract].result =  spice_session_get_username(c->session);
-            interact[ninteract].len = strlen(interact[ninteract].result);
-            break;
-
-        case SASL_CB_PASS:
-            if (spice_session_get_password(c->session) == NULL) {
-                /* Even if we reach this point, we have to continue looking for
-                 * SASL_CB_AUTHNAME|SASL_CB_USER, otherwise we would return a
-                 * wrong error to the applications */
-                ret = FALSE;
-                continue;
-            }
-
-            interact[ninteract].result =  spice_session_get_password(c->session);
-            interact[ninteract].len = strlen(interact[ninteract].result);
-            break;
-        }
-    }
-
-    CHANNEL_DEBUG(channel, "Filled SASL interact");
-
-    return ret;
-}
-
-/*
- *
- * Init msg from server
- *
- *  u32 mechlist-length
- *  u8-array mechlist-string
- *
- * Start msg to server
- *
- *  u32 mechname-length
- *  u8-array mechname-string
- *  u32 clientout-length
- *  u8-array clientout-string
- *
- * Start msg from server
- *
- *  u32 serverin-length
- *  u8-array serverin-string
- *  u8 continue
- *
- * Step msg to server
- *
- *  u32 clientout-length
- *  u8-array clientout-string
- *
- * Step msg from server
- *
- *  u32 serverin-length
- *  u8-array serverin-string
- *  u8 continue
- */
-
-#define SASL_MAX_MECHLIST_LEN 300
-#define SASL_MAX_MECHNAME_LEN 100
-#define SASL_MAX_DATA_LEN (1024 * 1024)
-
-/* Perform the SASL authentication process
- */
-static gboolean spice_channel_perform_auth_sasl(SpiceChannel *channel)
-{
-    SpiceChannelPrivate *c;
-    sasl_conn_t *saslconn = NULL;
-    sasl_security_properties_t secprops;
-    const char *clientout;
-    char *serverin = NULL;
-    unsigned int clientoutlen;
-    int err;
-    char *localAddr = NULL, *remoteAddr = NULL;
-    const void *val;
-    sasl_ssf_t ssf;
-    static const sasl_callback_t saslcb[] = {
-        { .id = SASL_CB_USER },
-        { .id = SASL_CB_AUTHNAME },
-        { .id = SASL_CB_PASS },
-        { .id = 0 },
-    };
-    sasl_interact_t *interact = NULL;
-    guint32 len;
-    char *mechlist = NULL;
-    const char *mechname;
-    gboolean ret = FALSE;
-    GSocketAddress *addr = NULL;
-    guint8 complete;
-
-    g_return_val_if_fail(channel != NULL, FALSE);
-    g_return_val_if_fail(channel->priv != NULL, FALSE);
-
-    c = channel->priv;
-
-    /* Sets up the SASL library as a whole */
-    err = sasl_client_init(NULL);
-    CHANNEL_DEBUG(channel, "Client initialize SASL authentication %d", err);
-    if (err != SASL_OK) {
-        g_critical("failed to initialize SASL library: %d (%s)",
-                   err, sasl_errstring(err, NULL, NULL));
-        goto error;
-    }
-
-    /* Get local address in form  IPADDR:PORT */
-    addr = g_socket_get_local_address(c->sock, NULL);
-    if (!addr) {
-        g_critical("failed to get local address");
-        goto error;
-    }
-    if ((g_socket_address_get_family(addr) == G_SOCKET_FAMILY_IPV4 ||
-         g_socket_address_get_family(addr) == G_SOCKET_FAMILY_IPV6) &&
-        (localAddr = addr_to_string(addr)) == NULL)
-        goto error;
-    g_clear_object(&addr);
-
-    /* Get remote address in form  IPADDR:PORT */
-    addr = g_socket_get_remote_address(c->sock, NULL);
-    if (!addr) {
-        g_critical("failed to get peer address");
-        goto error;
-    }
-    if ((g_socket_address_get_family(addr) == G_SOCKET_FAMILY_IPV4 ||
-         g_socket_address_get_family(addr) == G_SOCKET_FAMILY_IPV6) &&
-        (remoteAddr = addr_to_string(addr)) == NULL)
-        goto error;
-    g_clear_object(&addr);
-
-    CHANNEL_DEBUG(channel, "Client SASL new host:'%s' local:'%s' remote:'%s'",
-                  spice_session_get_host(c->session), localAddr, remoteAddr);
-
-    /* Setup a handle for being a client */
-    err = sasl_client_new("spice",
-                          spice_session_get_host(c->session),
-                          localAddr,
-                          remoteAddr,
-                          saslcb,
-                          SASL_SUCCESS_DATA,
-                          &saslconn);
-
-    if (err != SASL_OK) {
-        g_critical("Failed to create SASL client context: %d (%s)",
-                   err, sasl_errstring(err, NULL, NULL));
-        goto error;
-    }
-
-    if (c->ssl) {
-        sasl_ssf_t ssf;
-
-        ssf = SSL_get_cipher_bits(c->ssl, NULL);
-        err = sasl_setprop(saslconn, SASL_SSF_EXTERNAL, &ssf);
-        if (err != SASL_OK) {
-            g_critical("cannot set SASL external SSF %d (%s)",
-                       err, sasl_errstring(err, NULL, NULL));
-            goto error;
-        }
-    }
-
-    memset(&secprops, 0, sizeof secprops);
-    /* If we've got TLS, we don't care about SSF */
-    secprops.min_ssf = c->ssl ? 0 : 56; /* Equiv to DES supported by all Kerberos */
-    secprops.max_ssf = c->ssl ? 0 : 100000; /* Very strong ! AES == 256 */
-    secprops.maxbufsize = 100000;
-    /* If we're not TLS, then forbid any anonymous or trivially crackable auth */
-    secprops.security_flags = c->ssl ? 0 :
-        SASL_SEC_NOANONYMOUS | SASL_SEC_NOPLAINTEXT;
-
-    err = sasl_setprop(saslconn, SASL_SEC_PROPS, &secprops);
-    if (err != SASL_OK) {
-        g_critical("cannot set security props %d (%s)",
-                   err, sasl_errstring(err, NULL, NULL));
-        goto error;
-    }
-
-    /* Get the supported mechanisms from the server */
-    spice_channel_read(channel, &len, sizeof(len));
-    if (c->has_error)
-        goto error;
-    if (len > SASL_MAX_MECHLIST_LEN) {
-        g_critical("mechlistlen %d too long", len);
-        goto error;
-    }
-
-    mechlist = g_malloc0(len + 1);
-    spice_channel_read(channel, mechlist, len);
-    mechlist[len] = '\0';
-    if (c->has_error) {
-        goto error;
-    }
-
-restart:
-    /* Start the auth negotiation on the client end first */
-    CHANNEL_DEBUG(channel, "Client start negotiation mechlist '%s'", mechlist);
-    err = sasl_client_start(saslconn,
-                            mechlist,
-                            &interact,
-                            &clientout,
-                            &clientoutlen,
-                            &mechname);
-    if (err != SASL_OK && err != SASL_CONTINUE && err != SASL_INTERACT) {
-        g_critical("Failed to start SASL negotiation: %d (%s)",
-                   err, sasl_errdetail(saslconn));
-        goto error;
-    }
-
-    /* Need to gather some credentials from the client */
-    if (err == SASL_INTERACT) {
-        if (!spice_channel_gather_sasl_credentials(channel, interact)) {
-            CHANNEL_DEBUG(channel, "Failed to collect auth credentials");
-            goto error;
-        }
-        goto restart;
-    }
-
-    CHANNEL_DEBUG(channel, "Server start negotiation with mech %s. Data %d bytes %p '%s'",
-                  mechname, clientoutlen, clientout, clientout);
-
-    if (clientoutlen > SASL_MAX_DATA_LEN) {
-        g_critical("SASL negotiation data too long: %d bytes",
-                   clientoutlen);
-        goto error;
-    }
-
-    /* Send back the chosen mechname */
-    len = strlen(mechname);
-    spice_channel_write(channel, &len, sizeof(guint32));
-    spice_channel_write(channel, mechname, len);
-
-    /* NB, distinction of NULL vs "" is *critical* in SASL */
-    if (clientout) {
-        len = clientoutlen + 1;
-        spice_channel_write(channel, &len, sizeof(guint32));
-        spice_channel_write(channel, clientout, len);
-    } else {
-        len = 0;
-        spice_channel_write(channel, &len, sizeof(guint32));
-    }
-
-    if (c->has_error)
-        goto error;
-
-    CHANNEL_DEBUG(channel, "Getting sever start negotiation reply");
-    /* Read the 'START' message reply from server */
-    spice_channel_read(channel, &len, sizeof(len));
-    if (c->has_error)
-        goto error;
-    if (len > SASL_MAX_DATA_LEN) {
-        g_critical("SASL negotiation data too long: %d bytes",
-                   len);
-        goto error;
-    }
-
-    /* NB, distinction of NULL vs "" is *critical* in SASL */
-    if (len > 0) {
-        serverin = g_malloc0(len);
-        spice_channel_read(channel, serverin, len);
-        serverin[len - 1] = '\0';
-        len--;
-    } else {
-        serverin = NULL;
-    }
-    spice_channel_read(channel, &complete, sizeof(guint8));
-    if (c->has_error)
-        goto error;
-
-    CHANNEL_DEBUG(channel, "Client start result complete: %d. Data %d bytes %p '%s'",
-                complete, len, serverin, serverin);
-
-    /* Loop-the-loop...
-     * Even if the server has completed, the client must *always* do at least one step
-     * in this loop to verify the server isn't lying about something. Mutual auth */
-    for (;;) {
-       if (complete && err == SASL_OK)
-            break;
-
-    restep:
-        err = sasl_client_step(saslconn,
-                               serverin,
-                               len,
-                               &interact,
-                               &clientout,
-                               &clientoutlen);
-        if (err != SASL_OK && err != SASL_CONTINUE && err != SASL_INTERACT) {
-            g_critical("Failed SASL step: %d (%s)",
-                       err, sasl_errdetail(saslconn));
-            goto error;
-        }
-
-        /* Need to gather some credentials from the client */
-        if (err == SASL_INTERACT) {
-            if (!spice_channel_gather_sasl_credentials(channel,
-                                                       interact)) {
-                CHANNEL_DEBUG(channel, "%s", "Failed to collect auth credentials");
-                goto error;
-            }
-            goto restep;
-        }
-
-        if (serverin) {
-            g_free(serverin);
-            serverin = NULL;
-        }
-
-        CHANNEL_DEBUG(channel, "Client step result %d. Data %d bytes %p '%s'", err, clientoutlen, clientout, clientout);
-
-        /* Previous server call showed completion & we're now locally complete too */
-        if (complete && err == SASL_OK)
-            break;
-
-        /* Not done, prepare to talk with the server for another iteration */
-
-        /* NB, distinction of NULL vs "" is *critical* in SASL */
-        if (clientout) {
-            len = clientoutlen + 1;
-            spice_channel_write(channel, &len, sizeof(guint32));
-            spice_channel_write(channel, clientout, len);
-        } else {
-            len = 0;
-            spice_channel_write(channel, &len, sizeof(guint32));
-        }
-
-        if (c->has_error)
-            goto error;
-
-        CHANNEL_DEBUG(channel, "Server step with %d bytes %p", clientoutlen, clientout);
-
-        spice_channel_read(channel, &len, sizeof(guint32));
-        if (c->has_error)
-            goto error;
-        if (len > SASL_MAX_DATA_LEN) {
-            g_critical("SASL negotiation data too long: %d bytes", len);
-            goto error;
-        }
-
-        /* NB, distinction of NULL vs "" is *critical* in SASL */
-        if (len) {
-            serverin = g_malloc0(len);
-            spice_channel_read(channel, serverin, len);
-            serverin[len - 1] = '\0';
-            len--;
-        } else {
-            serverin = NULL;
-        }
-
-        spice_channel_read(channel, &complete, sizeof(guint8));
-        if (c->has_error)
-            goto error;
-
-        CHANNEL_DEBUG(channel, "Client step result complete: %d. Data %d bytes %p '%s'",
-                    complete, len, serverin, serverin);
-
-        /* This server call shows complete, and earlier client step was OK */
-        if (complete) {
-            g_free(serverin);
-            serverin = NULL;
-            if (err == SASL_CONTINUE) /* something went wrong */
-                goto complete;
-            break;
-        }
-    }
-
-    /* Check for suitable SSF if non-TLS */
-    if (!c->ssl) {
-        err = sasl_getprop(saslconn, SASL_SSF, &val);
-        if (err != SASL_OK) {
-            g_critical("cannot query SASL ssf on connection %d (%s)",
-                       err, sasl_errstring(err, NULL, NULL));
-            goto error;
-        }
-        ssf = *(const int *)val;
-        CHANNEL_DEBUG(channel, "SASL SSF value %d", ssf);
-        if (ssf < 56) { /* 56 == DES level, good for Kerberos */
-            g_critical("negotiation SSF %d was not strong enough", ssf);
-            goto error;
-        }
-    }
-
-complete:
-    CHANNEL_DEBUG(channel, "%s", "SASL authentication complete");
-    spice_channel_read(channel, &len, sizeof(len));
-    if (len == SPICE_LINK_ERR_OK) {
-        ret = TRUE;
-        /* This must come *after* check-auth-result, because the former
-         * is defined to be sent unencrypted, and setting saslconn turns
-         * on the SSF layer encryption processing */
-        c->sasl_conn = saslconn;
-        goto cleanup;
-    }
-
-error:
-    if (saslconn)
-        sasl_dispose(&saslconn);
-
-    spice_channel_failed_authentication(channel);
-    ret = FALSE;
-
-cleanup:
-    g_free(localAddr);
-    g_free(remoteAddr);
-    g_free(mechlist);
-    g_free(serverin);
-    g_clear_object(&addr);
-    return ret;
-}
-#endif /* HAVE_SASL */
-
-/* coroutine context */
-static gboolean spice_channel_recv_link_msg(SpiceChannel *channel)
-{
-    SpiceChannelPrivate *c;
-    int rc, num_caps, i;
-    uint32_t *caps;
-
-    g_return_val_if_fail(channel != NULL, FALSE);
-    g_return_val_if_fail(channel->priv != NULL, FALSE);
-
-    c = channel->priv;
-
-    rc = spice_channel_read(channel, (uint8_t*)c->peer_msg + c->peer_pos,
-                            c->peer_hdr.size - c->peer_pos);
-    c->peer_pos += rc;
-    if (c->peer_pos != c->peer_hdr.size) {
-        g_critical("%s: %s: incomplete link reply (%d/%d)",
-                  c->name, __FUNCTION__, rc, c->peer_hdr.size);
-        goto error;
-    }
-    switch (c->peer_msg->error) {
-    case SPICE_LINK_ERR_OK:
-        /* nothing */
-        break;
-    case SPICE_LINK_ERR_NEED_SECURED:
-        c->state = SPICE_CHANNEL_STATE_RECONNECTING;
-        CHANNEL_DEBUG(channel, "switching to tls");
-        c->tls = TRUE;
-        return FALSE;
-    default:
-        g_warning("%s: %s: unhandled error %d",
-                c->name, __FUNCTION__, c->peer_msg->error);
-        goto error;
-    }
-
-    num_caps = c->peer_msg->num_channel_caps + c->peer_msg->num_common_caps;
-    CHANNEL_DEBUG(channel, "%s: %d caps", __FUNCTION__, num_caps);
-
-    /* see original spice/client code: */
-    /* g_return_if_fail(c->peer_msg + c->peer_msg->caps_offset * sizeof(uint32_t) > c->peer_msg + c->peer_hdr.size); */
-
-    caps = (uint32_t *)((uint8_t *)c->peer_msg + c->peer_msg->caps_offset);
-
-    g_array_set_size(c->remote_common_caps, c->peer_msg->num_common_caps);
-    for (i = 0; i < c->peer_msg->num_common_caps; i++, caps++) {
-        g_array_index(c->remote_common_caps, uint32_t, i) = *caps;
-        CHANNEL_DEBUG(channel, "got common caps %u:0x%X", i, *caps);
-    }
-
-    g_array_set_size(c->remote_caps, c->peer_msg->num_channel_caps);
-    for (i = 0; i < c->peer_msg->num_channel_caps; i++, caps++) {
-        g_array_index(c->remote_caps, uint32_t, i) = *caps;
-        CHANNEL_DEBUG(channel, "got channel caps %u:0x%X", i, *caps);
-    }
-
-    if (!spice_channel_test_common_capability(channel,
-            SPICE_COMMON_CAP_PROTOCOL_AUTH_SELECTION)) {
-        CHANNEL_DEBUG(channel, "Server supports spice ticket auth only");
-        spice_channel_send_spice_ticket(channel);
-    } else {
-        SpiceLinkAuthMechanism auth = { 0, };
-
-#if HAVE_SASL
-        if (spice_channel_test_common_capability(channel, SPICE_COMMON_CAP_AUTH_SASL)) {
-            CHANNEL_DEBUG(channel, "Choosing SASL mechanism");
-            auth.auth_mechanism = SPICE_COMMON_CAP_AUTH_SASL;
-            spice_channel_write(channel, &auth, sizeof(auth));
-            if (!spice_channel_perform_auth_sasl(channel))
-                return FALSE;
-        } else
-#endif
-        if (spice_channel_test_common_capability(channel, SPICE_COMMON_CAP_AUTH_SPICE)) {
-            auth.auth_mechanism = SPICE_COMMON_CAP_AUTH_SPICE;
-            spice_channel_write(channel, &auth, sizeof(auth));
-            spice_channel_send_spice_ticket(channel);
-        } else {
-            g_warning("No compatible AUTH mechanism");
-            goto error;
-        }
-    }
-    c->use_mini_header = spice_channel_test_common_capability(channel,
-                                                              SPICE_COMMON_CAP_MINI_HEADER);
-    CHANNEL_DEBUG(channel, "use mini header: %d", c->use_mini_header);
-    return TRUE;
-
-error:
-    c->has_error = TRUE;
-    c->event = SPICE_CHANNEL_ERROR_LINK;
-    return FALSE;
-}
-
-/* system context */
-G_GNUC_INTERNAL
-void spice_channel_wakeup(SpiceChannel *channel, gboolean cancel)
-{
-    GCoroutine *c = &channel->priv->coroutine;
-
-    if (cancel)
-        g_coroutine_condition_cancel(c);
-
-    g_coroutine_wakeup(c);
-}
-
-G_GNUC_INTERNAL
-gboolean spice_channel_get_read_only(SpiceChannel *channel)
-{
-    return spice_session_get_read_only(channel->priv->session);
-}
-
-/* coroutine context */
-G_GNUC_INTERNAL
-void spice_channel_recv_msg(SpiceChannel *channel,
-                            handler_msg_in msg_handler, gpointer data)
-{
-    SpiceChannelPrivate *c = channel->priv;
-    SpiceMsgIn *in;
-    int msg_size;
-    int msg_type;
-    int sub_list_offset = 0;
-
-    in = spice_msg_in_new(channel);
-
-    /* receive message */
-    spice_channel_read(channel, in->header,
-                       spice_header_get_header_size(c->use_mini_header));
-    if (c->has_error)
-        goto end;
-
-    msg_size = spice_header_get_msg_size(in->header, c->use_mini_header);
-    /* FIXME: do not allow others to take ref on in, and use realloc here?
-     * this would avoid malloc/free on each message?
-     */
-    in->data = g_malloc0(msg_size);
-    spice_channel_read(channel, in->data, msg_size);
-    if (c->has_error)
-        goto end;
-    in->dpos = msg_size;
-
-    msg_type = spice_header_get_msg_type(in->header, c->use_mini_header);
-    sub_list_offset = spice_header_get_msg_sub_list(in->header, c->use_mini_header);
-
-    if (msg_type == SPICE_MSG_LIST || sub_list_offset) {
-        SpiceSubMessageList *sub_list;
-        SpiceSubMessage *sub;
-        SpiceMsgIn *sub_in;
-        int i;
-
-        sub_list = (SpiceSubMessageList *)(in->data + sub_list_offset);
-        for (i = 0; i < sub_list->size; i++) {
-            sub = (SpiceSubMessage *)(in->data + sub_list->sub_messages[i]);
-            sub_in = spice_msg_in_sub_new(channel, in, sub);
-            sub_in->parsed = c->parser(sub_in->data, sub_in->data + sub_in->dpos,
-                                       spice_header_get_msg_type(sub_in->header,
-                                                                 c->use_mini_header),
-                                       c->peer_hdr.minor_version,
-                                       &sub_in->psize, &sub_in->pfree);
-            if (sub_in->parsed == NULL) {
-                g_critical("failed to parse sub-message: %s type %d",
-                           c->name, spice_header_get_msg_type(sub_in->header, c->use_mini_header));
-                goto end;
-            }
-            msg_handler(channel, sub_in, data);
-            spice_msg_in_unref(sub_in);
-        }
-    }
-
-    /* ack message */
-    if (c->message_ack_count) {
-        c->message_ack_count--;
-        if (!c->message_ack_count) {
-            SpiceMsgOut *out = spice_msg_out_new(channel, SPICE_MSGC_ACK);
-            spice_msg_out_send_internal(out);
-            c->message_ack_count = c->message_ack_window;
-        }
-    }
-
-    if (msg_type == SPICE_MSG_LIST) {
-        goto end;
-    }
-
-    /* parse message */
-    in->parsed = c->parser(in->data, in->data + msg_size, msg_type,
-                           c->peer_hdr.minor_version, &in->psize, &in->pfree);
-    if (in->parsed == NULL) {
-        g_critical("failed to parse message: %s type %d",
-                   c->name, msg_type);
-        goto end;
-    }
-
-    /* process message */
-    /* spice_msg_in_hexdump(in); */
-    msg_handler(channel, in, data);
-
-end:
-    /* If the server uses full header, the serial is not necessarily equal
-     * to c->in_serial (the server can sometimes skip serials) */
-    c->last_message_serial = spice_header_get_in_msg_serial(in);
-    c->in_serial++;
-    spice_msg_in_unref(in);
-}
-
-static const char *to_string[] = {
-    NULL,
-    [ SPICE_CHANNEL_MAIN ] = "main",
-    [ SPICE_CHANNEL_DISPLAY ] = "display",
-    [ SPICE_CHANNEL_INPUTS ] = "inputs",
-    [ SPICE_CHANNEL_CURSOR ] = "cursor",
-    [ SPICE_CHANNEL_PLAYBACK ] = "playback",
-    [ SPICE_CHANNEL_RECORD ] = "record",
-    [ SPICE_CHANNEL_TUNNEL ] = "tunnel",
-    [ SPICE_CHANNEL_SMARTCARD ] = "smartcard",
-    [ SPICE_CHANNEL_USBREDIR ] = "usbredir",
-    [ SPICE_CHANNEL_PORT ] = "port",
-    [ SPICE_CHANNEL_WEBDAV ] = "webdav",
-};
-
-/**
- * spice_channel_type_to_string:
- * @type: a channel-type property value
- *
- * Convert a channel-type property value to a string.
- *
- * Returns: string representation of @type.
- * Since: 0.20
- **/
-const gchar* spice_channel_type_to_string(gint type)
-{
-    const char *str = NULL;
-
-    if (type >= 0 && type < G_N_ELEMENTS(to_string)) {
-        str = to_string[type];
-    }
-
-    return str ? str : "unknown channel type";
-}
-
-/**
- * spice_channel_string_to_type:
- * @str: a string representation of the channel-type property
- *
- * Convert a channel-type property value to a string.
- *
- * Returns: the channel-type property value for a @str channel
- * Since: 0.21
- **/
-gint spice_channel_string_to_type(const gchar *str)
-{
-    int i;
-
-    g_return_val_if_fail(str != NULL, -1);
-
-    for (i = 0; i < G_N_ELEMENTS(to_string); i++)
-        if (g_strcmp0(str, to_string[i]) == 0)
-            return i;
-
-    return -1;
-}
-
-G_GNUC_INTERNAL
-gchar *spice_channel_supported_string(void)
-{
-    return g_strjoin(", ",
-                     spice_channel_type_to_string(SPICE_CHANNEL_MAIN),
-                     spice_channel_type_to_string(SPICE_CHANNEL_DISPLAY),
-                     spice_channel_type_to_string(SPICE_CHANNEL_INPUTS),
-                     spice_channel_type_to_string(SPICE_CHANNEL_CURSOR),
-                     spice_channel_type_to_string(SPICE_CHANNEL_PLAYBACK),
-                     spice_channel_type_to_string(SPICE_CHANNEL_RECORD),
-#ifdef USE_SMARTCARD
-                     spice_channel_type_to_string(SPICE_CHANNEL_SMARTCARD),
-#endif
-#ifdef USE_USBREDIR
-                     spice_channel_type_to_string(SPICE_CHANNEL_USBREDIR),
-#endif
-#ifdef USE_PHODAV
-                     spice_channel_type_to_string(SPICE_CHANNEL_WEBDAV),
-#endif
-                     NULL);
-}
-
-
-/**
- * spice_channel_new:
- * @s: the @SpiceSession the channel is linked to
- * @type: the requested SPICECHANNELPRIVATE type
- * @id: the channel-id
- *
- * Create a new #SpiceChannel of type @type, and channel ID @id.
- *
- * Returns: a weak reference to #SpiceChannel, the session owns the reference
- **/
-SpiceChannel *spice_channel_new(SpiceSession *s, int type, int id)
-{
-    SpiceChannel *channel;
-    GType gtype = 0;
-
-    g_return_val_if_fail(s != NULL, NULL);
-
-    switch (type) {
-    case SPICE_CHANNEL_MAIN:
-        gtype = SPICE_TYPE_MAIN_CHANNEL;
-        break;
-    case SPICE_CHANNEL_DISPLAY:
-        gtype = SPICE_TYPE_DISPLAY_CHANNEL;
-        break;
-    case SPICE_CHANNEL_CURSOR:
-        gtype = SPICE_TYPE_CURSOR_CHANNEL;
-        break;
-    case SPICE_CHANNEL_INPUTS:
-        gtype = SPICE_TYPE_INPUTS_CHANNEL;
-        break;
-    case SPICE_CHANNEL_PLAYBACK:
-    case SPICE_CHANNEL_RECORD: {
-        if (!spice_session_get_audio_enabled(s)) {
-            g_debug("audio channel is disabled, not creating it");
-            return NULL;
-        }
-        gtype = type == SPICE_CHANNEL_RECORD ?
-            SPICE_TYPE_RECORD_CHANNEL : SPICE_TYPE_PLAYBACK_CHANNEL;
-        break;
-    }
-#ifdef USE_SMARTCARD
-    case SPICE_CHANNEL_SMARTCARD: {
-        if (!spice_session_get_smartcard_enabled(s)) {
-            g_debug("smartcard channel is disabled, not creating it");
-            return NULL;
-        }
-        gtype = SPICE_TYPE_SMARTCARD_CHANNEL;
-        break;
-    }
-#endif
-#ifdef USE_USBREDIR
-    case SPICE_CHANNEL_USBREDIR: {
-        if (!spice_session_get_usbredir_enabled(s)) {
-            g_debug("usbredir channel is disabled, not creating it");
-            return NULL;
-        }
-        gtype = SPICE_TYPE_USBREDIR_CHANNEL;
-        break;
-    }
-#endif
-#ifdef USE_PHODAV
-    case SPICE_CHANNEL_WEBDAV: {
-        gtype = SPICE_TYPE_WEBDAV_CHANNEL;
-        break;
-    }
-#endif
-    case SPICE_CHANNEL_PORT:
-        gtype = SPICE_TYPE_PORT_CHANNEL;
-        break;
-    default:
-        g_debug("unsupported channel kind: %s: %d",
-                spice_channel_type_to_string(type), type);
-        return NULL;
-    }
-    channel = SPICE_CHANNEL(g_object_new(gtype,
-                                         "spice-session", s,
-                                         "channel-type", type,
-                                         "channel-id", id,
-                                         NULL));
-    return channel;
-}
-
-/**
- * spice_channel_destroy:
- * @channel:
- *
- * Disconnect and unref the @channel.
- *
- * Deprecated: 0.27: this function has been deprecated because it is
- * misleading, the object is not actually destroyed. Instead, it is
- * recommended to call explicitely spice_channel_disconnect() and
- * g_object_unref().
- **/
-void spice_channel_destroy(SpiceChannel *channel)
-{
-    g_return_if_fail(channel != NULL);
-
-    CHANNEL_DEBUG(channel, "channel destroy");
-    spice_channel_disconnect(channel, SPICE_CHANNEL_NONE);
-    g_object_unref(channel);
-}
-
-/* any context */
-static void spice_channel_flushed(SpiceChannel *channel, gboolean success)
-{
-    SpiceChannelPrivate *c = channel->priv;
-    GSList *l;
-
-    for (l = c->flushing; l != NULL; l = l->next) {
-        GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT(l->data);
-        g_simple_async_result_set_op_res_gboolean(result, success);
-        g_simple_async_result_complete_in_idle(result);
-    }
-
-    g_slist_free_full(c->flushing, g_object_unref);
-    c->flushing = NULL;
-}
-
-/* coroutine context */
-static void spice_channel_iterate_write(SpiceChannel *channel)
-{
-    SpiceChannelPrivate *c = channel->priv;
-    SpiceMsgOut *out;
-
-    do {
-        STATIC_MUTEX_LOCK(c->xmit_queue_lock);
-        out = g_queue_pop_head(&c->xmit_queue);
-        STATIC_MUTEX_UNLOCK(c->xmit_queue_lock);
-        if (out)
-            spice_channel_write_msg(channel, out);
-    } while (out);
-
-    spice_channel_flushed(channel, TRUE);
-}
-
-/* coroutine context */
-static void spice_channel_iterate_read(SpiceChannel *channel)
-{
-    SpiceChannelPrivate *c = channel->priv;
-
-    g_coroutine_socket_wait(&c->coroutine, c->sock, G_IO_IN);
-
-    /* treat all incoming data (block on message completion) */
-    while (!c->has_error &&
-           c->state != SPICE_CHANNEL_STATE_MIGRATING &&
-           g_pollable_input_stream_is_readable(G_POLLABLE_INPUT_STREAM(c->in))
-    ) { do
-            spice_channel_recv_msg(channel,
-                                   (handler_msg_in)SPICE_CHANNEL_GET_CLASS(channel)->handle_msg, NULL);
-#if HAVE_SASL
-            /* flush the sasl buffer too */
-        while (c->sasl_decoded != NULL);
-#else
-        while (FALSE);
-#endif
-    }
-
-}
-
-static gboolean wait_migration(gpointer data)
-{
-    SpiceChannel *channel = SPICE_CHANNEL(data);
-    SpiceChannelPrivate *c = channel->priv;
-
-    if (c->state != SPICE_CHANNEL_STATE_MIGRATING) {
-        CHANNEL_DEBUG(channel, "unfreeze channel");
-        return TRUE;
-    }
-
-    return FALSE;
-}
-
-/* coroutine context */
-static gboolean spice_channel_iterate(SpiceChannel *channel)
-{
-    SpiceChannelPrivate *c = channel->priv;
-
-    if (c->state == SPICE_CHANNEL_STATE_MIGRATING &&
-        !g_coroutine_condition_wait(&c->coroutine, wait_migration, channel))
-        CHANNEL_DEBUG(channel, "migration wait cancelled");
-
-    /* flush any pending write and read */
-    if (!c->has_error)
-        SPICE_CHANNEL_GET_CLASS(channel)->iterate_write(channel);
-    if (!c->has_error)
-        SPICE_CHANNEL_GET_CLASS(channel)->iterate_read(channel);
-
-    if (c->has_error) {
-        GIOCondition ret;
-
-        if (!c->sock)
-            return FALSE;
-
-        /* We don't want to report an error if the socket was closed gracefully
-         * on the other end (VM shutdown) */
-        ret = g_socket_condition_check(c->sock, G_IO_IN | G_IO_ERR);
-
-        if (ret & G_IO_ERR) {
-            CHANNEL_DEBUG(channel, "channel got error");
-
-            if (c->state > SPICE_CHANNEL_STATE_CONNECTING) {
-                if (c->state == SPICE_CHANNEL_STATE_READY)
-                    c->event = SPICE_CHANNEL_ERROR_IO;
-                else
-                    c->event = SPICE_CHANNEL_ERROR_LINK;
-            }
-        }
-        return FALSE;
-    }
-
-    return TRUE;
-}
-
-/* we use an idle function to allow the coroutine to exit before we actually
- * unref the object since the coroutine's state is part of the object */
-static gboolean spice_channel_delayed_unref(gpointer data)
-{
-    SpiceChannel *channel = SPICE_CHANNEL(data);
-    SpiceChannelPrivate *c = channel->priv;
-    gboolean was_ready = c->state == SPICE_CHANNEL_STATE_READY;
-
-    CHANNEL_DEBUG(channel, "Delayed unref channel %p", channel);
-
-    g_return_val_if_fail(c->coroutine.coroutine.exited == TRUE, FALSE);
-
-    c->state = SPICE_CHANNEL_STATE_UNCONNECTED;
-
-    if (c->event != SPICE_CHANNEL_NONE) {
-        g_coroutine_signal_emit(channel, signals[SPICE_CHANNEL_EVENT], 0, c->event);
-        c->event = SPICE_CHANNEL_NONE;
-        g_clear_error(&c->error);
-    }
-
-    if (was_ready)
-        g_coroutine_signal_emit(channel, signals[SPICE_CHANNEL_EVENT], 0, SPICE_CHANNEL_CLOSED);
-
-    g_object_unref(G_OBJECT(data));
-
-    return FALSE;
-}
-
-static X509_LOOKUP_METHOD spice_x509_mem_lookup = {
-    "spice_x509_mem_lookup",
-    0
-};
-
-static int spice_channel_load_ca(SpiceChannel *channel)
-{
-    SpiceChannelPrivate *c = channel->priv;
-    STACK_OF(X509_INFO) *inf;
-    X509_INFO *itmp;
-    X509_LOOKUP *lookup;
-    BIO *in;
-    int i, count = 0;
-    guint8 *ca;
-    guint size;
-    const gchar *ca_file;
-    int rc;
-
-    g_return_val_if_fail(c->ctx != NULL, 0);
-
-    lookup = X509_STORE_add_lookup(c->ctx->cert_store, &spice_x509_mem_lookup);
-    ca_file = spice_session_get_ca_file(c->session);
-    spice_session_get_ca(c->session, &ca, &size);
-
-    CHANNEL_DEBUG(channel, "Load CA, file: %s, data: %p", ca_file, ca);
-    g_warn_if_fail(ca_file || ca);
-
-    if (ca != NULL) {
-        in = BIO_new_mem_buf(ca, size);
-        inf = PEM_X509_INFO_read_bio(in, NULL, NULL, NULL);
-        BIO_free(in);
-
-        for (i = 0; i < sk_X509_INFO_num(inf); i++) {
-            itmp = sk_X509_INFO_value(inf, i);
-            if (itmp->x509) {
-                X509_STORE_add_cert(lookup->store_ctx, itmp->x509);
-                count++;
-            }
-            if (itmp->crl) {
-                X509_STORE_add_crl(lookup->store_ctx, itmp->crl);
-                count++;
-            }
-        }
-
-        sk_X509_INFO_pop_free(inf, X509_INFO_free);
-    }
-
-    if (ca_file != NULL) {
-        rc = SSL_CTX_load_verify_locations(c->ctx, ca_file, NULL);
-        if (rc != 1)
-            g_warning("loading ca certs from %s failed", ca_file);
-        else
-            count++;
-    }
-
-    if (count == 0) {
-        rc = SSL_CTX_set_default_verify_paths(c->ctx);
-        if (rc != 1)
-            g_warning("loading ca certs from default location failed");
-        else
-            count++;
-    }
-
-    return count;
-}
-
-/**
- * spice_channel_get_error:
- * @channel:
- *
- * Retrieves the #GError currently set on channel, if the #SpiceChannel
- * is in error state and can provide additional error details.
- *
- * Returns: the pointer to the error, or %NULL
- * Since: 0.24
- **/
-const GError* spice_channel_get_error(SpiceChannel *self)
-{
-    SpiceChannelPrivate *c;
-
-    g_return_val_if_fail(SPICE_IS_CHANNEL(self), NULL);
-    c = self->priv;
-
-    return c->error;
-}
-
-/* coroutine context */
-static void *spice_channel_coroutine(void *data)
-{
-    SpiceChannel *channel = SPICE_CHANNEL(data);
-    SpiceChannelPrivate *c = channel->priv;
-    guint verify;
-    int rc, delay_val = 1;
-    /* When some other SSL/TLS version becomes obsolete, add it to this
-     * variable. */
-    long ssl_options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
-
-    CHANNEL_DEBUG(channel, "Started background coroutine %p", &c->coroutine);
-
-    if (spice_session_get_client_provided_socket(c->session)) {
-        if (c->fd < 0) {
-            g_critical("fd not provided!");
-            c->event = SPICE_CHANNEL_ERROR_CONNECT;
-            goto cleanup;
-        }
-
-        if (!(c->sock = g_socket_new_from_fd(c->fd, NULL))) {
-                CHANNEL_DEBUG(channel, "Failed to open socket from fd %d", c->fd);
-                c->event = SPICE_CHANNEL_ERROR_CONNECT;
-                goto cleanup;
-        }
-
-        g_socket_set_blocking(c->sock, FALSE);
-        g_socket_set_keepalive(c->sock, TRUE);
-        c->conn = g_socket_connection_factory_create_connection(c->sock);
-        goto connected;
-    }
-
-
-reconnect:
-    c->conn = spice_session_channel_open_host(c->session, channel, &c->tls, &c->error);
-    if (c->conn == NULL) {
-        if (!c->error && !c->tls) {
-            CHANNEL_DEBUG(channel, "trying with TLS port");
-            c->tls = true; /* FIXME: does that really work with provided fd */
-            goto reconnect;
-        } else {
-            CHANNEL_DEBUG(channel, "Connect error");
-            c->event = SPICE_CHANNEL_ERROR_CONNECT;
-            goto cleanup;
-        }
-    }
-    c->sock = g_object_ref(g_socket_connection_get_socket(c->conn));
-
-    if (c->tls) {
-        c->ctx = SSL_CTX_new(SSLv23_method());
-        if (c->ctx == NULL) {
-            g_critical("SSL_CTX_new failed");
-            c->event = SPICE_CHANNEL_ERROR_TLS;
-            goto cleanup;
-        }
-
-        SSL_CTX_set_options(c->ctx, ssl_options);
-
-        verify = spice_session_get_verify(c->session);
-        if (verify &
-            (SPICE_SESSION_VERIFY_SUBJECT | SPICE_SESSION_VERIFY_HOSTNAME)) {
-            rc = spice_channel_load_ca(channel);
-            if (rc == 0) {
-                g_warning("no cert loaded");
-                if (verify & SPICE_SESSION_VERIFY_PUBKEY) {
-                    g_warning("only pubkey active");
-                    verify = SPICE_SESSION_VERIFY_PUBKEY;
-                } else {
-                    c->event = SPICE_CHANNEL_ERROR_TLS;
-                    goto cleanup;
-                }
-            }
-        }
-
-        {
-            const gchar *ciphers = spice_session_get_ciphers(c->session);
-            if (ciphers != NULL) {
-                rc = SSL_CTX_set_cipher_list(c->ctx, ciphers);
-                if (rc != 1)
-                    g_warning("loading cipher list %s failed", ciphers);
-            }
-        }
-
-        c->ssl = SSL_new(c->ctx);
-        if (c->ssl == NULL) {
-            g_critical("SSL_new failed");
-            c->event = SPICE_CHANNEL_ERROR_TLS;
-            goto cleanup;
-        }
-
-
-        BIO *bio = bio_new_giostream(G_IO_STREAM(c->conn));
-        SSL_set_bio(c->ssl, bio, bio);
-
-        {
-            guint8 *pubkey;
-            guint pubkey_len;
-
-            spice_session_get_pubkey(c->session, &pubkey, &pubkey_len);
-            c->sslverify = spice_openssl_verify_new(c->ssl, verify,
-                spice_session_get_host(c->session),
-                (char*)pubkey, pubkey_len,
-                spice_session_get_cert_subject(c->session));
-        }
-
-ssl_reconnect:
-        rc = SSL_connect(c->ssl);
-        if (rc <= 0) {
-            rc = SSL_get_error(c->ssl, rc);
-            if (rc == SSL_ERROR_WANT_READ || rc == SSL_ERROR_WANT_WRITE) {
-                g_coroutine_socket_wait(&c->coroutine, c->sock, G_IO_OUT|G_IO_ERR|G_IO_HUP);
-                goto ssl_reconnect;
-            } else {
-                g_warning("%s: SSL_connect: %s",
-                          c->name, ERR_error_string(rc, NULL));
-                c->event = SPICE_CHANNEL_ERROR_TLS;
-                goto cleanup;
-            }
-        }
-    }
-
-connected:
-    c->has_error = FALSE;
-    c->in = g_io_stream_get_input_stream(G_IO_STREAM(c->conn));
-    c->out = g_io_stream_get_output_stream(G_IO_STREAM(c->conn));
-
-    rc = setsockopt(g_socket_get_fd(c->sock), IPPROTO_TCP, TCP_NODELAY,
-                    (const char*)&delay_val, sizeof(delay_val));
-    if ((rc != 0)
-#ifdef ENOTSUP
-        && (errno != ENOTSUP)
-#endif
-        ) {
-        g_warning("%s: could not set sockopt TCP_NODELAY: %s", c->name,
-                  strerror(errno));
-    }
-
-    spice_channel_send_link(channel);
-    if (!spice_channel_recv_link_hdr(channel) ||
-        !spice_channel_recv_link_msg(channel) ||
-        !spice_channel_recv_auth(channel))
-        goto cleanup;
-
-    while (spice_channel_iterate(channel))
-        ;
-
-cleanup:
-    CHANNEL_DEBUG(channel, "Coroutine exit %s", c->name);
-
-    spice_channel_reset(channel, FALSE);
-
-    if (c->state == SPICE_CHANNEL_STATE_RECONNECTING ||
-        c->state == SPICE_CHANNEL_STATE_SWITCHING) {
-        g_warn_if_fail(c->event == SPICE_CHANNEL_NONE);
-        channel_connect(channel, c->tls);
-        g_object_unref(channel);
-    } else
-        g_idle_add(spice_channel_delayed_unref, data);
-
-    /* Co-routine exits now - the SpiceChannel object may no longer exist,
-       so don't do anything else now unless you like SEGVs */
-    return NULL;
-}
-
-static gboolean connect_delayed(gpointer data)
-{
-    SpiceChannel *channel = data;
-    SpiceChannelPrivate *c = channel->priv;
-    struct coroutine *co;
-
-    CHANNEL_DEBUG(channel, "Open coroutine starting %p", channel);
-    c->connect_delayed_id = 0;
-
-    co = &c->coroutine.coroutine;
-
-    co->stack_size = 16 << 20; /* 16Mb */
-    co->entry = spice_channel_coroutine;
-    co->release = NULL;
-
-    coroutine_init(co);
-    coroutine_yieldto(co, channel);
-
-    return FALSE;
-}
-
-/* any context */
-static gboolean channel_connect(SpiceChannel *channel, gboolean tls)
-{
-    SpiceChannelPrivate *c = channel->priv;
-
-    g_return_val_if_fail(c != NULL, FALSE);
-
-    if (c->session == NULL || c->channel_type == -1 || c->channel_id == -1) {
-        /* unset properties or unknown channel type */
-        g_warning("%s: channel setup incomplete", __FUNCTION__);
-        return false;
-    }
-
-    c->state = SPICE_CHANNEL_STATE_CONNECTING;
-    c->tls = tls;
-
-    if (spice_session_get_client_provided_socket(c->session)) {
-        if (c->fd == -1) {
-            CHANNEL_DEBUG(channel, "requesting fd");
-            /* FIXME: no way for client to provide fd atm. */
-            /* It could either chain on parent channel.. */
-            /* or register migration channel on parent session, or ? */
-            g_signal_emit(channel, signals[SPICE_CHANNEL_OPEN_FD], 0, c->tls);
-            return true;
-        }
-    }
-
-    c->xmit_queue_blocked = FALSE;
-
-    g_return_val_if_fail(c->sock == NULL, FALSE);
-    g_object_ref(G_OBJECT(channel)); /* Unref'd when co-routine exits */
-
-    /* we connect in idle, to let previous coroutine exit, if present */
-    c->connect_delayed_id = g_idle_add(connect_delayed, channel);
-
-    return true;
-}
-
-/**
- * spice_channel_connect:
- * @channel:
- *
- * Connect the channel, using #SpiceSession connection informations
- *
- * Returns: %TRUE on success.
- **/
-gboolean spice_channel_connect(SpiceChannel *channel)
-{
-    g_return_val_if_fail(SPICE_IS_CHANNEL(channel), FALSE);
-    SpiceChannelPrivate *c = channel->priv;
-
-    if (c->state >= SPICE_CHANNEL_STATE_CONNECTING)
-        return TRUE;
-
-    g_return_val_if_fail(channel->priv->fd == -1, FALSE);
-
-    return channel_connect(channel, FALSE);
-}
-
-/**
- * spice_channel_open_fd:
- * @channel:
- * @fd: a file descriptor (socket) or -1.
- * request mechanism
- *
- * Connect the channel using @fd socket.
- *
- * If @fd is -1, a valid fd will be requested later via the
- * SpiceChannel::open-fd signal.
- *
- * Returns: %TRUE on success.
- **/
-gboolean spice_channel_open_fd(SpiceChannel *channel, int fd)
-{
-    SpiceChannelPrivate *c;
-
-    g_return_val_if_fail(SPICE_IS_CHANNEL(channel), FALSE);
-    g_return_val_if_fail(channel->priv != NULL, FALSE);
-    g_return_val_if_fail(channel->priv->fd == -1, FALSE);
-    g_return_val_if_fail(fd >= -1, FALSE);
-
-    c = channel->priv;
-    if (c->state > SPICE_CHANNEL_STATE_CONNECTING) {
-        g_warning("Invalid channel_connect state: %d", c->state);
-        return true;
-    }
-
-    c->fd = fd;
-
-    return channel_connect(channel, FALSE);
-}
-
-/* system or coroutine context */
-static void channel_reset(SpiceChannel *channel, gboolean migrating)
-{
-    SpiceChannelPrivate *c = channel->priv;
-
-    CHANNEL_DEBUG(channel, "channel reset");
-    if (c->connect_delayed_id) {
-        g_source_remove(c->connect_delayed_id);
-        c->connect_delayed_id = 0;
-    }
-
-#if HAVE_SASL
-    if (c->sasl_conn) {
-        sasl_dispose(&c->sasl_conn);
-        c->sasl_conn = NULL;
-        c->sasl_decoded_offset = c->sasl_decoded_length = 0;
-    }
-#endif
-
-    spice_openssl_verify_free(c->sslverify);
-    c->sslverify = NULL;
-
-    if (c->ssl) {
-        SSL_free(c->ssl);
-        c->ssl = NULL;
-    }
-
-    if (c->ctx) {
-        SSL_CTX_free(c->ctx);
-        c->ctx = NULL;
-    }
-
-    if (c->conn) {
-        g_object_unref(c->conn);
-        c->conn = NULL;
-    }
-
-    g_clear_object(&c->sock);
-
-    c->fd = -1;
-
-    c->auth_needs_username_and_password = FALSE;
-
-    g_free(c->peer_msg);
-    c->peer_msg = NULL;
-    c->peer_pos = 0;
-
-    STATIC_MUTEX_LOCK(c->xmit_queue_lock);
-    c->xmit_queue_blocked = TRUE; /* Disallow queuing new messages */
-    gboolean was_empty = g_queue_is_empty(&c->xmit_queue);
-    g_queue_foreach(&c->xmit_queue, (GFunc)spice_msg_out_unref, NULL);
-    g_queue_clear(&c->xmit_queue);
-    if (c->xmit_queue_wakeup_id) {
-        g_source_remove(c->xmit_queue_wakeup_id);
-        c->xmit_queue_wakeup_id = 0;
-    }
-    STATIC_MUTEX_UNLOCK(c->xmit_queue_lock);
-    spice_channel_flushed(channel, was_empty);
-
-    g_array_set_size(c->remote_common_caps, 0);
-    g_array_set_size(c->remote_caps, 0);
-    g_array_set_size(c->common_caps, 0);
-    /* Restore our default capabilities in case the channel gets re-used */
-    spice_channel_set_common_capability(channel, SPICE_COMMON_CAP_PROTOCOL_AUTH_SELECTION);
-    spice_channel_set_common_capability(channel, SPICE_COMMON_CAP_MINI_HEADER);
-    spice_channel_reset_capabilities(channel);
-
-    if (c->state == SPICE_CHANNEL_STATE_SWITCHING)
-        spice_session_set_migration_state(spice_channel_get_session(channel),
-                                          SPICE_SESSION_MIGRATION_NONE);
-}
-
-/* system or coroutine context */
-G_GNUC_INTERNAL
-void spice_channel_reset(SpiceChannel *channel, gboolean migrating)
-{
-    CHANNEL_DEBUG(channel, "reset %s", migrating ? "migrating" : "");
-    SPICE_CHANNEL_GET_CLASS(channel)->channel_reset(channel, migrating);
-}
-
-/**
- * spice_channel_disconnect:
- * @channel:
- * @reason: a channel event emitted on main context (or #SPICE_CHANNEL_NONE)
- *
- * Close the socket and reset connection specific data. Finally, emit
- * @reason #SpiceChannel::channel-event on main context if not
- * #SPICE_CHANNEL_NONE.
- **/
-void spice_channel_disconnect(SpiceChannel *channel, SpiceChannelEvent reason)
-{
-    SpiceChannelPrivate *c;
-
-    CHANNEL_DEBUG(channel, "channel disconnect %d", reason);
-
-    g_return_if_fail(SPICE_IS_CHANNEL(channel));
-    g_return_if_fail(channel->priv != NULL);
-
-    c = channel->priv;
-
-    if (c->state == SPICE_CHANNEL_STATE_UNCONNECTED)
-        return;
-
-    if (reason == SPICE_CHANNEL_SWITCHING)
-        c->state = SPICE_CHANNEL_STATE_SWITCHING;
-
-    c->has_error = TRUE; /* break the loop */
-
-    if (c->state == SPICE_CHANNEL_STATE_MIGRATING) {
-        c->state = SPICE_CHANNEL_STATE_READY;
-    } else
-        spice_channel_wakeup(channel, TRUE);
-
-    if (reason != SPICE_CHANNEL_NONE)
-        g_signal_emit(G_OBJECT(channel), signals[SPICE_CHANNEL_EVENT], 0, reason);
-}
-
-static gboolean test_capability(GArray *caps, guint32 cap)
-{
-    guint32 c, word_index = cap / 32;
-    gboolean ret;
-
-    if (caps == NULL)
-        return FALSE;
-
-    if (caps->len < word_index + 1)
-        return FALSE;
-
-    c = g_array_index(caps, guint32, word_index);
-    ret = (c & (1 << (cap % 32))) != 0;
-
-    SPICE_DEBUG("test cap %d in 0x%X: %s", cap, c, ret ? "yes" : "no");
-    return ret;
-}
-
-/**
- * spice_channel_test_capability:
- * @channel:
- * @cap:
- *
- * Test availability of remote "channel kind capability".
- *
- * Returns: %TRUE if @cap (channel kind capability) is available.
- **/
-gboolean spice_channel_test_capability(SpiceChannel *self, guint32 cap)
-{
-    SpiceChannelPrivate *c;
-
-    g_return_val_if_fail(SPICE_IS_CHANNEL(self), FALSE);
-
-    c = self->priv;
-    return test_capability(c->remote_caps, cap);
-}
-
-/**
- * spice_channel_test_common_capability:
- * @channel:
- * @cap:
- *
- * Test availability of remote "common channel capability".
- *
- * Returns: %TRUE if @cap (common channel capability) is available.
- **/
-gboolean spice_channel_test_common_capability(SpiceChannel *self, guint32 cap)
-{
-    SpiceChannelPrivate *c;
-
-    g_return_val_if_fail(SPICE_IS_CHANNEL(self), FALSE);
-
-    c = self->priv;
-    return test_capability(c->remote_common_caps, cap);
-}
-
-static void set_capability(GArray *caps, guint32 cap)
-{
-    guint word_index = cap / 32;
-
-    g_return_if_fail(caps != NULL);
-
-    if (caps->len <= word_index)
-        g_array_set_size(caps, word_index + 1);
-
-    g_array_index(caps, guint32, word_index) =
-        g_array_index(caps, guint32, word_index) | (1 << (cap % 32));
-}
-
-/**
- * spice_channel_set_capability:
- * @channel:
- * @cap: a capability
- *
- * Enable specific channel-kind capability.
- * Deprecated: 0.13: this function has been removed
- **/
-#undef spice_channel_set_capability
-void spice_channel_set_capability(SpiceChannel *channel, guint32 cap)
-{
-    SpiceChannelPrivate *c;
-
-    g_return_if_fail(SPICE_IS_CHANNEL(channel));
-
-    c = channel->priv;
-    set_capability(c->caps, cap);
-}
-
-G_GNUC_INTERNAL
-void spice_caps_set(GArray *caps, guint32 cap, const gchar *desc)
-{
-    g_return_if_fail(caps != NULL);
-    g_return_if_fail(desc != NULL);
-
-    if (g_strcmp0(g_getenv(desc), "0") == 0)
-        return;
-
-    set_capability(caps, cap);
-}
-
-G_GNUC_INTERNAL
-SpiceSession* spice_channel_get_session(SpiceChannel *channel)
-{
-    g_return_val_if_fail(SPICE_IS_CHANNEL(channel), NULL);
-
-    return channel->priv->session;
-}
-
-G_GNUC_INTERNAL
-enum spice_channel_state spice_channel_get_state(SpiceChannel *channel)
-{
-    g_return_val_if_fail(SPICE_IS_CHANNEL(channel),
-                         SPICE_CHANNEL_STATE_UNCONNECTED);
-
-    return channel->priv->state;
-}
-
-G_GNUC_INTERNAL
-void spice_channel_swap(SpiceChannel *channel, SpiceChannel *swap, gboolean swap_msgs)
-{
-    SpiceChannelPrivate *c = channel->priv;
-    SpiceChannelPrivate *s = swap->priv;
-
-    g_return_if_fail(c != NULL);
-    g_return_if_fail(s != NULL);
-
-    g_return_if_fail(s->session != NULL);
-    g_return_if_fail(s->sock != NULL);
-
-#define SWAP(Field) ({                          \
-    typeof (c->Field) Field = c->Field;         \
-    c->Field = s->Field;                        \
-    s->Field = Field;                           \
-})
-
-    /* TODO: split channel in 2 objects: a controller and a swappable
-       state object */
-    SWAP(sock);
-    SWAP(conn);
-    SWAP(in);
-    SWAP(out);
-    SWAP(ctx);
-    SWAP(ssl);
-    SWAP(sslverify);
-    SWAP(tls);
-    SWAP(use_mini_header);
-    if (swap_msgs) {
-        SWAP(xmit_queue);
-        SWAP(xmit_queue_blocked);
-        SWAP(in_serial);
-        SWAP(out_serial);
-    }
-    SWAP(caps);
-    SWAP(common_caps);
-    SWAP(remote_caps);
-    SWAP(remote_common_caps);
-#if HAVE_SASL
-    SWAP(sasl_conn);
-    SWAP(sasl_decoded);
-    SWAP(sasl_decoded_length);
-    SWAP(sasl_decoded_offset);
-#endif
-}
-
-/* coroutine context */
-static void spice_channel_handle_msg(SpiceChannel *channel, SpiceMsgIn *msg)
-{
-    SpiceChannelClass *klass = SPICE_CHANNEL_GET_CLASS(channel);
-    int type = spice_msg_in_type(msg);
-    spice_msg_handler handler;
-
-    g_return_if_fail(type < klass->handlers->len);
-    if (type > SPICE_MSG_BASE_LAST && channel->priv->disable_channel_msg)
-        return;
-
-    handler = g_array_index(klass->handlers, spice_msg_handler, type);
-    g_return_if_fail(handler != NULL);
-    handler(channel, msg);
-}
-
-static void spice_channel_reset_capabilities(SpiceChannel *channel)
-{
-    SpiceChannelPrivate *c = channel->priv;
-    g_array_set_size(c->caps, 0);
-
-    if (SPICE_CHANNEL_GET_CLASS(channel)->channel_reset_capabilities) {
-        SPICE_CHANNEL_GET_CLASS(channel)->channel_reset_capabilities(channel);
-    }
-}
-
-static void spice_channel_send_migration_handshake(SpiceChannel *channel)
-{
-    SpiceChannelPrivate *c = channel->priv;
-
-    if (SPICE_CHANNEL_GET_CLASS(channel)->channel_send_migration_handshake) {
-        SPICE_CHANNEL_GET_CLASS(channel)->channel_send_migration_handshake(channel);
-    } else {
-        c->state = SPICE_CHANNEL_STATE_MIGRATING;
-    }
-}
-
-/**
- * spice_channel_flush_async:
- * @channel: a #SpiceChannel
- * @cancellable: (allow-none): optional GCancellable object, %NULL to ignore
- * @callback: (scope async): callback to call when the request is satisfied
- * @user_data: (closure): the data to pass to callback function
- *
- * Forces an asynchronous write of all user-space buffered data for
- * the given channel.
- *
- * When the operation is finished callback will be called. You can
- * then call spice_channel_flush_finish() to get the result of the
- * operation.
- *
- * Since: 0.15
- **/
-void spice_channel_flush_async(SpiceChannel *self, GCancellable *cancellable,
-                               GAsyncReadyCallback callback, gpointer user_data)
-{
-    GSimpleAsyncResult *simple;
-    SpiceChannelPrivate *c;
-    gboolean was_empty;
-
-    g_return_if_fail(SPICE_IS_CHANNEL(self));
-    c = self->priv;
-
-    if (c->state != SPICE_CHANNEL_STATE_READY) {
-        g_simple_async_report_error_in_idle(G_OBJECT(self), callback, user_data,
-            SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
-            "The channel is not ready yet");
-        return;
-    }
-
-    simple = g_simple_async_result_new(G_OBJECT(self), callback, user_data,
-                                       spice_channel_flush_async);
-
-    STATIC_MUTEX_LOCK(c->xmit_queue_lock);
-    was_empty = g_queue_is_empty(&c->xmit_queue);
-    STATIC_MUTEX_UNLOCK(c->xmit_queue_lock);
-    if (was_empty) {
-        g_simple_async_result_set_op_res_gboolean(simple, TRUE);
-        g_simple_async_result_complete_in_idle(simple);
-        g_object_unref(simple);
-        return;
-    }
-
-    c->flushing = g_slist_append(c->flushing, simple);
-}
-
-/**
- * spice_channel_flush_finish:
- * @channel: a #SpiceChannel
- * @result: a #GAsyncResult
- * @error: a #GError location to store the error occurring, or %NULL
- * to ignore.
- *
- * Finishes flushing a channel.
- *
- * Returns: %TRUE if flush operation succeeded, %FALSE otherwise.
- * Since: 0.15
- **/
-gboolean spice_channel_flush_finish(SpiceChannel *self, GAsyncResult *result,
-                                    GError **error)
-{
-    GSimpleAsyncResult *simple;
-
-    g_return_val_if_fail(SPICE_IS_CHANNEL(self), FALSE);
-    g_return_val_if_fail(result != NULL, FALSE);
-
-    simple = (GSimpleAsyncResult *)result;
-
-    if (g_simple_async_result_propagate_error(simple, error))
-        return -1;
-
-    g_return_val_if_fail(g_simple_async_result_is_valid(result, G_OBJECT(self),
-                                                        spice_channel_flush_async), FALSE);
-
-    CHANNEL_DEBUG(self, "flushed finished!");
-    return g_simple_async_result_get_op_res_gboolean(simple);
-}
diff --git a/gtk/spice-channel.h b/gtk/spice-channel.h
deleted file mode 100644
index 7f132f6..0000000
--- a/gtk/spice-channel.h
+++ /dev/null
@@ -1,131 +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_CLIENT_CHANNEL_H__
-#define __SPICE_CLIENT_CHANNEL_H__
-
-G_BEGIN_DECLS
-
-#include <gio/gio.h>
-#include "spice-types.h"
-#include "spice-glib-enums.h"
-#include "spice-util.h"
-
-#define SPICE_TYPE_CHANNEL            (spice_channel_get_type ())
-#define SPICE_CHANNEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), SPICE_TYPE_CHANNEL, SpiceChannel))
-#define SPICE_CHANNEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SPICE_TYPE_CHANNEL, SpiceChannelClass))
-#define SPICE_IS_CHANNEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SPICE_TYPE_CHANNEL))
-#define SPICE_IS_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SPICE_TYPE_CHANNEL))
-#define SPICE_CHANNEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SPICE_TYPE_CHANNEL, SpiceChannelClass))
-
-typedef struct _SpiceMsgIn  SpiceMsgIn;
-typedef struct _SpiceMsgOut SpiceMsgOut;
-
-/**
- * SpiceChannelEvent:
- * @SPICE_CHANNEL_NONE: no event, or ignored event
- * @SPICE_CHANNEL_OPENED: connection is authentified and ready
- * @SPICE_CHANNEL_CLOSED: connection is closed normally (sent if channel was ready)
- * @SPICE_CHANNEL_ERROR_CONNECT: connection error
- * @SPICE_CHANNEL_ERROR_TLS: SSL error
- * @SPICE_CHANNEL_ERROR_LINK: error during link process
- * @SPICE_CHANNEL_ERROR_AUTH: authentication error
- * @SPICE_CHANNEL_ERROR_IO: IO error
- *
- * An event, emitted by #SpiceChannel::channel-event signal.
- **/
-typedef enum
-{
-    SPICE_CHANNEL_NONE = 0,
-    SPICE_CHANNEL_OPENED = 10,
-    SPICE_CHANNEL_SWITCHING,
-    SPICE_CHANNEL_CLOSED,
-    SPICE_CHANNEL_ERROR_CONNECT = 20,
-    SPICE_CHANNEL_ERROR_TLS,
-    SPICE_CHANNEL_ERROR_LINK,
-    SPICE_CHANNEL_ERROR_AUTH,
-    SPICE_CHANNEL_ERROR_IO,
-} SpiceChannelEvent;
-
-struct _SpiceChannel
-{
-    GObject parent;
-    SpiceChannelPrivate *priv;
-    /* Do not add fields to this struct */
-};
-
-struct _SpiceChannelClass
-{
-    GObjectClass parent_class;
-
-    /*< public >*/
-    /* signals, main context */
-    void (*channel_event)(SpiceChannel *channel, SpiceChannelEvent event);
-    void (*open_fd)(SpiceChannel *channel, int with_tls);
-
-    /*< private >*/
-    /* virtual methods, coroutine context */
-    void (*handle_msg)(SpiceChannel *channel, SpiceMsgIn *msg);
-    void (*channel_up)(SpiceChannel *channel);
-    void (*iterate_write)(SpiceChannel *channel);
-    void (*iterate_read)(SpiceChannel *channel);
-
-    /*< private >*/
-    /* virtual method, any context */
-    gpointer deprecated;
-    void (*channel_reset)(SpiceChannel *channel, gboolean migrating);
-    void (*channel_reset_capabilities)(SpiceChannel *channel);
-
-    /*< private >*/
-    /* virtual methods, coroutine context */
-    void (*channel_send_migration_handshake)(SpiceChannel *channel);
-
-    GArray                      *handlers;
-    /*
-     * If adding fields to this struct, remove corresponding
-     * amount of padding to avoid changing overall struct size
-     */
-    gchar _spice_reserved[SPICE_RESERVED_PADDING - 2 * sizeof(void *)];
-};
-
-GType spice_channel_get_type(void);
-
-typedef void (*spice_msg_handler)(SpiceChannel *channel, SpiceMsgIn *in);
-
-SpiceChannel *spice_channel_new(SpiceSession *s, int type, int id);
-gboolean spice_channel_connect(SpiceChannel *channel);
-gboolean spice_channel_open_fd(SpiceChannel *channel, int fd);
-void spice_channel_disconnect(SpiceChannel *channel, SpiceChannelEvent reason);
-gboolean spice_channel_test_capability(SpiceChannel *channel, guint32 cap);
-gboolean spice_channel_test_common_capability(SpiceChannel *channel, guint32 cap);
-void spice_channel_flush_async(SpiceChannel *channel, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data);
-gboolean spice_channel_flush_finish(SpiceChannel *channel, GAsyncResult *result, GError **error);
-#ifndef SPICE_DISABLE_DEPRECATED
-SPICE_DEPRECATED
-void spice_channel_set_capability(SpiceChannel *channel, guint32 cap);
-SPICE_DEPRECATED
-void spice_channel_destroy(SpiceChannel *channel);
-#endif
-
-const gchar* spice_channel_type_to_string(gint type);
-gint spice_channel_string_to_type(const gchar *str);
-
-const GError* spice_channel_get_error(SpiceChannel *channel);
-
-G_END_DECLS
-
-#endif /* __SPICE_CLIENT_CHANNEL_H__ */
diff --git a/gtk/spice-client-glib-usb-acl-helper.c b/gtk/spice-client-glib-usb-acl-helper.c
deleted file mode 100644
index bc09776..0000000
--- a/gtk/spice-client-glib-usb-acl-helper.c
+++ /dev/null
@@ -1,372 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2011,2012 Red Hat, Inc.
-   Copyright (C) 2009 Kay Sievers <kay.sievers at vrfy.org>
-
-   Red Hat Authors:
-   Hans de Goede <hdegoede at redhat.com>
-
-   This program is free software; you can redistribute it and/or
-   modify it under the terms of the GNU General Public License as published
-   by the Free Software Foundation; either version 2 of the License,
-   or (at your option) any later version.
-
-   This program 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
-   General Public License for more details.
-
-   You should have received a copy of the GNU General Public License along
-   with this program; if not, see <http://www.gnu.org/licenses/>.
-*/
-
-#include "config.h"
-
-#include <ctype.h>
-#include <errno.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <gio/gunixinputstream.h>
-#include <polkit/polkit.h>
-#include <acl/libacl.h>
-
-#include "glib-compat.h"
-
-#define FATAL_ERROR(...) \
-    do { \
-        /* We print the error both to stdout, for the app invoking us and \
-           stderr for the end user */ \
-        fprintf(stdout, "Error " __VA_ARGS__); \
-        fprintf(stderr, "spice-client-glib-usb-helper: Error " __VA_ARGS__); \
-        exit_status = 1; \
-        cleanup(); \
-    } while (0)
-
-#define ERROR(...) \
-    do { \
-        fprintf(stdout, __VA_ARGS__); \
-        cleanup(); \
-    } while (0)
-
-enum state {
-    STATE_WAITING_FOR_BUS_N_DEV,
-    STATE_WAITING_FOR_POL_KIT,
-    STATE_WAITING_FOR_STDIN_EOF,
-};
-
-static enum state state = STATE_WAITING_FOR_BUS_N_DEV;
-static int exit_status;
-static int busnum, devnum;
-static char path[PATH_MAX];
-static GMainLoop *loop;
-static GDataInputStream *stdin_stream;
-static GCancellable *polkit_cancellable;
-static PolkitSubject *subject;
-static PolkitAuthority *authority;
-
-/*
- * This function is a copy of the same function in udev, written by Kay
- * Sievers, you can find it in udev in extras/udev-acl/udev-acl.c
- */
-static int set_facl(const char* filename, uid_t uid, int add)
-{
-    int get;
-    acl_t acl;
-    acl_entry_t entry = NULL;
-    acl_entry_t e;
-    acl_permset_t permset;
-    int ret;
-
-    /* don't touch ACLs for root */
-    if (uid == 0)
-        return 0;
-
-    /* read current record */
-    acl = acl_get_file(filename, ACL_TYPE_ACCESS);
-    if (!acl)
-        return -1;
-
-    /* locate ACL_USER entry for uid */
-    get = acl_get_entry(acl, ACL_FIRST_ENTRY, &e);
-    while (get == 1) {
-        acl_tag_t t;
-
-        acl_get_tag_type(e, &t);
-        if (t == ACL_USER) {
-            uid_t *u;
-
-            u = (uid_t*)acl_get_qualifier(e);
-            if (u == NULL) {
-                ret = -1;
-                goto out;
-            }
-            if (*u == uid) {
-                entry = e;
-                acl_free(u);
-                break;
-            }
-            acl_free(u);
-        }
-
-        get = acl_get_entry(acl, ACL_NEXT_ENTRY, &e);
-    }
-
-    /* remove ACL_USER entry for uid */
-    if (!add) {
-        if (entry == NULL) {
-            ret = 0;
-            goto out;
-        }
-        acl_delete_entry(acl, entry);
-        goto update;
-    }
-
-    /* create ACL_USER entry for uid */
-    if (entry == NULL) {
-        ret = acl_create_entry(&acl, &entry);
-        if (ret != 0)
-            goto out;
-        acl_set_tag_type(entry, ACL_USER);
-        acl_set_qualifier(entry, &uid);
-    }
-
-    /* add permissions for uid */
-    acl_get_permset(entry, &permset);
-    acl_add_perm(permset, ACL_READ|ACL_WRITE);
-update:
-    /* update record */
-    acl_calc_mask(&acl);
-    ret = acl_set_file(filename, ACL_TYPE_ACCESS, acl);
-    if (ret != 0)
-        goto out;
-out:
-    acl_free(acl);
-    return ret;
-}
-
-static void cleanup(void)
-{
-    if (polkit_cancellable)
-        g_cancellable_cancel(polkit_cancellable);
-
-    if (state == STATE_WAITING_FOR_STDIN_EOF)
-        set_facl(path, getuid(), 0);
-
-    if (loop)
-        g_main_loop_quit(loop);
-}
-
-/* Not available in polkit < 0.101 */
-#if !HAVE_POLKIT_AUTHORIZATION_RESULT_GET_DISMISSED
-static gboolean
-polkit_authorization_result_get_dismissed(PolkitAuthorizationResult *result)
-{
-    gboolean ret;
-    PolkitDetails *details;
-
-    g_return_val_if_fail(POLKIT_IS_AUTHORIZATION_RESULT(result), FALSE);
-
-    ret = FALSE;
-    details = polkit_authorization_result_get_details(result);
-    if (details != NULL && polkit_details_lookup(details, "polkit.dismissed"))
-        ret = TRUE;
-
-    return ret;
-}
-#endif
-
-static void check_authorization_cb(PolkitAuthority *authority,
-                                   GAsyncResult *res, gpointer data)
-{
-    PolkitAuthorizationResult *result;
-    GError *err = NULL;
-    struct stat stat_buf;
-
-    g_clear_object(&polkit_cancellable);
-
-    result = polkit_authority_check_authorization_finish(authority, res, &err);
-    if (err) {
-        FATAL_ERROR("PoliciKit error: %s\n", err->message);
-        g_error_free(err);
-        return;
-    }
-
-    if (polkit_authorization_result_get_dismissed(result)) {
-        ERROR("CANCELED\n");
-        return;
-    }
-
-    if (!polkit_authorization_result_get_is_authorized(result)) {
-        ERROR("Not authorized\n");
-        return;
-    }
-
-    snprintf(path, PATH_MAX, "/dev/bus/usb/%03d/%03d", busnum, devnum);
-
-    if (stat(path, &stat_buf) != 0) {
-        FATAL_ERROR("statting %s: %s\n", path, strerror(errno));
-        return;
-    }
-    if (!S_ISCHR(stat_buf.st_mode)) {
-        FATAL_ERROR("%s is not a character device\n", path);
-        return;
-    }
-
-    if (set_facl(path, getuid(), 1)) {
-        FATAL_ERROR("setting facl: %s\n", strerror(errno));
-        return;
-    }
-
-    fprintf(stdout, "SUCCESS\n");
-    fflush(stdout);
-    state = STATE_WAITING_FOR_STDIN_EOF;
-}
-
-static void stdin_read_complete(GObject *src, GAsyncResult *res, gpointer data)
-{
-    char *s, *ep;
-    GError *err = NULL;
-    gsize len;
-
-    s = g_data_input_stream_read_line_finish(G_DATA_INPUT_STREAM(src), res,
-                                             &len, &err);
-    if (!s) {
-        if (err) {
-            FATAL_ERROR("Reading from stdin: %s\n", err->message);
-            g_error_free(err);
-            return;
-        }
-
-        switch (state) {
-        case STATE_WAITING_FOR_BUS_N_DEV:
-            FATAL_ERROR("EOF while waiting for bus and device num\n");
-            break;
-        case STATE_WAITING_FOR_POL_KIT:
-            ERROR("Cancelled while waiting for authorization\n");
-            break;
-        case STATE_WAITING_FOR_STDIN_EOF:
-            cleanup();
-            break;
-        }
-        return;
-    }
-
-    switch (state) {
-    case STATE_WAITING_FOR_BUS_N_DEV:
-        busnum = strtol(s, &ep, 10);
-        if (!isspace(*ep)) {
-            FATAL_ERROR("Invalid busnum / devnum: %s\n", s);
-            break;
-        }
-        devnum = strtol(ep, &ep, 10);
-        if (*ep != '\0') {
-            FATAL_ERROR("Invalid busnum / devnum: %s\n", s);
-            break;
-        }
-
-        /*
-         * The set_facl() call is a no-op for root, so no need to ask PolKit
-         * and then if ok call set_facl(), when called by a root process.
-         */
-        if (getuid() != 0) {
-            polkit_cancellable = g_cancellable_new();
-            polkit_authority_check_authorization(
-                authority, subject, "org.spice-space.lowlevelusbaccess", NULL,
-                POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION,
-                polkit_cancellable,
-                (GAsyncReadyCallback)check_authorization_cb, NULL);
-            state = STATE_WAITING_FOR_POL_KIT;
-        } else {
-            fprintf(stdout, "SUCCESS\n");
-            fflush(stdout);
-            state = STATE_WAITING_FOR_STDIN_EOF;
-        }
-
-        g_data_input_stream_read_line_async(stdin_stream, G_PRIORITY_DEFAULT,
-                                            NULL, stdin_read_complete, NULL);
-        break;
-    default:
-        FATAL_ERROR("Unexpected extra input in state %d: %s\n", state, s);
-    }
-    g_free(s);
-}
-
-/* Fix for polkit 0.97 and later */
-#if !HAVE_POLKIT_AUTHORITY_GET_SYNC
-static PolkitAuthority *
-polkit_authority_get_sync (GCancellable *cancellable, GError **error)
-{
-    PolkitAuthority *authority;
-
-    authority = polkit_authority_get ();
-    if (!authority)
-        g_set_error (error, 0, 0, "failed to get the PolicyKit authority");
-
-    return authority;
-}
-#endif
-
-#ifndef HAVE_CLEARENV
-extern char **environ;
-
-static int
-clearenv (void)
-{
-        if (environ != NULL)
-                environ[0] = NULL;
-        return 0;
-}
-#endif
-
-int main(void)
-{
-    pid_t parent_pid;
-    GInputStream *stdin_unix_stream;
-
-  /* Nuke the environment to get a well-known and sanitized
-   * environment to avoid attacks via e.g. the DBUS_SYSTEM_BUS_ADDRESS
-   * environment variable and similar.
-   */
-    if (clearenv () != 0) {
-        FATAL_ERROR("Error clearing environment: %s\n", g_strerror (errno));
-        return 1;
-    }
-
-#if !GLIB_CHECK_VERSION(2,36,0)
-    g_type_init();
-#endif
-
-    loop = g_main_loop_new(NULL, FALSE);
-
-    authority = polkit_authority_get_sync(NULL, NULL);
-    parent_pid = getppid ();
-    if (parent_pid == 1) {
-            FATAL_ERROR("Parent process was reaped by init(1)\n");
-            return 1;
-    }
-    /* Do what pkexec does */
-    subject = polkit_unix_process_new_for_owner(parent_pid, 0, getuid ());
-
-    stdin_unix_stream = g_unix_input_stream_new(STDIN_FILENO, 0);
-    stdin_stream = g_data_input_stream_new(stdin_unix_stream);
-    g_data_input_stream_set_newline_type(stdin_stream,
-                                         G_DATA_STREAM_NEWLINE_TYPE_LF);
-    g_clear_object(&stdin_unix_stream);
-    g_data_input_stream_read_line_async(stdin_stream, G_PRIORITY_DEFAULT, NULL,
-                                        stdin_read_complete, NULL);
-
-    g_main_loop_run(loop);
-
-    if (polkit_cancellable)
-        g_clear_object(&polkit_cancellable);
-    g_object_unref(stdin_stream);
-    g_object_unref(authority);
-    g_object_unref(subject);
-    g_main_loop_unref(loop);
-
-    return exit_status;
-}
diff --git a/gtk/spice-client-gtk-manual.defs b/gtk/spice-client-gtk-manual.defs
deleted file mode 100644
index 9631b74..0000000
--- a/gtk/spice-client-gtk-manual.defs
+++ /dev/null
@@ -1,117 +0,0 @@
-(define-method set_display
-  (of-object "SpiceMainChannel")
-  (c-name "spice_main_set_display")
-  (return-type "none")
-  (parameters
-    '("int" "id")
-    '("int" "x")
-    '("int" "y")
-    '("int" "width")
-    '("int" "height")
-  )
-)
-
-(define-method clipboard_grab
-  (of-object "SpiceMainChannel")
-  (c-name "spice_main_clipboard_grab")
-  (return-type "none")
-  (parameters
-    '("int*" "types")
-    '("int" "ntypes")
-  )
-)
-
-(define-method clipboard_release
-  (of-object "SpiceMainChannel")
-  (c-name "spice_main_clipboard_release")
-  (return-type "none")
-)
-
-(define-method motion
-  (of-object "SpiceInputsChannel")
-  (c-name "spice_inputs_motion")
-  (return-type "none")
-  (parameters
-    '("gint" "dx")
-    '("gint" "dy")
-    '("gint" "button_state")
-  )
-)
-
-(define-method position
-  (of-object "SpiceInputsChannel")
-  (c-name "spice_inputs_position")
-  (return-type "none")
-  (parameters
-    '("gint" "x")
-    '("gint" "y")
-    '("gint" "display")
-    '("gint" "button_state")
-  )
-)
-
-(define-method button_press
-  (of-object "SpiceInputsChannel")
-  (c-name "spice_inputs_button_press")
-  (return-type "none")
-  (parameters
-    '("gint" "button")
-    '("gint" "button_state")
-  )
-)
-
-(define-method button_release
-  (of-object "SpiceInputsChannel")
-  (c-name "spice_inputs_button_release")
-  (return-type "none")
-  (parameters
-    '("gint" "button")
-    '("gint" "button_state")
-  )
-)
-
-(define-method key_press
-  (of-object "SpiceInputsChannel")
-  (c-name "spice_inputs_key_press")
-  (return-type "none")
-  (parameters
-    '("guint" "keyval")
-  )
-)
-
-(define-method key_release
-  (of-object "SpiceInputsChannel")
-  (c-name "spice_inputs_key_release")
-  (return-type "none")
-  (parameters
-    '("guint" "keyval")
-  )
-)
-
-(define-method set_key_locks
-  (of-object "SpiceInputsChannel")
-  (c-name "spice_inputs_set_key_locks")
-  (return-type "none")
-  (parameters
-    '("guint" "locks")
-  )
-)
-
-(define-enum ClientError
-  (in-module "Spice")
-  (c-name "SpiceClientError")
-  (values
-    '("failed" "SPICE_CLIENT_ERROR_FAILED")
-  )
-)
-
-(define-function spice_audio_new
-  (c-name "spice_audio_new")
-  (is-constructor-of "SpiceAudio")
-  (return-type "SpiceAudio*")
-  (parameters
-    '("SpiceSession*" "session")
-    '("GMainContext*" "context")
-    '("const-char*" "name")
-  )
-)
diff --git a/gtk/spice-client-gtk-module.c b/gtk/spice-client-gtk-module.c
deleted file mode 100644
index b82f1e3..0000000
--- a/gtk/spice-client-gtk-module.c
+++ /dev/null
@@ -1,45 +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 <pygobject.h>
-
-void spice_register_classes (PyObject *d);
-void spice_add_constants(PyObject *module, const gchar *strip_prefix);
-extern PyMethodDef spice_functions[];
-
-DL_EXPORT(void) initSpiceClientGtk(void)
-{
-    PyObject *m, *d;
-
-    init_pygobject();
-
-    m = Py_InitModule("SpiceClientGtk", spice_functions);
-    if (PyErr_Occurred())
-        Py_FatalError("can't init module");
-
-    d = PyModule_GetDict(m);
-    if (PyErr_Occurred())
-        Py_FatalError("can't get dict");
-
-    spice_register_classes(d);
-    spice_add_constants(m, "SPICE_");
-
-    if (PyErr_Occurred()) {
-        Py_FatalError("can't initialise module SpiceClientGtk");
-    }
-}
diff --git a/gtk/spice-client-gtk.override b/gtk/spice-client-gtk.override
deleted file mode 100644
index 41aeee3..0000000
--- a/gtk/spice-client-gtk.override
+++ /dev/null
@@ -1,171 +0,0 @@
-%%
-headers
-#include <Python.h>
-#include "pygobject.h"
-#include "spice-common.h"
-#include "spice-widget.h"
-#include "spice-gtk-session.h"
-#include "spice-audio.h"
-#include "usb-device-widget.h"
-%%
-modulename spice_client_gtk
-%%
-import gobject.GObject as PyGObject_Type
-import gtk.DrawingArea as PyGtkDrawingArea_Type
-import gtk.Widget as PyGtkWidget_Type
-import gtk.VBox as PyGtkVBox_Type
-%%
-ignore-glob
-  *_get_type
-%%
-%%
-override spice_display_send_keys kwargs
-static PyObject*
-_wrap_spice_display_send_keys(PyGObject *self,
-                            PyObject *args, PyObject *kwargs)
-{
-    static char *kwlist[] = {"keys", "kind", NULL};
-    PyObject *keyList;
-    int kind = SPICE_DISPLAY_KEY_EVENT_CLICK;
-    int i, len;
-    guint *keys;
-
-    if (!PyArg_ParseTupleAndKeywords(args, kwargs,
-                                     "O|I:SpiceDisplay.send_keys", kwlist,
-                                     &keyList, &kind))
-	return NULL;
-
-    if (!PyList_Check(keyList))
-	return NULL;
-
-    len = PyList_Size(keyList);
-    keys = g_malloc0(sizeof(guint)*len);
-
-    for (i = 0 ; i < len ; i++) {
-        PyObject *val;
-        char *sym;
-        val = PyList_GetItem(keyList, i);
-        sym = PyString_AsString(val);
-        if (!sym) {
-            g_free(keys);
-	    return NULL;
-        }
-        keys[i] = gdk_keyval_from_name(sym);
-    }
-
-    spice_display_send_keys(SPICE_DISPLAY(self->obj), keys, len, kind);
-    g_free(keys);
-
-    Py_INCREF(Py_None);
-    return Py_None;
-}
-%%
-override spice_display_get_grab_keys kwargs
-static PyObject*
-_wrap_spice_display_get_grab_keys(PyGObject *self,
-                            PyObject *args, PyObject *kwargs)
-{
-    SpiceGrabSequence *seq;
-    PyObject *keyList;
-    int i;
-
-    seq = spice_display_get_grab_keys(SPICE_DISPLAY(self->obj));
-
-    keyList = PyList_New(0);
-    for (i = 0 ; i < seq->nkeysyms ; i++)
-       PyList_Append(keyList, PyInt_FromLong(seq->keysyms[i]));
-
-    return keyList;
-}
-%%
-override spice_display_set_grab_keys kwargs
-static PyObject*
-_wrap_spice_display_set_grab_keys(PyGObject *self,
-                            PyObject *args, PyObject *kwargs)
-{
-    static char *kwlist[] = {"keys", NULL};
-    PyObject *keyList;
-    int i;
-    guint nkeysyms;
-    guint *keysyms;
-    SpiceGrabSequence *seq;
-
-    if (!PyArg_ParseTupleAndKeywords(args, kwargs,
-                                     "O|I:SpiceDisplay.set_grab_keys", kwlist,
-                                     &keyList))
-        return NULL;
-
-    if (!PyList_Check(keyList))
-        return NULL;
-
-    nkeysyms = PyList_Size(keyList);
-    keysyms = g_new0(guint, nkeysyms);
-
-    for (i = 0 ; i < nkeysyms ; i++) {
-        PyObject *val = PyList_GetItem(keyList, i);
-        keysyms[i] = (guint)PyInt_AsLong(val);
-    }
-
-    seq = spice_grab_sequence_new(nkeysyms, keysyms);
-    g_free(keysyms);
-
-    spice_display_set_grab_keys(SPICE_DISPLAY(self->obj), seq);
-
-    spice_grab_sequence_free(seq);
-
-    Py_INCREF(Py_None);
-    return Py_None;
-}
-%%
-override spice_session_get_channels
-static PyObject*
-_wrap_spice_session_get_channels(PyGObject *self,
-                                 PyObject *args, PyObject *kwargs)
-{
-    PyObject *py_list;
-    GList *list, *tmp;
-    PyObject *chann;
-
-    list = spice_session_get_channels(SPICE_SESSION(self->obj));
-
-    if ((py_list = PyList_New(0)) == NULL) {
-        return NULL;
-    }
-    for (tmp = list; tmp != NULL; tmp = tmp->next) {
-        chann = pygobject_new(G_OBJECT(tmp->data));
-        if (chann == NULL) {
-            Py_DECREF(py_list);
-            return NULL;
-        }
-        PyList_Append(py_list, chann);
-        Py_DECREF(chann);
-    }
-    return py_list;
-}
-%%
-override spice_audio_new
-static int
-_wrap_spice_audio_new(PyGObject *self,
-                      PyObject *args, PyObject *kwargs)
-{
-    static char *kwlist[] = {"session", "context", "name", NULL};
-    PyGObject *session = NULL;
-    PyObject *py_context = NULL;
-    char *name = NULL;
-
-    if (!PyArg_ParseTupleAndKeywords(args, kwargs,
-                                     "O!|Os:SpiceAudio", kwlist,
-                                     &PySpiceSession_Type, &session,
-                                     &py_context, &name))
-	return -1;
-
-    self->obj = (GObject *)spice_audio_new(SPICE_SESSION(session->obj), NULL, NULL);
-
-    if (!self->obj) {
-        PyErr_SetString(PyExc_RuntimeError, "could not create SpiceAudio object");
-        return -1;
-    }
-    pygobject_register_wrapper((PyObject *)self);
-    return 0;
-
-}
diff --git a/gtk/spice-client.c b/gtk/spice-client.c
deleted file mode 100644
index 5fd511f..0000000
--- a/gtk/spice-client.c
+++ /dev/null
@@ -1,27 +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.h>
-
-#include "spice-client.h"
-
-GQuark spice_client_error_quark(void)
-{
-    return g_quark_from_static_string("spice-client-error-quark");
-}
diff --git a/gtk/spice-client.h b/gtk/spice-client.h
deleted file mode 100644
index c2474d1..0000000
--- a/gtk/spice-client.h
+++ /dev/null
@@ -1,79 +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_CLIENT_CLIENT_H__
-#define __SPICE_CLIENT_CLIENT_H__
-
-/* glib */
-#include <glib.h>
-#include <glib-object.h>
-
-/* spice-protocol */
-#include <spice/enums.h>
-#include <spice/protocol.h>
-
-/* spice/gtk */
-#include "spice-types.h"
-#include "spice-session.h"
-#include "spice-channel.h"
-#include "spice-option.h"
-#include "spice-uri.h"
-#include "spice-version.h"
-
-#include "channel-main.h"
-#include "channel-display.h"
-#include "channel-cursor.h"
-#include "channel-inputs.h"
-#include "channel-playback.h"
-#include "channel-record.h"
-#include "channel-smartcard.h"
-#include "channel-usbredir.h"
-#include "channel-port.h"
-#include "channel-webdav.h"
-
-#include "smartcard-manager.h"
-#include "usb-device-manager.h"
-#include "spice-audio.h"
-
-G_BEGIN_DECLS
-
-#define SPICE_CLIENT_ERROR spice_client_error_quark()
-
-/**
- * SpiceClientError:
- * @SPICE_CLIENT_ERROR_FAILED: generic error code
- * @SPICE_CLIENT_USB_DEVICE_REJECTED: usb device rejected by host
- * @SPICE_CLIENT_USB_DEVICE_LOST: usb device disconnected (fatal IO error)
- * @SPICE_CLIENT_ERROR_AUTH_NEEDS_PASSWORD: password is required
- * @SPICE_CLIENT_ERROR_AUTH_NEEDS_PASSWORD_AND_USERNAME: password and username are required
- *
- * Error codes returned by spice-client API.
- */
-typedef enum
-{
-    SPICE_CLIENT_ERROR_FAILED,
-    SPICE_CLIENT_USB_DEVICE_REJECTED,
-    SPICE_CLIENT_USB_DEVICE_LOST,
-    SPICE_CLIENT_ERROR_AUTH_NEEDS_PASSWORD,
-    SPICE_CLIENT_ERROR_AUTH_NEEDS_PASSWORD_AND_USERNAME,
-} SpiceClientError;
-
-GQuark spice_client_error_quark(void);
-
-G_END_DECLS
-
-#endif /* __SPICE_CLIENT_CLIENT_H__ */
diff --git a/gtk/spice-cmdline.c b/gtk/spice-cmdline.c
deleted file mode 100644
index 8619b57..0000000
--- a/gtk/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.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/gtk/spice-cmdline.h b/gtk/spice-cmdline.h
deleted file mode 100644
index 11a8086..0000000
--- a/gtk/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/gtk/spice-common.h b/gtk/spice-common.h
deleted file mode 100644
index 8554f4c..0000000
--- a/gtk/spice-common.h
+++ /dev/null
@@ -1,36 +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_COMMON_H_
-# define SPICE_COMMON_H_
-
-/* system */
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdbool.h>
-#include <string.h>
-#include <unistd.h>
-#include <errno.h>
-#include <inttypes.h>
-
-#include "common/mem.h"
-#include "common/messages.h"
-#include "common/marshaller.h"
-
-#include "spice-util.h"
-
-#endif // SPICE_COMMON_H_
diff --git a/gtk/spice-glib-sym-file b/gtk/spice-glib-sym-file
deleted file mode 100644
index 3a8da93..0000000
--- a/gtk/spice-glib-sym-file
+++ /dev/null
@@ -1,111 +0,0 @@
-spice_audio_get
-spice_audio_get_type
-spice_audio_new
-spice_channel_connect
-spice_channel_destroy
-spice_channel_disconnect
-spice_channel_event_get_type
-spice_channel_flush_async
-spice_channel_flush_finish
-spice_channel_get_error
-spice_channel_get_type
-spice_channel_new
-spice_channel_open_fd
-spice_channel_set_capability
-spice_channel_string_to_type
-spice_channel_test_capability
-spice_channel_test_common_capability
-spice_channel_type_to_string
-spice_client_error_quark
-spice_cursor_channel_get_type
-spice_display_channel_get_type
-spice_display_get_primary
-spice_get_option_group
-spice_g_signal_connect_object
-spice_inputs_button_press
-spice_inputs_button_release
-spice_inputs_channel_get_type
-spice_inputs_key_press
-spice_inputs_key_press_and_release
-spice_inputs_key_release
-spice_inputs_lock_get_type
-spice_inputs_motion
-spice_inputs_position
-spice_inputs_set_key_locks
-spice_main_agent_test_capability
-spice_main_channel_get_type
-spice_main_clipboard_grab
-spice_main_clipboard_notify
-spice_main_clipboard_release
-spice_main_clipboard_request
-spice_main_clipboard_selection_grab
-spice_main_clipboard_selection_notify
-spice_main_clipboard_selection_release
-spice_main_clipboard_selection_request
-spice_main_file_copy_async
-spice_main_file_copy_finish
-spice_main_send_monitor_config
-spice_main_set_display
-spice_main_set_display_enabled
-spice_main_update_display
-spice_playback_channel_get_type
-spice_playback_channel_set_delay
-spice_port_channel_get_type
-spice_port_event
-spice_port_write_async
-spice_port_write_finish
-spice_record_channel_get_type
-spice_record_send_data
-spice_session_connect
-spice_session_disconnect
-spice_session_get_channels
-spice_session_get_proxy_uri
-spice_session_get_read_only
-spice_session_get_type
-spice_session_has_channel_type
-spice_session_is_for_migration
-spice_session_migration_get_type
-spice_session_new
-spice_session_open_fd
-spice_session_verify_get_type
-spice_set_session_option
-spice_smartcard_channel_get_type
-spice_smartcard_manager_get
-spice_smartcard_manager_get_readers
-spice_smartcard_manager_get_type
-spice_smartcard_manager_insert_card
-spice_smartcard_manager_remove_card
-spice_smartcard_reader_get_type
-spice_smartcard_reader_insert_card
-spice_smartcard_reader_is_software
-spice_smartcard_reader_remove_card
-spice_uri_get_hostname
-spice_uri_get_password
-spice_uri_get_port
-spice_uri_get_scheme
-spice_uri_get_type
-spice_uri_get_user
-spice_uri_set_hostname
-spice_uri_set_password
-spice_uri_set_port
-spice_uri_set_scheme
-spice_uri_set_user
-spice_uri_to_string
-spice_usb_device_get_description
-spice_usb_device_get_libusb_device
-spice_usb_device_get_type
-spice_usb_device_manager_can_redirect_device
-spice_usb_device_manager_connect_device_async
-spice_usb_device_manager_connect_device_finish
-spice_usb_device_manager_disconnect_device
-spice_usb_device_manager_get
-spice_usb_device_manager_get_devices
-spice_usb_device_manager_get_devices_with_filter
-spice_usb_device_manager_get_type
-spice_usb_device_manager_is_device_connected
-spice_usbredir_channel_get_type
-spice_util_get_debug
-spice_util_get_version_string
-spice_util_set_debug
-spice_uuid_to_string
-spice_webdav_channel_get_type
diff --git a/gtk/spice-grabsequence.c b/gtk/spice-grabsequence.c
deleted file mode 100644
index 39adfb0..0000000
--- a/gtk/spice-grabsequence.c
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * GTK VNC Widget
- *
- * Copyright (C) 2010 Daniel P. Berrange <dan at berrange.com>
- *
- * 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.0 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, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
- */
-
-#include "config.h"
-
-#include <string.h>
-#include <gdk/gdk.h>
-
-#include "spice-grabsequence.h"
-
-GType spice_grab_sequence_get_type(void)
-{
-	static GType grab_sequence_type = 0;
-	static volatile gsize grab_sequence_type_volatile;
-
-	if (g_once_init_enter(&grab_sequence_type_volatile)) {
-		grab_sequence_type = g_boxed_type_register_static
-			("SpiceGrabSequence",
-			 (GBoxedCopyFunc)spice_grab_sequence_copy,
-			 (GBoxedFreeFunc)spice_grab_sequence_free);
-		g_once_init_leave(&grab_sequence_type_volatile,
-				  grab_sequence_type);
-	}
-
-	return grab_sequence_type;
-}
-
-
-/**
- * spice_grab_sequence_new:
- * @nkeysyms: length of @keysyms
- * @keysyms: (array length=nkeysyms): the keysym values
- *
- * Creates a new grab sequence from a list of keysym values
- *
- * Returns: (transfer full): a new grab sequence object
- */
-SpiceGrabSequence *spice_grab_sequence_new(guint nkeysyms, guint *keysyms)
-{
-	SpiceGrabSequence *sequence;
-
-	sequence = g_slice_new0(SpiceGrabSequence);
-	sequence->nkeysyms = nkeysyms;
-	sequence->keysyms = g_new0(guint, nkeysyms);
-	memcpy(sequence->keysyms, keysyms, sizeof(guint)*nkeysyms);
-
-	return sequence;
-}
-
-
-/**
- * spice_grab_sequence_new_from_string:
- * @str: a string of '+' seperated key names (ex: "Control_L+Alt_L")
- *
- * Returns: a new #SpiceGrabSequence.
- **/
-SpiceGrabSequence *spice_grab_sequence_new_from_string(const gchar *str)
-{
-	gchar **keysymstr;
-	int i;
-	SpiceGrabSequence *sequence;
-
-	sequence = g_slice_new0(SpiceGrabSequence);
-
-	keysymstr = g_strsplit(str, "+", 5);
-
-	sequence->nkeysyms = 0;
-	while (keysymstr[sequence->nkeysyms])
-		sequence->nkeysyms++;
-
-	sequence->keysyms = g_new0(guint, sequence->nkeysyms);
-	for (i = 0 ; i < sequence->nkeysyms ; i++) {
-		sequence->keysyms[i] =
-			(guint)gdk_keyval_from_name(keysymstr[i]);
-                if (sequence->keysyms[i] == 0) {
-                        g_critical("Invalid key: %s", keysymstr[i]);
-                }
-        }
-	g_strfreev(keysymstr);
-
-	return sequence;
-
-}
-
-
-/**
- * spice_grab_sequence_copy:
- * @sequence: sequence to copy
- *
- * Returns: (transfer full): a copy of @sequence
- **/
-SpiceGrabSequence *spice_grab_sequence_copy(SpiceGrabSequence *srcSequence)
-{
-	SpiceGrabSequence *sequence;
-
-	sequence = g_slice_dup(SpiceGrabSequence, srcSequence);
-	sequence->keysyms = g_new0(guint, srcSequence->nkeysyms);
-	memcpy(sequence->keysyms, srcSequence->keysyms,
-	       sizeof(guint) * sequence->nkeysyms);
-
-	return sequence;
-}
-
-
-/**
- * spice_grab_sequence_free:
- * @sequence:
- *
- * Free @sequence.
- **/
-void spice_grab_sequence_free(SpiceGrabSequence *sequence)
-{
-	g_free(sequence->keysyms);
-	g_slice_free(SpiceGrabSequence, sequence);
-}
-
-
-/**
- * spice_grab_sequence_as_string:
- * @sequence:
- *
- * Returns: (transfer full): a newly allocated string representing the key sequence
- **/
-gchar *spice_grab_sequence_as_string(SpiceGrabSequence *sequence)
-{
-	GString *str = g_string_new("");
-	int i;
-
-	for (i = 0 ; i < sequence->nkeysyms ; i++) {
-		if (i > 0)
-			g_string_append_c(str, '+');
-		g_string_append(str, gdk_keyval_name(sequence->keysyms[i]));
-	}
-
-	return g_string_free(str, FALSE);
-
-}
-
-
-/*
- * Local variables:
- *  c-indent-level: 8
- *  c-basic-offset: 8
- *  tab-width: 8
- * End:
- */
diff --git a/gtk/spice-grabsequence.h b/gtk/spice-grabsequence.h
deleted file mode 100644
index fe58fc1..0000000
--- a/gtk/spice-grabsequence.h
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * GTK VNC Widget
- *
- * Copyright (C) 2006  Anthony Liguori <anthony at codemonkey.ws>
- * Copyright (C) 2009-2010 Daniel P. Berrange <dan at berrange.com>
- *
- * 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.0 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, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
- */
-
-#ifndef SPICE_GRAB_SEQUENCE_H
-#define SPICE_GRAB_SEQUENCE_H
-
-#include <glib.h>
-#include <glib-object.h>
-
-G_BEGIN_DECLS
-
-#define SPICE_TYPE_GRAB_SEQUENCE            (spice_grab_sequence_get_type ())
-
-typedef struct _SpiceGrabSequence SpiceGrabSequence;
-
-struct _SpiceGrabSequence {
-        /*< private >*/
-	guint nkeysyms;
-	guint *keysyms;
-
-	/* Do not add fields to this struct */
-};
-
-GType spice_grab_sequence_get_type(void);
-
-SpiceGrabSequence *spice_grab_sequence_new(guint nkeysyms, guint *keysyms);
-SpiceGrabSequence *spice_grab_sequence_new_from_string(const gchar *str);
-SpiceGrabSequence *spice_grab_sequence_copy(SpiceGrabSequence *sequence);
-void spice_grab_sequence_free(SpiceGrabSequence *sequence);
-gchar *spice_grab_sequence_as_string(SpiceGrabSequence *sequence);
-
-
-G_END_DECLS
-
-#endif /* SPICE_GRAB_SEQUENCE_H */
-
-/*
- * Local variables:
- *  c-indent-level: 8
- *  c-basic-offset: 8
- *  tab-width: 8
- * End:
- */
diff --git a/gtk/spice-gstaudio.c b/gtk/spice-gstaudio.c
deleted file mode 100644
index 1623421..0000000
--- a/gtk/spice-gstaudio.c
+++ /dev/null
@@ -1,739 +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 <gst/gst.h>
-#include <gst/app/gstappsrc.h>
-#include <gst/app/gstappsink.h>
-#include <gst/audio/streamvolume.h>
-
-#include "spice-gstaudio.h"
-#include "spice-common.h"
-#include "spice-session.h"
-#include "spice-util.h"
-
-#define SPICE_GSTAUDIO_GET_PRIVATE(obj)                                  \
-    (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_GSTAUDIO, SpiceGstaudioPrivate))
-
-G_DEFINE_TYPE(SpiceGstaudio, spice_gstaudio, SPICE_TYPE_AUDIO)
-
-struct stream {
-    GstElement              *pipe;
-    GstElement              *src;
-    GstElement              *sink;
-    guint                   rate;
-    guint                   channels;
-};
-
-struct _SpiceGstaudioPrivate {
-    SpiceChannel            *pchannel;
-    SpiceChannel            *rchannel;
-    struct stream           playback;
-    struct stream           record;
-    guint                   mmtime_id;
-};
-
-static gboolean connect_channel(SpiceAudio *audio, SpiceChannel *channel);
-static void channel_weak_notified(gpointer data, GObject *where_the_object_was);
-static void spice_gstaudio_get_playback_volume_info_async(SpiceAudio *audio,
-        GCancellable *cancellable, SpiceMainChannel *main_channel,
-        GAsyncReadyCallback callback, gpointer user_data);
-static gboolean spice_gstaudio_get_playback_volume_info_finish(SpiceAudio *audio,
-        GAsyncResult *res, gboolean *mute, guint8 *nchannels, guint16 **volume, GError **error);
-static void spice_gstaudio_get_record_volume_info_async(SpiceAudio *audio,
-        GCancellable *cancellable, SpiceMainChannel *main_channel,
-        GAsyncReadyCallback callback, gpointer user_data);
-static gboolean spice_gstaudio_get_record_volume_info_finish(SpiceAudio *audio,
-        GAsyncResult *res, gboolean *mute, guint8 *nchannels, guint16 **volume, GError **error);
-
-static void spice_gstaudio_finalize(GObject *obj)
-{
-    G_OBJECT_CLASS(spice_gstaudio_parent_class)->finalize(obj);
-}
-
-void stream_dispose(struct stream *s)
-{
-    if (s->pipe) {
-        gst_element_set_state(s->pipe, GST_STATE_NULL);
-        gst_object_unref(s->pipe);
-        s->pipe = NULL;
-    }
-
-    if (s->src) {
-        gst_object_unref(s->src);
-        s->src = NULL;
-    }
-
-    if (s->sink) {
-        gst_object_unref(s->sink);
-        s->sink = NULL;
-    }
-}
-
-static void spice_gstaudio_dispose(GObject *obj)
-{
-    SpiceGstaudio *gstaudio = SPICE_GSTAUDIO(obj);
-    SpiceGstaudioPrivate *p;
-    SPICE_DEBUG("%s", __FUNCTION__);
-    p = gstaudio->priv;
-
-    stream_dispose(&p->playback);
-    stream_dispose(&p->record);
-
-    if (p->pchannel)
-        g_object_weak_unref(G_OBJECT(p->pchannel), channel_weak_notified, gstaudio);
-    p->pchannel = NULL;
-
-    if (p->rchannel)
-        g_object_weak_unref(G_OBJECT(p->rchannel), channel_weak_notified, gstaudio);
-    p->rchannel = NULL;
-
-    if (G_OBJECT_CLASS(spice_gstaudio_parent_class)->dispose)
-        G_OBJECT_CLASS(spice_gstaudio_parent_class)->dispose(obj);
-}
-
-static void spice_gstaudio_init(SpiceGstaudio *gstaudio)
-{
-    gstaudio->priv = SPICE_GSTAUDIO_GET_PRIVATE(gstaudio);
-}
-
-static void spice_gstaudio_class_init(SpiceGstaudioClass *klass)
-{
-    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
-    SpiceAudioClass *audio_class = SPICE_AUDIO_CLASS(klass);
-
-    audio_class->connect_channel = connect_channel;
-    audio_class->get_playback_volume_info_async = spice_gstaudio_get_playback_volume_info_async;
-    audio_class->get_playback_volume_info_finish = spice_gstaudio_get_playback_volume_info_finish;
-    audio_class->get_record_volume_info_async = spice_gstaudio_get_record_volume_info_async;
-    audio_class->get_record_volume_info_finish = spice_gstaudio_get_record_volume_info_finish;
-
-    gobject_class->finalize = spice_gstaudio_finalize;
-    gobject_class->dispose = spice_gstaudio_dispose;
-
-    g_type_class_add_private(klass, sizeof(SpiceGstaudioPrivate));
-}
-
-static GstFlowReturn record_new_buffer(GstAppSink *appsink, gpointer data)
-{
-    SpiceGstaudio *gstaudio = data;
-    SpiceGstaudioPrivate *p = gstaudio->priv;
-    GstMessage *msg;
-
-    g_return_val_if_fail(p != NULL, GST_FLOW_ERROR);
-
-    msg = gst_message_new_application(GST_OBJECT(p->record.pipe),
-                                      gst_structure_new_empty ("new-sample"));
-    gst_element_post_message(p->record.pipe, msg);
-    return GST_FLOW_OK;
-}
-
-static void record_stop(SpiceGstaudio *gstaudio)
-{
-    SpiceGstaudioPrivate *p = gstaudio->priv;
-
-    SPICE_DEBUG("%s", __FUNCTION__);
-    if (p->record.pipe)
-        gst_element_set_state(p->record.pipe, GST_STATE_READY);
-}
-
-static gboolean record_bus_cb(GstBus *bus, GstMessage *msg, gpointer data)
-{
-    SpiceGstaudio *gstaudio = data;
-    SpiceGstaudioPrivate *p = gstaudio->priv;
-
-    g_return_val_if_fail(p != NULL, FALSE);
-
-    switch (GST_MESSAGE_TYPE(msg)) {
-    case GST_MESSAGE_APPLICATION: {
-        GstSample *s;
-        GstBuffer *buffer;
-        GstMapInfo mapping;
-
-        s = gst_app_sink_pull_sample(GST_APP_SINK(p->record.sink));
-        if (!s) {
-            if (!gst_app_sink_is_eos(GST_APP_SINK(p->record.sink)))
-                g_warning("eos not reached, but can't pull new sample");
-            return TRUE;
-        }
-
-        buffer = gst_sample_get_buffer(s);
-        if (!buffer) {
-            if (!gst_app_sink_is_eos(GST_APP_SINK(p->record.sink)))
-                g_warning("eos not reached, but can't pull new buffer");
-            return TRUE;
-        }
-        if (!gst_buffer_map(buffer, &mapping, GST_MAP_READ)) {
-            return TRUE;
-        }
-
-        spice_record_send_data(SPICE_RECORD_CHANNEL(p->rchannel),
-                               /* FIXME: server side doesn't care about ts?
-                                  what is the unit? ms apparently */
-                               mapping.data, mapping.size, 0);
-        gst_buffer_unmap(buffer, &mapping);
-        gst_sample_unref(s);
-        break;
-    }
-    default:
-        break;
-    }
-
-    return TRUE;
-}
-
-static void record_start(SpiceRecordChannel *channel, gint format, gint channels,
-                         gint frequency, gpointer data)
-{
-    SpiceGstaudio *gstaudio = data;
-    SpiceGstaudioPrivate *p = gstaudio->priv;
-
-    g_return_if_fail(p != NULL);
-    g_return_if_fail(format == SPICE_AUDIO_FMT_S16);
-
-    if (p->record.pipe &&
-        (p->record.rate != frequency ||
-         p->record.channels != channels)) {
-        record_stop(gstaudio);
-        gst_object_unref(p->record.pipe);
-        p->record.pipe = NULL;
-    }
-
-    if (!p->record.pipe) {
-        GError *error = NULL;
-        GstBus *bus;
-        gchar *audio_caps =
-            g_strdup_printf("audio/x-raw,format=\"S16LE\",channels=%d,rate=%d,"
-                            "layout=interleaved", channels, frequency);
-        gchar *pipeline =
-            g_strdup_printf("autoaudiosrc name=audiosrc ! queue ! audioconvert ! audioresample ! "
-                            "appsink caps=\"%s\" name=appsink", audio_caps);
-
-        p->record.pipe = gst_parse_launch(pipeline, &error);
-        if (error != NULL) {
-            g_warning("Failed to create pipeline: %s", error->message);
-            goto cleanup;
-        }
-
-        bus = gst_pipeline_get_bus(GST_PIPELINE(p->record.pipe));
-        gst_bus_add_watch(bus, record_bus_cb, data);
-        gst_object_unref(GST_OBJECT(bus));
-
-        p->record.src = gst_bin_get_by_name(GST_BIN(p->record.pipe), "audiosrc");
-        p->record.sink = gst_bin_get_by_name(GST_BIN(p->record.pipe), "appsink");
-        p->record.rate = frequency;
-        p->record.channels = channels;
-
-        gst_app_sink_set_emit_signals(GST_APP_SINK(p->record.sink), TRUE);
-        spice_g_signal_connect_object(p->record.sink, "new-sample",
-                                      G_CALLBACK(record_new_buffer), gstaudio, 0);
-
-cleanup:
-        if (error != NULL && p->record.pipe != NULL) {
-            gst_object_unref(p->record.pipe);
-            p->record.pipe = NULL;
-        }
-        g_clear_error(&error);
-        g_free(audio_caps);
-        g_free(pipeline);
-    }
-
-    if (p->record.pipe)
-        gst_element_set_state(p->record.pipe, GST_STATE_PLAYING);
-}
-
-static void playback_stop(SpiceGstaudio *gstaudio)
-{
-    SpiceGstaudioPrivate *p = gstaudio->priv;
-
-    if (p->playback.pipe)
-        gst_element_set_state(p->playback.pipe, GST_STATE_READY);
-    if (p->mmtime_id != 0) {
-        g_source_remove(p->mmtime_id);
-        p->mmtime_id = 0;
-    }
-}
-
-static gboolean update_mmtime_timeout_cb(gpointer data)
-{
-    SpiceGstaudio *gstaudio = data;
-    SpiceGstaudioPrivate *p = gstaudio->priv;
-    GstQuery *q;
-
-    q = gst_query_new_latency();
-    if (gst_element_query(p->playback.pipe, q)) {
-        gboolean live;
-        GstClockTime minlat, maxlat;
-        gst_query_parse_latency(q, &live, &minlat, &maxlat);
-        SPICE_DEBUG("got min latency %" GST_TIME_FORMAT ", max latency %"
-                    GST_TIME_FORMAT ", live %d", GST_TIME_ARGS (minlat),
-                    GST_TIME_ARGS (maxlat), live);
-        spice_playback_channel_set_delay(SPICE_PLAYBACK_CHANNEL(p->pchannel), GST_TIME_AS_MSECONDS(minlat));
-    }
-    gst_query_unref (q);
-
-    return TRUE;
-}
-
-static void playback_start(SpicePlaybackChannel *channel, gint format, gint channels,
-                           gint frequency, gpointer data)
-{
-    SpiceGstaudio *gstaudio = data;
-    SpiceGstaudioPrivate *p = gstaudio->priv;
-
-    g_return_if_fail(p != NULL);
-    g_return_if_fail(format == SPICE_AUDIO_FMT_S16);
-
-    if (p->playback.pipe &&
-        (p->playback.rate != frequency ||
-         p->playback.channels != channels)) {
-        playback_stop(gstaudio);
-        gst_object_unref(p->playback.pipe);
-        p->playback.pipe = NULL;
-    }
-
-    if (!p->playback.pipe) {
-        GError *error = NULL;
-        gchar *audio_caps =
-            g_strdup_printf("audio/x-raw,format=\"S16LE\",channels=%d,rate=%d,"
-                            "layout=interleaved", channels, frequency);
-        gchar *pipeline = g_strdup (g_getenv("SPICE_GST_AUDIOSINK"));
-        if (pipeline == NULL)
-            pipeline = g_strdup_printf("appsrc is-live=1 do-timestamp=0 caps=\"%s\" name=\"appsrc\" ! queue ! "
-                                       "audioconvert ! audioresample ! autoaudiosink name=\"audiosink\"", audio_caps);
-        SPICE_DEBUG("audio pipeline: %s", pipeline);
-        p->playback.pipe = gst_parse_launch(pipeline, &error);
-        if (error != NULL) {
-            g_warning("Failed to create pipeline: %s", error->message);
-            goto cleanup;
-        }
-        p->playback.src = gst_bin_get_by_name(GST_BIN(p->playback.pipe), "appsrc");
-        p->playback.sink = gst_bin_get_by_name(GST_BIN(p->playback.pipe), "audiosink");
-        p->playback.rate = frequency;
-        p->playback.channels = channels;
-
-cleanup:
-        if (error != NULL && p->playback.pipe != NULL) {
-            gst_object_unref(p->playback.pipe);
-            p->playback.pipe = NULL;
-        }
-        g_clear_error(&error);
-        g_free(audio_caps);
-        g_free(pipeline);
-    }
-
-    if (p->playback.pipe)
-        gst_element_set_state(p->playback.pipe, GST_STATE_PLAYING);
-
-    if (p->mmtime_id == 0) {
-        update_mmtime_timeout_cb(gstaudio);
-        p->mmtime_id = g_timeout_add_seconds(1, update_mmtime_timeout_cb, gstaudio);
-    }
-}
-
-static void playback_data(SpicePlaybackChannel *channel,
-                          gpointer *audio, gint size,
-                          gpointer data)
-{
-    SpiceGstaudio *gstaudio = data;
-    SpiceGstaudioPrivate *p = gstaudio->priv;
-    GstBuffer *buf;
-
-    g_return_if_fail(p != NULL);
-
-    audio = g_memdup(audio, size); /* TODO: try to avoid memory copy */
-    buf = gst_buffer_new_wrapped(audio, size);
-    gst_app_src_push_buffer(GST_APP_SRC(p->playback.src), buf);
-}
-
-#define VOLUME_NORMAL 65535
-
-static void playback_volume_changed(GObject *object, GParamSpec *pspec, gpointer data)
-{
-    SpiceGstaudio *gstaudio = data;
-    GstElement *e;
-    guint16 *volume;
-    guint nchannels;
-    SpiceGstaudioPrivate *p = gstaudio->priv;
-    gdouble vol;
-
-    if (!p->playback.sink)
-        return;
-
-    g_object_get(object,
-                 "volume", &volume,
-                 "nchannels", &nchannels,
-                 NULL);
-
-    g_return_if_fail(nchannels > 0);
-
-    vol = 1.0 * volume[0] / VOLUME_NORMAL;
-    SPICE_DEBUG("playback volume changed to %u (%0.2f)", volume[0], 100*vol);
-
-    if (GST_IS_BIN(p->playback.sink))
-        e = gst_bin_get_by_interface(GST_BIN(p->playback.sink), GST_TYPE_STREAM_VOLUME);
-    else
-        e = g_object_ref(p->playback.sink);
-
-    if (GST_IS_STREAM_VOLUME(e))
-        gst_stream_volume_set_volume(GST_STREAM_VOLUME(e), GST_STREAM_VOLUME_FORMAT_CUBIC, vol);
-    else
-        g_object_set(e, "volume", vol, NULL);
-
-    g_object_unref(e);
-}
-
-static void playback_mute_changed(GObject *object, GParamSpec *pspec, gpointer data)
-{
-    SpiceGstaudio *gstaudio = data;
-    SpiceGstaudioPrivate *p = gstaudio->priv;
-    GstElement *e;
-    gboolean mute;
-
-    if (!p->playback.sink)
-        return;
-
-    g_object_get(object, "mute", &mute, NULL);
-    SPICE_DEBUG("playback mute changed to %u", mute);
-
-    if (GST_IS_BIN(p->playback.sink))
-        e = gst_bin_get_by_interface(GST_BIN(p->playback.sink), GST_TYPE_STREAM_VOLUME);
-    else
-        e = g_object_ref(p->playback.sink);
-
-    if (GST_IS_STREAM_VOLUME(e))
-        gst_stream_volume_set_mute(GST_STREAM_VOLUME(e), mute);
-
-    g_object_unref(e);
-}
-
-static void record_volume_changed(GObject *object, GParamSpec *pspec, gpointer data)
-{
-    SpiceGstaudio *gstaudio = data;
-    SpiceGstaudioPrivate *p = gstaudio->priv;
-    GstElement *e;
-    guint16 *volume;
-    guint nchannels;
-    gdouble vol;
-
-    if (!p->record.src)
-        return;
-
-    g_object_get(object,
-                 "volume", &volume,
-                 "nchannels", &nchannels,
-                 NULL);
-
-    g_return_if_fail(nchannels > 0);
-
-    vol = 1.0 * volume[0] / VOLUME_NORMAL;
-    SPICE_DEBUG("record volume changed to %u (%0.2f)", volume[0], 100*vol);
-
-    /* TODO directsoundsrc doesn't support IDirectSoundBuffer_SetVolume */
-    /* TODO pulsesrc doesn't support volume property, it's all coming! */
-
-    if (GST_IS_BIN(p->record.src))
-        e = gst_bin_get_by_interface(GST_BIN(p->record.src), GST_TYPE_STREAM_VOLUME);
-    else
-        e = g_object_ref(p->record.src);
-
-    if (GST_IS_STREAM_VOLUME(e))
-        gst_stream_volume_set_volume(GST_STREAM_VOLUME(e), GST_STREAM_VOLUME_FORMAT_CUBIC, vol);
-    else
-        g_warning("gst lacks volume capabilities on src (TODO)");
-
-    g_object_unref(e);
-}
-
-static void record_mute_changed(GObject *object, GParamSpec *pspec, gpointer data)
-{
-    SpiceGstaudio *gstaudio = data;
-    SpiceGstaudioPrivate *p = gstaudio->priv;
-    GstElement *e;
-    gboolean mute;
-
-    if (!p->record.src)
-        return;
-
-    g_object_get(object, "mute", &mute, NULL);
-    SPICE_DEBUG("record mute changed to %u", mute);
-
-    if (GST_IS_BIN(p->record.src))
-        e = gst_bin_get_by_interface(GST_BIN(p->record.src), GST_TYPE_STREAM_VOLUME);
-    else
-        e = g_object_ref(p->record.src);
-
-    if (GST_IS_STREAM_VOLUME (e))
-        gst_stream_volume_set_mute(GST_STREAM_VOLUME(e), mute);
-    else
-        g_warning("gst lacks mute capabilities on src: %d (TODO)", mute);
-
-    g_object_unref(e);
-}
-
-static void
-channel_weak_notified(gpointer data,
-                      GObject *where_the_object_was)
-{
-    SpiceGstaudio *gstaudio = SPICE_GSTAUDIO(data);
-    SpiceGstaudioPrivate *p = gstaudio->priv;
-
-    if (where_the_object_was == (GObject *)p->pchannel) {
-        SPICE_DEBUG("playback closed");
-        playback_stop(gstaudio);
-        p->pchannel = NULL;
-    } else if (where_the_object_was == (GObject *)p->rchannel) {
-        SPICE_DEBUG("record closed");
-        record_stop(gstaudio);
-        p->rchannel = NULL;
-    }
-}
-
-static gboolean connect_channel(SpiceAudio *audio, SpiceChannel *channel)
-{
-    SpiceGstaudio *gstaudio = SPICE_GSTAUDIO(audio);
-    SpiceGstaudioPrivate *p = gstaudio->priv;
-
-    if (SPICE_IS_PLAYBACK_CHANNEL(channel)) {
-        g_return_val_if_fail(p->pchannel == NULL, FALSE);
-
-        p->pchannel = channel;
-        g_object_weak_ref(G_OBJECT(p->pchannel), channel_weak_notified, audio);
-        spice_g_signal_connect_object(channel, "playback-start",
-                                      G_CALLBACK(playback_start), gstaudio, 0);
-        spice_g_signal_connect_object(channel, "playback-data",
-                                      G_CALLBACK(playback_data), gstaudio, 0);
-        spice_g_signal_connect_object(channel, "playback-stop",
-                                      G_CALLBACK(playback_stop), gstaudio, G_CONNECT_SWAPPED);
-        spice_g_signal_connect_object(channel, "notify::volume",
-                                      G_CALLBACK(playback_volume_changed), gstaudio, 0);
-        spice_g_signal_connect_object(channel, "notify::mute",
-                                      G_CALLBACK(playback_mute_changed), gstaudio, 0);
-
-        return TRUE;
-    }
-
-    if (SPICE_IS_RECORD_CHANNEL(channel)) {
-        g_return_val_if_fail(p->rchannel == NULL, FALSE);
-
-        p->rchannel = channel;
-        g_object_weak_ref(G_OBJECT(p->rchannel), channel_weak_notified, audio);
-        spice_g_signal_connect_object(channel, "record-start",
-                                      G_CALLBACK(record_start), gstaudio, 0);
-        spice_g_signal_connect_object(channel, "record-stop",
-                                      G_CALLBACK(record_stop), gstaudio, G_CONNECT_SWAPPED);
-        spice_g_signal_connect_object(channel, "notify::volume",
-                                      G_CALLBACK(record_volume_changed), gstaudio, 0);
-        spice_g_signal_connect_object(channel, "notify::mute",
-                                      G_CALLBACK(record_mute_changed), gstaudio, 0);
-
-        return TRUE;
-    }
-
-    return FALSE;
-}
-
-SpiceGstaudio *spice_gstaudio_new(SpiceSession *session, GMainContext *context,
-                                  const char *name)
-{
-    SpiceGstaudio *gstaudio;
-
-    gst_init(NULL, NULL);
-    gstaudio = g_object_new(SPICE_TYPE_GSTAUDIO,
-                            "session", session,
-                            "main-context", context,
-                            NULL);
-
-    return gstaudio;
-}
-
-static void spice_gstaudio_get_playback_volume_info_async(SpiceAudio *audio,
-                                                          GCancellable *cancellable,
-                                                          SpiceMainChannel *main_channel,
-                                                          GAsyncReadyCallback callback,
-                                                          gpointer user_data)
-{
-    GSimpleAsyncResult *simple;
-
-    simple = g_simple_async_result_new(G_OBJECT(audio),
-                                       callback,
-                                       user_data,
-                                       spice_gstaudio_get_playback_volume_info_async);
-    g_simple_async_result_set_check_cancellable (simple, cancellable);
-
-    g_simple_async_result_set_op_res_gboolean(simple, TRUE);
-    g_simple_async_result_complete_in_idle(simple);
-}
-
-static gboolean spice_gstaudio_get_playback_volume_info_finish(SpiceAudio *audio,
-                                                               GAsyncResult *res,
-                                                               gboolean *mute,
-                                                               guint8 *nchannels,
-                                                               guint16 **volume,
-                                                               GError **error)
-{
-    SpiceGstaudioPrivate *p = SPICE_GSTAUDIO(audio)->priv;
-    GstElement *e;
-    gboolean lmute;
-    gdouble vol;
-    gboolean fake_channel = FALSE;
-    GSimpleAsyncResult *simple = (GSimpleAsyncResult *) res;
-
-    g_return_val_if_fail(g_simple_async_result_is_valid(res,
-        G_OBJECT(audio), spice_gstaudio_get_playback_volume_info_async), FALSE);
-
-    if (g_simple_async_result_propagate_error(simple, error)) {
-        return FALSE;
-    }
-
-    if (p->playback.sink == NULL || p->playback.channels == 0) {
-        SPICE_DEBUG("PlaybackChannel not created yet, force start");
-        /* In order to get system volume, we start the pipeline */
-        playback_start(NULL, SPICE_AUDIO_FMT_S16, 2, 48000, audio);
-        fake_channel = TRUE;
-    }
-
-    if (GST_IS_BIN(p->playback.sink))
-        e = gst_bin_get_by_interface(GST_BIN(p->playback.sink), GST_TYPE_STREAM_VOLUME);
-    else
-        e = g_object_ref(p->playback.sink);
-
-    if (GST_IS_STREAM_VOLUME(e)) {
-        vol = gst_stream_volume_get_volume(GST_STREAM_VOLUME(e), GST_STREAM_VOLUME_FORMAT_CUBIC);
-        lmute = gst_stream_volume_get_mute(GST_STREAM_VOLUME(e));
-    } else {
-        g_object_get(e,
-                     "volume", &vol,
-                     "mute", &lmute, NULL);
-    }
-    g_object_unref(e);
-
-    if (fake_channel) {
-        SPICE_DEBUG("Stop faked PlaybackChannel");
-        playback_stop(SPICE_GSTAUDIO(audio));
-    }
-
-    if (mute != NULL) {
-        *mute = lmute;
-    }
-
-    if (nchannels != NULL) {
-        *nchannels = p->playback.channels;
-    }
-
-    if (volume != NULL) {
-        gint i;
-        *volume = g_new(guint16, p->playback.channels);
-        for (i = 0; i < p->playback.channels; i++) {
-            (*volume)[i] = (guint16) (vol * VOLUME_NORMAL);
-            SPICE_DEBUG("(playback) volume at %d is %u (%0.2f%%)", i, (*volume)[i], 100*vol);
-        }
-    }
-
-    return g_simple_async_result_get_op_res_gboolean(simple);
-}
-
-static void spice_gstaudio_get_record_volume_info_async(SpiceAudio *audio,
-                                                        GCancellable *cancellable,
-                                                        SpiceMainChannel *main_channel,
-                                                        GAsyncReadyCallback callback,
-                                                        gpointer user_data)
-{
-    GSimpleAsyncResult *simple;
-
-    simple = g_simple_async_result_new(G_OBJECT(audio),
-                                       callback,
-                                       user_data,
-                                       spice_gstaudio_get_record_volume_info_async);
-    g_simple_async_result_set_check_cancellable (simple, cancellable);
-
-    g_simple_async_result_set_op_res_gboolean(simple, TRUE);
-    g_simple_async_result_complete_in_idle(simple);
-}
-
-static gboolean spice_gstaudio_get_record_volume_info_finish(SpiceAudio *audio,
-                                                             GAsyncResult *res,
-                                                             gboolean *mute,
-                                                             guint8 *nchannels,
-                                                             guint16 **volume,
-                                                             GError **error)
-{
-    SpiceGstaudioPrivate *p = SPICE_GSTAUDIO(audio)->priv;
-    GstElement *e;
-    gboolean lmute;
-    gdouble vol;
-    gboolean fake_channel = FALSE;
-    GSimpleAsyncResult *simple = (GSimpleAsyncResult *) res;
-
-    g_return_val_if_fail(g_simple_async_result_is_valid(res,
-        G_OBJECT(audio), spice_gstaudio_get_record_volume_info_async), FALSE);
-
-    if (g_simple_async_result_propagate_error(simple, error)) {
-        /* set out args that should have new alloc'ed memory to NULL */
-        if (volume != NULL) {
-            *volume = NULL;
-        }
-        return FALSE;
-    }
-
-    if (p->record.src == NULL || p->record.channels == 0) {
-        SPICE_DEBUG("RecordChannel not created yet, force start");
-        /* In order to get system volume, we start the pipeline */
-        record_start(NULL, SPICE_AUDIO_FMT_S16, 2, 48000, audio);
-        fake_channel = TRUE;
-    }
-
-    if (GST_IS_BIN(p->record.src))
-        e = gst_bin_get_by_interface(GST_BIN(p->record.src), GST_TYPE_STREAM_VOLUME);
-    else
-        e = g_object_ref(p->record.src);
-
-    if (GST_IS_STREAM_VOLUME(e)) {
-        vol = gst_stream_volume_get_volume(GST_STREAM_VOLUME(e), GST_STREAM_VOLUME_FORMAT_CUBIC);
-        lmute = gst_stream_volume_get_mute(GST_STREAM_VOLUME(e));
-    } else {
-        g_object_get(e,
-                     "volume", &vol,
-                     "mute", &lmute, NULL);
-    }
-    g_object_unref(e);
-
-    if (fake_channel) {
-        SPICE_DEBUG("Stop faked RecordChannel");
-        record_stop(SPICE_GSTAUDIO(audio));
-    }
-
-    if (mute != NULL) {
-        *mute = lmute;
-    }
-
-    if (nchannels != NULL) {
-        *nchannels = p->record.channels;
-    }
-
-    if (volume != NULL) {
-        gint i;
-        *volume = g_new(guint16, p->record.channels);
-        for (i = 0; i < p->record.channels; i++) {
-            (*volume)[i] = (guint16) (vol * VOLUME_NORMAL);
-            SPICE_DEBUG("(record) volume at %d is %u (%0.2f%%)", i, (*volume)[i], 100*vol);
-        }
-    }
-
-    return g_simple_async_result_get_op_res_gboolean(simple);
-}
diff --git a/gtk/spice-gstaudio.h b/gtk/spice-gstaudio.h
deleted file mode 100644
index b605f1c..0000000
--- a/gtk/spice-gstaudio.h
+++ /dev/null
@@ -1,56 +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_CLIENT_GSTAUDIO_H__
-#define __SPICE_CLIENT_GSTAUDIO_H__
-
-#include "spice-client.h"
-#include "spice-audio.h"
-
-G_BEGIN_DECLS
-
-#define SPICE_TYPE_GSTAUDIO            (spice_gstaudio_get_type())
-#define SPICE_GSTAUDIO(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), SPICE_TYPE_GSTAUDIO, SpiceGstaudio))
-#define SPICE_GSTAUDIO_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), SPICE_TYPE_GSTAUDIO, SpiceGstaudioClass))
-#define SPICE_IS_GSTAUDIO(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), SPICE_TYPE_GSTAUDIO))
-#define SPICE_IS_GSTAUDIO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SPICE_TYPE_GSTAUDIO))
-#define SPICE_GSTAUDIO_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), SPICE_TYPE_GSTAUDIO, SpiceGstaudioClass))
-
-
-typedef struct _SpiceGstaudio SpiceGstaudio;
-typedef struct _SpiceGstaudioClass SpiceGstaudioClass;
-typedef struct _SpiceGstaudioPrivate SpiceGstaudioPrivate;
-
-struct _SpiceGstaudio {
-    SpiceAudio parent;
-    SpiceGstaudioPrivate *priv;
-    /* Do not add fields to this struct */
-};
-
-struct _SpiceGstaudioClass {
-    SpiceAudioClass parent_class;
-    /* Do not add fields to this struct */
-};
-
-GType spice_gstaudio_get_type(void);
-
-SpiceGstaudio *spice_gstaudio_new(SpiceSession *session,
-                                  GMainContext *context, const char *name);
-
-G_END_DECLS
-
-#endif /* __SPICE_CLIENT_GSTAUDIO_H__ */
diff --git a/gtk/spice-gtk-session-priv.h b/gtk/spice-gtk-session-priv.h
deleted file mode 100644
index 91304b2..0000000
--- a/gtk/spice-gtk-session-priv.h
+++ /dev/null
@@ -1,34 +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/>.
-*/
-#ifndef __SPICE_CLIENT_GTK_SESSION_PRIV_H__
-#define __SPICE_CLIENT_GTK_SESSION_PRIV_H__
-
-#include "spice-gtk-session.h"
-
-G_BEGIN_DECLS
-
-void spice_gtk_session_request_auto_usbredir(SpiceGtkSession *self,
-                                             gboolean state);
-gboolean spice_gtk_session_get_read_only(SpiceGtkSession *self);
-void spice_gtk_session_sync_keyboard_modifiers(SpiceGtkSession *self);
-void spice_gtk_session_set_pointer_grabbed(SpiceGtkSession *self, gboolean grabbed);
-gboolean spice_gtk_session_get_pointer_grabbed(SpiceGtkSession *self);
-
-G_END_DECLS
-
-#endif /* __SPICE_CLIENT_GTK_SESSION_PRIV_H__ */
diff --git a/gtk/spice-gtk-session.c b/gtk/spice-gtk-session.c
deleted file mode 100644
index 0937434..0000000
--- a/gtk/spice-gtk-session.c
+++ /dev/null
@@ -1,1229 +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>
-
-#if HAVE_X11_XKBLIB_H
-#include <X11/XKBlib.h>
-#include <gdk/gdkx.h>
-#endif
-#ifdef GDK_WINDOWING_X11
-#include <X11/Xlib.h>
-#include <gdk/gdkx.h>
-#endif
-#ifdef G_OS_WIN32
-#include <windows.h>
-#include <gdk/gdkwin32.h>
-#ifndef MAPVK_VK_TO_VSC /* may be undefined in older mingw-headers */
-#define MAPVK_VK_TO_VSC 0
-#endif
-#endif
-
-#include <gtk/gtk.h>
-#include <spice/vd_agent.h>
-#include "desktop-integration.h"
-#include "gtk-compat.h"
-#include "spice-common.h"
-#include "spice-gtk-session.h"
-#include "spice-gtk-session-priv.h"
-#include "spice-session-priv.h"
-#include "spice-util-priv.h"
-#include "spice-channel-priv.h"
-
-#define CLIPBOARD_LAST (VD_AGENT_CLIPBOARD_SELECTION_SECONDARY + 1)
-
-struct _SpiceGtkSessionPrivate {
-    SpiceSession            *session;
-    /* Clipboard related */
-    gboolean                auto_clipboard_enable;
-    SpiceMainChannel        *main;
-    GtkClipboard            *clipboard;
-    GtkClipboard            *clipboard_primary;
-    GtkTargetEntry          *clip_targets[CLIPBOARD_LAST];
-    guint                   nclip_targets[CLIPBOARD_LAST];
-    gboolean                clip_hasdata[CLIPBOARD_LAST];
-    gboolean                clip_grabbed[CLIPBOARD_LAST];
-    gboolean                clipboard_by_guest[CLIPBOARD_LAST];
-    /* auto-usbredir related */
-    gboolean                auto_usbredir_enable;
-    int                     auto_usbredir_reqs;
-    gboolean                pointer_grabbed;
-};
-
-/**
- * SECTION:spice-gtk-session
- * @short_description: handles GTK connection details
- * @title: Spice GTK Session
- * @section_id:
- * @see_also: #SpiceSession, and the GTK widget #SpiceDisplay
- * @stability: Stable
- * @include: spice-gtk-session.h
- *
- * The #SpiceGtkSession class is the spice-client-gtk counter part of
- * #SpiceSession. It contains functionality which should be handled per
- * session rather then per #SpiceDisplay (one session can have multiple
- * displays), but which cannot live in #SpiceSession as it depends on
- * GTK. For example the clipboard functionality.
- *
- * There should always be a 1:1 relation between #SpiceGtkSession objects
- * and #SpiceSession objects. Therefor there is no spice_gtk_session_new,
- * instead there is spice_gtk_session_get() which ensures this 1:1 relation.
- *
- * Client and guest clipboards will be shared automatically if
- * #SpiceGtkSession:auto-clipboard is set to #TRUE. Alternatively, you
- * can send / receive clipboard data from client to guest with
- * spice_gtk_session_copy_to_guest() / spice_gtk_session_paste_from_guest().
- */
-
-/* ------------------------------------------------------------------ */
-/* Prototypes for private functions */
-static void clipboard_owner_change(GtkClipboard *clipboard,
-                                   GdkEventOwnerChange *event,
-                                   gpointer user_data);
-static void channel_new(SpiceSession *session, SpiceChannel *channel,
-                        gpointer user_data);
-static void channel_destroy(SpiceSession *session, SpiceChannel *channel,
-                            gpointer user_data);
-static gboolean read_only(SpiceGtkSession *self);
-
-/* ------------------------------------------------------------------ */
-/* gobject glue                                                       */
-
-#define SPICE_GTK_SESSION_GET_PRIVATE(obj) \
-    (G_TYPE_INSTANCE_GET_PRIVATE ((obj), SPICE_TYPE_GTK_SESSION, SpiceGtkSessionPrivate))
-
-G_DEFINE_TYPE (SpiceGtkSession, spice_gtk_session, G_TYPE_OBJECT);
-
-/* Properties */
-enum {
-    PROP_0,
-    PROP_SESSION,
-    PROP_AUTO_CLIPBOARD,
-    PROP_AUTO_USBREDIR,
-    PROP_POINTER_GRABBED,
-};
-
-static guint32 get_keyboard_lock_modifiers(void)
-{
-    guint32 modifiers = 0;
-#if GTK_CHECK_VERSION(3,18,0)
-    GdkKeymap *keyboard = gdk_keymap_get_default();
-
-    if (gdk_keymap_get_caps_lock_state(keyboard)) {
-        modifiers |= SPICE_INPUTS_CAPS_LOCK;
-    }
-
-    if (gdk_keymap_get_num_lock_state(keyboard)) {
-        modifiers |= SPICE_INPUTS_NUM_LOCK;
-    }
-
-    if (gdk_keymap_get_scroll_lock_state(keyboard)) {
-        modifiers |= SPICE_INPUTS_SCROLL_LOCK;
-    }
-#else
-#if HAVE_X11_XKBLIB_H
-    Display *x_display = NULL;
-    XKeyboardState keyboard_state;
-
-    GdkScreen *screen = gdk_screen_get_default();
-    if (!GDK_IS_X11_DISPLAY(gdk_screen_get_display(screen))) {
-        SPICE_DEBUG("FIXME: gtk backend is not X11");
-        return 0;
-    }
-
-    x_display = GDK_SCREEN_XDISPLAY(screen);
-    XGetKeyboardControl(x_display, &keyboard_state);
-
-    if (keyboard_state.led_mask & 0x01) {
-        modifiers |= SPICE_INPUTS_CAPS_LOCK;
-    }
-    if (keyboard_state.led_mask & 0x02) {
-        modifiers |= SPICE_INPUTS_NUM_LOCK;
-    }
-    if (keyboard_state.led_mask & 0x04) {
-        modifiers |= SPICE_INPUTS_SCROLL_LOCK;
-    }
-#elif defined(G_OS_WIN32)
-    if (GetKeyState(VK_CAPITAL) & 1) {
-        modifiers |= SPICE_INPUTS_CAPS_LOCK;
-    }
-    if (GetKeyState(VK_NUMLOCK) & 1) {
-        modifiers |= SPICE_INPUTS_NUM_LOCK;
-    }
-    if (GetKeyState(VK_SCROLL) & 1) {
-        modifiers |= SPICE_INPUTS_SCROLL_LOCK;
-    }
-#else
-    g_warning("get_keyboard_lock_modifiers not implemented");
-#endif // HAVE_X11_XKBLIB_H
-#endif // GTK_CHECK_VERSION(3,18,0)
-    return modifiers;
-}
-
-static void spice_gtk_session_sync_keyboard_modifiers_for_channel(SpiceGtkSession *self,
-                                                                  SpiceInputsChannel* inputs,
-                                                                  gboolean force)
-{
-    gint guest_modifiers = 0, client_modifiers = 0;
-
-    g_return_if_fail(SPICE_IS_INPUTS_CHANNEL(inputs));
-
-    g_object_get(inputs, "key-modifiers", &guest_modifiers, NULL);
-    client_modifiers = get_keyboard_lock_modifiers();
-
-    if (force || client_modifiers != guest_modifiers) {
-        CHANNEL_DEBUG(inputs, "client_modifiers:0x%x, guest_modifiers:0x%x",
-                      client_modifiers, guest_modifiers);
-        spice_inputs_set_key_locks(inputs, client_modifiers);
-    }
-}
-
-static void keymap_modifiers_changed(GdkKeymap *keymap, gpointer data)
-{
-    SpiceGtkSession *self = data;
-
-    spice_gtk_session_sync_keyboard_modifiers(self);
-}
-
-static void guest_modifiers_changed(SpiceInputsChannel *inputs, gpointer data)
-{
-    SpiceGtkSession *self = data;
-
-    spice_gtk_session_sync_keyboard_modifiers_for_channel(self, inputs, FALSE);
-}
-
-static void spice_gtk_session_init(SpiceGtkSession *self)
-{
-    SpiceGtkSessionPrivate *s;
-    GdkKeymap *keymap = gdk_keymap_get_default();
-
-    s = self->priv = SPICE_GTK_SESSION_GET_PRIVATE(self);
-
-    s->clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
-    g_signal_connect(G_OBJECT(s->clipboard), "owner-change",
-                     G_CALLBACK(clipboard_owner_change), self);
-    s->clipboard_primary = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
-    g_signal_connect(G_OBJECT(s->clipboard_primary), "owner-change",
-                     G_CALLBACK(clipboard_owner_change), self);
-    spice_g_signal_connect_object(keymap, "state-changed",
-                                  G_CALLBACK(keymap_modifiers_changed), self, 0);
-}
-
-static GObject *
-spice_gtk_session_constructor(GType                  gtype,
-                              guint                  n_properties,
-                              GObjectConstructParam *properties)
-{
-    GObject *obj;
-    SpiceGtkSession *self;
-    SpiceGtkSessionPrivate *s;
-    GList *list;
-    GList *it;
-
-    {
-        /* Always chain up to the parent constructor */
-        GObjectClass *parent_class;
-        parent_class = G_OBJECT_CLASS(spice_gtk_session_parent_class);
-        obj = parent_class->constructor(gtype, n_properties, properties);
-    }
-
-    self = SPICE_GTK_SESSION(obj);
-    s = self->priv;
-    if (!s->session)
-        g_error("SpiceGtKSession constructed without a session");
-
-    g_signal_connect(s->session, "channel-new",
-                     G_CALLBACK(channel_new), self);
-    g_signal_connect(s->session, "channel-destroy",
-                     G_CALLBACK(channel_destroy), self);
-    list = spice_session_get_channels(s->session);
-    for (it = g_list_first(list); it != NULL; it = g_list_next(it)) {
-        channel_new(s->session, it->data, (gpointer*)self);
-    }
-    g_list_free(list);
-
-    return obj;
-}
-
-static void spice_gtk_session_dispose(GObject *gobject)
-{
-    SpiceGtkSession *self = SPICE_GTK_SESSION(gobject);
-    SpiceGtkSessionPrivate *s = self->priv;
-
-    /* release stuff */
-    if (s->clipboard) {
-        g_signal_handlers_disconnect_by_func(s->clipboard,
-                G_CALLBACK(clipboard_owner_change), self);
-        s->clipboard = NULL;
-    }
-
-    if (s->clipboard_primary) {
-        g_signal_handlers_disconnect_by_func(s->clipboard_primary,
-                G_CALLBACK(clipboard_owner_change), self);
-        s->clipboard_primary = NULL;
-    }
-
-    if (s->session) {
-        g_signal_handlers_disconnect_by_func(s->session,
-                                             G_CALLBACK(channel_new),
-                                             self);
-        g_signal_handlers_disconnect_by_func(s->session,
-                                             G_CALLBACK(channel_destroy),
-                                             self);
-        s->session = NULL;
-    }
-
-    /* Chain up to the parent class */
-    if (G_OBJECT_CLASS(spice_gtk_session_parent_class)->dispose)
-        G_OBJECT_CLASS(spice_gtk_session_parent_class)->dispose(gobject);
-}
-
-static void spice_gtk_session_finalize(GObject *gobject)
-{
-    SpiceGtkSession *self = SPICE_GTK_SESSION(gobject);
-    SpiceGtkSessionPrivate *s = self->priv;
-    int i;
-
-    /* release stuff */
-    for (i = 0; i < CLIPBOARD_LAST; ++i) {
-        g_free(s->clip_targets[i]);
-        s->clip_targets[i] = NULL;
-    }
-
-    /* Chain up to the parent class */
-    if (G_OBJECT_CLASS(spice_gtk_session_parent_class)->finalize)
-        G_OBJECT_CLASS(spice_gtk_session_parent_class)->finalize(gobject);
-}
-
-static void spice_gtk_session_get_property(GObject    *gobject,
-                                           guint       prop_id,
-                                           GValue     *value,
-                                           GParamSpec *pspec)
-{
-    SpiceGtkSession *self = SPICE_GTK_SESSION(gobject);
-    SpiceGtkSessionPrivate *s = self->priv;
-
-    switch (prop_id) {
-    case PROP_SESSION:
-        g_value_set_object(value, s->session);
-	break;
-    case PROP_AUTO_CLIPBOARD:
-        g_value_set_boolean(value, s->auto_clipboard_enable);
-        break;
-    case PROP_AUTO_USBREDIR:
-        g_value_set_boolean(value, s->auto_usbredir_enable);
-        break;
-    case PROP_POINTER_GRABBED:
-        g_value_set_boolean(value, s->pointer_grabbed);
-        break;
-    default:
-	G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
-	break;
-    }
-}
-
-static void spice_gtk_session_set_property(GObject      *gobject,
-                                           guint         prop_id,
-                                           const GValue *value,
-                                           GParamSpec   *pspec)
-{
-    SpiceGtkSession *self = SPICE_GTK_SESSION(gobject);
-    SpiceGtkSessionPrivate *s = self->priv;
-
-    switch (prop_id) {
-    case PROP_SESSION:
-        s->session = g_value_get_object(value);
-        break;
-    case PROP_AUTO_CLIPBOARD:
-        s->auto_clipboard_enable = g_value_get_boolean(value);
-        break;
-    case PROP_AUTO_USBREDIR: {
-        SpiceDesktopIntegration *desktop_int;
-        gboolean orig_value = s->auto_usbredir_enable;
-
-        s->auto_usbredir_enable = g_value_get_boolean(value);
-        if (s->auto_usbredir_enable == orig_value)
-            break;
-
-        if (s->auto_usbredir_reqs) {
-            SpiceUsbDeviceManager *manager =
-                spice_usb_device_manager_get(s->session, NULL);
-
-            if (!manager)
-                break;
-
-            g_object_set(manager, "auto-connect", s->auto_usbredir_enable,
-                         NULL);
-
-            desktop_int = spice_desktop_integration_get(s->session);
-            if (s->auto_usbredir_enable)
-                spice_desktop_integration_inhibit_automount(desktop_int);
-            else
-                spice_desktop_integration_uninhibit_automount(desktop_int);
-        }
-        break;
-    }
-    default:
-        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
-        break;
-    }
-}
-
-static void spice_gtk_session_class_init(SpiceGtkSessionClass *klass)
-{
-    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
-
-    gobject_class->constructor  = spice_gtk_session_constructor;
-    gobject_class->dispose      = spice_gtk_session_dispose;
-    gobject_class->finalize     = spice_gtk_session_finalize;
-    gobject_class->get_property = spice_gtk_session_get_property;
-    gobject_class->set_property = spice_gtk_session_set_property;
-
-    /**
-     * SpiceGtkSession:session:
-     *
-     * #SpiceSession this #SpiceGtkSession is associated with
-     *
-     * Since: 0.8
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_SESSION,
-         g_param_spec_object("session",
-                             "Session",
-                             "SpiceSession",
-                             SPICE_TYPE_SESSION,
-                             G_PARAM_READWRITE |
-                             G_PARAM_CONSTRUCT_ONLY |
-                             G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceGtkSession:auto-clipboard:
-     *
-     * When this is true the clipboard gets automatically shared between host
-     * and guest.
-     *
-     * Since: 0.8
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_AUTO_CLIPBOARD,
-         g_param_spec_boolean("auto-clipboard",
-                              "Auto clipboard",
-                              "Automatically relay clipboard changes between "
-                              "host and guest.",
-                              TRUE,
-                              G_PARAM_READWRITE |
-                              G_PARAM_CONSTRUCT |
-                              G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceGtkSession:auto-usbredir:
-     *
-     * Automatically redirect newly plugged in USB devices. Note the auto
-     * redirection only happens when a #SpiceDisplay associated with the
-     * session had keyboard focus.
-     *
-     * Since: 0.8
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_AUTO_USBREDIR,
-         g_param_spec_boolean("auto-usbredir",
-                              "Auto USB Redirection",
-                              "Automatically redirect newly plugged in USB"
-                              "Devices to the guest.",
-                              FALSE,
-                              G_PARAM_READWRITE |
-                              G_PARAM_CONSTRUCT |
-                              G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceGtkSession:pointer-grabbed:
-     *
-     * Returns %TRUE if the pointer is currently grabbed by this session.
-     *
-     * Since: 0.27
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_POINTER_GRABBED,
-         g_param_spec_boolean("pointer-grabbed",
-                              "Pointer grabbed",
-                              "Whether the pointer is grabbed",
-                              FALSE,
-                              G_PARAM_READABLE |
-                              G_PARAM_STATIC_STRINGS));
-
-    g_type_class_add_private(klass, sizeof(SpiceGtkSessionPrivate));
-}
-
-/* ---------------------------------------------------------------- */
-/* private functions (clipboard related)                            */
-
-static GtkClipboard* get_clipboard_from_selection(SpiceGtkSessionPrivate *s,
-                                                  guint selection)
-{
-    if (selection == VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD) {
-        return s->clipboard;
-    } else if (selection == VD_AGENT_CLIPBOARD_SELECTION_PRIMARY) {
-        return s->clipboard_primary;
-    } else {
-        g_warning("Unhandled clipboard selection: %d", selection);
-        return NULL;
-    }
-}
-
-static gint get_selection_from_clipboard(SpiceGtkSessionPrivate *s,
-                                         GtkClipboard* cb)
-{
-    if (cb == s->clipboard) {
-        return VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD;
-    } else if (cb == s->clipboard_primary) {
-        return VD_AGENT_CLIPBOARD_SELECTION_PRIMARY;
-    } else {
-        g_warning("Unhandled clipboard");
-        return -1;
-    }
-}
-
-static const struct {
-    const char  *xatom;
-    uint32_t    vdagent;
-} atom2agent[] = {
-    {
-        .vdagent = VD_AGENT_CLIPBOARD_UTF8_TEXT,
-        .xatom   = "UTF8_STRING",
-    },{
-        .vdagent = VD_AGENT_CLIPBOARD_UTF8_TEXT,
-        .xatom   = "text/plain;charset=utf-8"
-    },{
-        .vdagent = VD_AGENT_CLIPBOARD_UTF8_TEXT,
-        .xatom   = "STRING"
-    },{
-        .vdagent = VD_AGENT_CLIPBOARD_UTF8_TEXT,
-        .xatom   = "TEXT"
-    },{
-        .vdagent = VD_AGENT_CLIPBOARD_UTF8_TEXT,
-        .xatom   = "text/plain"
-    },{
-        .vdagent = VD_AGENT_CLIPBOARD_IMAGE_PNG,
-        .xatom   = "image/png"
-    },{
-        .vdagent = VD_AGENT_CLIPBOARD_IMAGE_BMP,
-        .xatom   = "image/bmp"
-    },{
-        .vdagent = VD_AGENT_CLIPBOARD_IMAGE_BMP,
-        .xatom   = "image/x-bmp"
-    },{
-        .vdagent = VD_AGENT_CLIPBOARD_IMAGE_BMP,
-        .xatom   = "image/x-MS-bmp"
-    },{
-        .vdagent = VD_AGENT_CLIPBOARD_IMAGE_BMP,
-        .xatom   = "image/x-win-bitmap"
-    },{
-        .vdagent = VD_AGENT_CLIPBOARD_IMAGE_TIFF,
-        .xatom   = "image/tiff"
-    },{
-        .vdagent = VD_AGENT_CLIPBOARD_IMAGE_JPG,
-        .xatom   = "image/jpeg"
-    }
-};
-
-typedef struct _WeakRef {
-    GObject *object;
-} WeakRef;
-
-static void weak_notify_cb(WeakRef *weakref, GObject *object)
-{
-    weakref->object = NULL;
-}
-
-static WeakRef* weak_ref(GObject *object)
-{
-    WeakRef *weakref = g_new(WeakRef, 1);
-
-    g_object_weak_ref(object, (GWeakNotify)weak_notify_cb, weakref);
-    weakref->object = object;
-
-    return weakref;
-}
-
-static void weak_unref(WeakRef* weakref)
-{
-    if (weakref->object)
-        g_object_weak_unref(weakref->object, (GWeakNotify)weak_notify_cb, weakref);
-
-    g_free(weakref);
-}
-
-static void clipboard_get_targets(GtkClipboard *clipboard,
-                                  GdkAtom *atoms,
-                                  gint n_atoms,
-                                  gpointer user_data)
-{
-    WeakRef *weakref = user_data;
-    SpiceGtkSession *self = (SpiceGtkSession*)weakref->object;
-    weak_unref(weakref);
-
-    if (self == NULL)
-        return;
-
-    g_return_if_fail(SPICE_IS_GTK_SESSION(self));
-
-    SpiceGtkSessionPrivate *s = self->priv;
-    guint32 types[SPICE_N_ELEMENTS(atom2agent)];
-    char *name;
-    int a, m, t;
-    int selection;
-
-    if (s->main == NULL)
-        return;
-
-    selection = get_selection_from_clipboard(s, clipboard);
-    g_return_if_fail(selection != -1);
-
-    SPICE_DEBUG("%s:", __FUNCTION__);
-    if (spice_util_get_debug()) {
-        for (a = 0; a < n_atoms; a++) {
-            name = gdk_atom_name(atoms[a]);
-            SPICE_DEBUG(" \"%s\"", name);
-            g_free(name);
-        }
-    }
-
-    memset(types, 0, sizeof(types));
-    for (a = 0; a < n_atoms; a++) {
-        name = gdk_atom_name(atoms[a]);
-        for (m = 0; m < SPICE_N_ELEMENTS(atom2agent); m++) {
-            if (strcasecmp(name, atom2agent[m].xatom) != 0) {
-                continue;
-            }
-            /* found match */
-            for (t = 0; t < SPICE_N_ELEMENTS(atom2agent); t++) {
-                if (types[t] == atom2agent[m].vdagent) {
-                    /* type already in list */
-                    break;
-                }
-                if (types[t] == 0) {
-                    /* add type to empty slot */
-                    types[t] = atom2agent[m].vdagent;
-                    break;
-                }
-            }
-            break;
-        }
-        g_free(name);
-    }
-    for (t = 0; t < SPICE_N_ELEMENTS(atom2agent); t++) {
-        if (types[t] == 0) {
-            break;
-        }
-    }
-    if (!s->clip_grabbed[selection] && t > 0) {
-        s->clip_grabbed[selection] = TRUE;
-
-        if (spice_main_agent_test_capability(s->main, VD_AGENT_CAP_CLIPBOARD_BY_DEMAND))
-            spice_main_clipboard_selection_grab(s->main, selection, types, t);
-        /* Sending a grab causes the agent to do an impicit release */
-        s->nclip_targets[selection] = 0;
-    }
-}
-
-static void clipboard_owner_change(GtkClipboard        *clipboard,
-                                   GdkEventOwnerChange *event,
-                                   gpointer            user_data)
-{
-    g_return_if_fail(SPICE_IS_GTK_SESSION(user_data));
-
-    SpiceGtkSession *self = user_data;
-    SpiceGtkSessionPrivate *s = self->priv;
-    int selection;
-
-    selection = get_selection_from_clipboard(s, clipboard);
-    g_return_if_fail(selection != -1);
-
-    if (s->main == NULL)
-        return;
-
-    if (s->clip_grabbed[selection]) {
-        s->clip_grabbed[selection] = FALSE;
-        if (spice_main_agent_test_capability(s->main, VD_AGENT_CAP_CLIPBOARD_BY_DEMAND))
-            spice_main_clipboard_selection_release(s->main, selection);
-    }
-
-    switch (event->reason) {
-    case GDK_OWNER_CHANGE_NEW_OWNER:
-        if (gtk_clipboard_get_owner(clipboard) == G_OBJECT(self))
-            break;
-
-        s->clipboard_by_guest[selection] = FALSE;
-        s->clip_hasdata[selection] = TRUE;
-        if (s->auto_clipboard_enable && !read_only(self))
-            gtk_clipboard_request_targets(clipboard, clipboard_get_targets,
-                                          weak_ref(G_OBJECT(self)));
-        break;
-    default:
-        s->clip_hasdata[selection] = FALSE;
-        break;
-    }
-}
-
-typedef struct
-{
-    SpiceGtkSession *self;
-    GMainLoop *loop;
-    GtkSelectionData *selection_data;
-    guint info;
-    guint selection;
-} RunInfo;
-
-static void clipboard_got_from_guest(SpiceMainChannel *main, guint selection,
-                                     guint type, const guchar *data, guint size,
-                                     gpointer user_data)
-{
-    RunInfo *ri = user_data;
-    SpiceGtkSessionPrivate *s = ri->self->priv;
-    gchar *conv = NULL;
-
-    g_return_if_fail(selection == ri->selection);
-
-    SPICE_DEBUG("clipboard got data");
-
-    if (atom2agent[ri->info].vdagent == VD_AGENT_CLIPBOARD_UTF8_TEXT) {
-        /* on windows, gtk+ would already convert to LF endings, but
-           not on unix */
-        if (spice_main_agent_test_capability(s->main, VD_AGENT_CAP_GUEST_LINEEND_CRLF)) {
-            GError *err = NULL;
-
-            conv = spice_dos2unix((gchar*)data, size, &err);
-            if (err) {
-                g_warning("Failed to convert text line ending: %s", err->message);
-                g_clear_error(&err);
-                goto end;
-            }
-
-            size = strlen(conv);
-        }
-
-        gtk_selection_data_set_text(ri->selection_data, conv ?: (gchar*)data, size);
-    } else {
-        gtk_selection_data_set(ri->selection_data,
-            gdk_atom_intern_static_string(atom2agent[ri->info].xatom),
-            8, data, size);
-    }
-
-end:
-    if (g_main_loop_is_running (ri->loop))
-        g_main_loop_quit (ri->loop);
-
-    g_free(conv);
-}
-
-static void clipboard_agent_connected(RunInfo *ri)
-{
-    g_warning("agent status changed, cancel clipboard request");
-
-    if (g_main_loop_is_running(ri->loop))
-        g_main_loop_quit(ri->loop);
-}
-
-static void clipboard_get(GtkClipboard *clipboard,
-                          GtkSelectionData *selection_data,
-                          guint info, gpointer user_data)
-{
-    g_return_if_fail(SPICE_IS_GTK_SESSION(user_data));
-
-    RunInfo ri = { NULL, };
-    SpiceGtkSession *self = user_data;
-    SpiceGtkSessionPrivate *s = self->priv;
-    gboolean agent_connected = FALSE;
-    gulong clipboard_handler;
-    gulong agent_handler;
-    int selection;
-
-    SPICE_DEBUG("clipboard get");
-
-    selection = get_selection_from_clipboard(s, clipboard);
-    g_return_if_fail(selection != -1);
-    g_return_if_fail(info < SPICE_N_ELEMENTS(atom2agent));
-    g_return_if_fail(s->main != NULL);
-
-    ri.selection_data = selection_data;
-    ri.info = info;
-    ri.loop = g_main_loop_new(NULL, FALSE);
-    ri.selection = selection;
-    ri.self = self;
-
-    clipboard_handler = g_signal_connect(s->main, "main-clipboard-selection",
-                                         G_CALLBACK(clipboard_got_from_guest),
-                                         &ri);
-    agent_handler = g_signal_connect_swapped(s->main, "notify::agent-connected",
-                                     G_CALLBACK(clipboard_agent_connected),
-                                     &ri);
-
-    spice_main_clipboard_selection_request(s->main, selection,
-                                           atom2agent[info].vdagent);
-
-
-    g_object_get(s->main, "agent-connected", &agent_connected, NULL);
-    if (!agent_connected) {
-        SPICE_DEBUG("canceled clipboard_get, before running loop");
-        goto cleanup;
-    }
-
-    /* apparently, this is needed to avoid dead-lock, from
-       gtk_dialog_run */
-    gdk_threads_leave();
-    g_main_loop_run(ri.loop);
-    gdk_threads_enter();
-
-cleanup:
-    g_main_loop_unref(ri.loop);
-    ri.loop = NULL;
-    g_signal_handler_disconnect(s->main, clipboard_handler);
-    g_signal_handler_disconnect(s->main, agent_handler);
-}
-
-static void clipboard_clear(GtkClipboard *clipboard, gpointer user_data)
-{
-    SPICE_DEBUG("clipboard_clear");
-    /* We watch for clipboard ownership changes and act on those, so we
-       don't need to do anything here */
-}
-
-static gboolean clipboard_grab(SpiceMainChannel *main, guint selection,
-                               guint32* types, guint32 ntypes,
-                               gpointer user_data)
-{
-    g_return_val_if_fail(SPICE_IS_GTK_SESSION(user_data), FALSE);
-
-    SpiceGtkSession *self = user_data;
-    SpiceGtkSessionPrivate *s = self->priv;
-    GtkTargetEntry targets[SPICE_N_ELEMENTS(atom2agent)];
-    gboolean target_selected[SPICE_N_ELEMENTS(atom2agent)] = { FALSE, };
-    gboolean found;
-    GtkClipboard* cb;
-    int m, n, i;
-
-    cb = get_clipboard_from_selection(s, selection);
-    g_return_val_if_fail(cb != NULL, FALSE);
-
-    i = 0;
-    for (n = 0; n < ntypes; ++n) {
-        found = FALSE;
-        for (m = 0; m < SPICE_N_ELEMENTS(atom2agent); m++) {
-            if (atom2agent[m].vdagent == types[n] && !target_selected[m]) {
-                found = TRUE;
-                g_return_val_if_fail(i < SPICE_N_ELEMENTS(atom2agent), FALSE);
-                targets[i].target = (gchar*)atom2agent[m].xatom;
-                targets[i].info = m;
-                target_selected[m] = TRUE;
-                i += 1;
-            }
-        }
-        if (!found) {
-            g_warning("clipboard: couldn't find a matching type for: %d",
-                      types[n]);
-        }
-    }
-
-    g_free(s->clip_targets[selection]);
-    s->nclip_targets[selection] = i;
-    s->clip_targets[selection] = g_memdup(targets, sizeof(GtkTargetEntry) * i);
-    /* Receiving a grab implies we've released our own grab */
-    s->clip_grabbed[selection] = FALSE;
-
-    if (read_only(self) ||
-        !s->auto_clipboard_enable ||
-        s->nclip_targets[selection] == 0)
-        goto skip_grab_clipboard;
-
-    if (!gtk_clipboard_set_with_owner(cb, targets, i,
-                                      clipboard_get, clipboard_clear, G_OBJECT(self))) {
-        g_warning("clipboard grab failed");
-        return FALSE;
-    }
-    s->clipboard_by_guest[selection] = TRUE;
-    s->clip_hasdata[selection] = FALSE;
-
-skip_grab_clipboard:
-    return TRUE;
-}
-
-static gboolean check_clipboard_size_limits(SpiceGtkSession *session,
-                                            gint clipboard_len)
-{
-    int max_clipboard;
-
-    g_object_get(session->priv->main, "max-clipboard", &max_clipboard, NULL);
-    if (max_clipboard != -1 && clipboard_len > max_clipboard) {
-        g_warning("discarded clipboard of size %d (max: %d)",
-                  clipboard_len, max_clipboard);
-        return FALSE;
-    } else if (clipboard_len <= 0) {
-        SPICE_DEBUG("discarding empty clipboard");
-        return FALSE;
-    }
-
-    return TRUE;
-}
-
-static void clipboard_received_cb(GtkClipboard *clipboard,
-                                  GtkSelectionData *selection_data,
-                                  gpointer user_data)
-{
-    WeakRef *weakref = user_data;
-    SpiceGtkSession *self = (SpiceGtkSession*)weakref->object;
-    weak_unref(weakref);
-
-    if (self == NULL)
-        return;
-
-    g_return_if_fail(SPICE_IS_GTK_SESSION(self));
-
-    SpiceGtkSessionPrivate *s = self->priv;
-    gint len = 0, m;
-    guint32 type = VD_AGENT_CLIPBOARD_NONE;
-    gchar* name;
-    GdkAtom atom;
-    int selection;
-
-    selection = get_selection_from_clipboard(s, clipboard);
-    g_return_if_fail(selection != -1);
-
-    len = gtk_selection_data_get_length(selection_data);
-    if (!check_clipboard_size_limits(self, len)) {
-        return;
-    } else {
-        atom = gtk_selection_data_get_data_type(selection_data);
-        name = gdk_atom_name(atom);
-        for (m = 0; m < SPICE_N_ELEMENTS(atom2agent); m++) {
-            if (strcasecmp(name, atom2agent[m].xatom) == 0) {
-                break;
-            }
-        }
-
-        if (m >= SPICE_N_ELEMENTS(atom2agent)) {
-            g_warning("clipboard_received for unsupported type: %s", name);
-        } else {
-            type = atom2agent[m].vdagent;
-        }
-
-        g_free(name);
-    }
-
-    const guchar *data = gtk_selection_data_get_data(selection_data);
-    gpointer conv = NULL;
-
-    /* gtk+ internal utf8 newline is always LF, even on windows */
-    if (type == VD_AGENT_CLIPBOARD_UTF8_TEXT) {
-        if (spice_main_agent_test_capability(s->main, VD_AGENT_CAP_GUEST_LINEEND_CRLF)) {
-            GError *err = NULL;
-
-            conv = spice_unix2dos((gchar*)data, len, &err);
-            if (err) {
-                g_warning("Failed to convert text line ending: %s", err->message);
-                g_clear_error(&err);
-                return;
-            }
-
-            len = strlen(conv);
-        } else {
-            /* On Windows, with some versions of gtk+, GtkSelectionData::length
-             * will include the final '\0'. When a string with this trailing '\0'
-             * is pasted in some linux applications, it will be pasted as <NIL> or
-             * as an invisible character, which is unwanted. Ensure the length we
-             * send to the agent does not include any trailing '\0'
-             * This is gtk+ bug https://bugzilla.gnome.org/show_bug.cgi?id=734670
-             */
-            len = strlen((const char *)data);
-        }
-        if (!check_clipboard_size_limits(self, len)) {
-            g_free(conv);
-            return;
-        }
-    }
-
-    spice_main_clipboard_selection_notify(s->main, selection, type,
-                                          conv ?: data, len);
-    g_free(conv);
-}
-
-static gboolean clipboard_request(SpiceMainChannel *main, guint selection,
-                                  guint type, gpointer user_data)
-{
-    g_return_val_if_fail(SPICE_IS_GTK_SESSION(user_data), FALSE);
-
-    SpiceGtkSession *self = user_data;
-    SpiceGtkSessionPrivate *s = self->priv;
-    GdkAtom atom;
-    GtkClipboard* cb;
-    int m;
-
-    g_return_val_if_fail(s->clipboard_by_guest[selection] == FALSE, FALSE);
-    g_return_val_if_fail(s->clip_grabbed[selection], FALSE);
-
-    if (read_only(self))
-        return FALSE;
-
-    cb = get_clipboard_from_selection(s, selection);
-    g_return_val_if_fail(cb != NULL, FALSE);
-
-    for (m = 0; m < SPICE_N_ELEMENTS(atom2agent); m++) {
-        if (atom2agent[m].vdagent == type)
-            break;
-    }
-
-    g_return_val_if_fail(m < SPICE_N_ELEMENTS(atom2agent), FALSE);
-
-    atom = gdk_atom_intern_static_string(atom2agent[m].xatom);
-    gtk_clipboard_request_contents(cb, atom, clipboard_received_cb,
-                                   weak_ref(G_OBJECT(self)));
-
-    return TRUE;
-}
-
-static void clipboard_release(SpiceMainChannel *main, guint selection,
-                              gpointer user_data)
-{
-    g_return_if_fail(SPICE_IS_GTK_SESSION(user_data));
-
-    SpiceGtkSession *self = user_data;
-    SpiceGtkSessionPrivate *s = self->priv;
-    GtkClipboard* clipboard = get_clipboard_from_selection(s, selection);
-
-    if (!clipboard)
-        return;
-
-    s->nclip_targets[selection] = 0;
-
-    if (!s->clipboard_by_guest[selection])
-        return;
-    gtk_clipboard_clear(clipboard);
-    s->clipboard_by_guest[selection] = FALSE;
-}
-
-static void channel_new(SpiceSession *session, SpiceChannel *channel,
-                        gpointer user_data)
-{
-    g_return_if_fail(SPICE_IS_GTK_SESSION(user_data));
-
-    SpiceGtkSession *self = user_data;
-    SpiceGtkSessionPrivate *s = self->priv;
-
-    if (SPICE_IS_MAIN_CHANNEL(channel)) {
-        SPICE_DEBUG("Changing main channel from %p to %p", s->main, channel);
-        s->main = SPICE_MAIN_CHANNEL(channel);
-        g_signal_connect(channel, "main-clipboard-selection-grab",
-                         G_CALLBACK(clipboard_grab), self);
-        g_signal_connect(channel, "main-clipboard-selection-request",
-                         G_CALLBACK(clipboard_request), self);
-        g_signal_connect(channel, "main-clipboard-selection-release",
-                         G_CALLBACK(clipboard_release), self);
-    }
-    if (SPICE_IS_INPUTS_CHANNEL(channel)) {
-        spice_g_signal_connect_object(channel, "inputs-modifiers",
-                                      G_CALLBACK(guest_modifiers_changed), self, 0);
-        spice_gtk_session_sync_keyboard_modifiers_for_channel(self, SPICE_INPUTS_CHANNEL(channel), TRUE);
-    }
-}
-
-static void channel_destroy(SpiceSession *session, SpiceChannel *channel,
-                            gpointer user_data)
-{
-    g_return_if_fail(SPICE_IS_GTK_SESSION(user_data));
-
-    SpiceGtkSession *self = user_data;
-    SpiceGtkSessionPrivate *s = self->priv;
-    guint i;
-
-    if (SPICE_IS_MAIN_CHANNEL(channel) && SPICE_MAIN_CHANNEL(channel) == s->main) {
-        s->main = NULL;
-        for (i = 0; i < CLIPBOARD_LAST; ++i) {
-            if (s->clipboard_by_guest[i]) {
-                GtkClipboard *cb = get_clipboard_from_selection(s, i);
-                if (cb)
-                    gtk_clipboard_clear(cb);
-                s->clipboard_by_guest[i] = FALSE;
-            }
-            s->clip_grabbed[i] = FALSE;
-            s->nclip_targets[i] = 0;
-        }
-    }
-}
-
-static gboolean read_only(SpiceGtkSession *self)
-{
-    return spice_session_get_read_only(self->priv->session);
-}
-
-/* ---------------------------------------------------------------- */
-/* private functions (usbredir related)                             */
-G_GNUC_INTERNAL
-void spice_gtk_session_request_auto_usbredir(SpiceGtkSession *self,
-                                             gboolean state)
-{
-    g_return_if_fail(SPICE_IS_GTK_SESSION(self));
-
-    SpiceGtkSessionPrivate *s = self->priv;
-    SpiceDesktopIntegration *desktop_int;
-    SpiceUsbDeviceManager *manager;
-
-    if (state) {
-        s->auto_usbredir_reqs++;
-        if (s->auto_usbredir_reqs != 1)
-            return;
-    } else {
-        g_return_if_fail(s->auto_usbredir_reqs > 0);
-        s->auto_usbredir_reqs--;
-        if (s->auto_usbredir_reqs != 0)
-            return;
-    }
-
-    if (!s->auto_usbredir_enable)
-        return;
-
-    manager = spice_usb_device_manager_get(s->session, NULL);
-    if (!manager)
-        return;
-
-    g_object_set(manager, "auto-connect", state, NULL);
-
-    desktop_int = spice_desktop_integration_get(s->session);
-    if (state)
-        spice_desktop_integration_inhibit_automount(desktop_int);
-    else
-        spice_desktop_integration_uninhibit_automount(desktop_int);
-}
-
-/* ------------------------------------------------------------------ */
-/* public functions                                                   */
-
-/**
- * spice_gtk_session_get:
- * @session: #SpiceSession for which to get the #SpiceGtkSession
- *
- * Gets the #SpiceGtkSession associated with the passed in #SpiceSession.
- * A new #SpiceGtkSession instance will be created the first time this
- * function is called for a certain #SpiceSession.
- *
- * Note that this function returns a weak reference, which should not be used
- * after the #SpiceSession itself has been unref-ed by the caller.
- *
- * Returns: (transfer none): a weak reference to the #SpiceGtkSession associated with the passed in #SpiceSession
- *
- * Since 0.8
- **/
-SpiceGtkSession *spice_gtk_session_get(SpiceSession *session)
-{
-    g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
-
-    SpiceGtkSession *self;
-    static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
-
-    g_static_mutex_lock(&mutex);
-    self = g_object_get_data(G_OBJECT(session), "spice-gtk-session");
-    if (self == NULL) {
-        self = g_object_new(SPICE_TYPE_GTK_SESSION, "session", session, NULL);
-        g_object_set_data_full(G_OBJECT(session), "spice-gtk-session", self, g_object_unref);
-    }
-    g_static_mutex_unlock(&mutex);
-
-    return SPICE_GTK_SESSION(self);
-}
-
-/**
- * spice_gtk_session_copy_to_guest:
- * @self:
- *
- * Copy client-side clipboard to guest clipboard.
- *
- * Since 0.8
- **/
-void spice_gtk_session_copy_to_guest(SpiceGtkSession *self)
-{
-    g_return_if_fail(SPICE_IS_GTK_SESSION(self));
-    g_return_if_fail(read_only(self) == FALSE);
-
-    SpiceGtkSessionPrivate *s = self->priv;
-    int selection = VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD;
-
-    if (s->clip_hasdata[selection] && !s->clip_grabbed[selection]) {
-        gtk_clipboard_request_targets(s->clipboard, clipboard_get_targets,
-                                      weak_ref(G_OBJECT(self)));
-    }
-}
-
-/**
- * spice_gtk_session_paste_from_guest:
- * @self:
- *
- * Copy guest clipboard to client-side clipboard.
- *
- * Since 0.8
- **/
-void spice_gtk_session_paste_from_guest(SpiceGtkSession *self)
-{
-    g_return_if_fail(SPICE_IS_GTK_SESSION(self));
-    g_return_if_fail(read_only(self) == FALSE);
-
-    SpiceGtkSessionPrivate *s = self->priv;
-    int selection = VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD;
-
-    if (s->nclip_targets[selection] == 0) {
-        g_warning("Guest clipboard is not available.");
-        return;
-    }
-
-    if (!gtk_clipboard_set_with_owner(s->clipboard, s->clip_targets[selection], s->nclip_targets[selection],
-                                      clipboard_get, clipboard_clear, G_OBJECT(self))) {
-        g_warning("Clipboard grab failed");
-        return;
-    }
-    s->clipboard_by_guest[selection] = TRUE;
-    s->clip_hasdata[selection] = FALSE;
-}
-
-G_GNUC_INTERNAL
-void spice_gtk_session_sync_keyboard_modifiers(SpiceGtkSession *self)
-{
-    GList *l = NULL, *channels = spice_session_get_channels(self->priv->session);
-
-    for (l = channels; l != NULL; l = l->next) {
-        if (SPICE_IS_INPUTS_CHANNEL(l->data)) {
-            SpiceInputsChannel *inputs = SPICE_INPUTS_CHANNEL(l->data);
-            spice_gtk_session_sync_keyboard_modifiers_for_channel(self, inputs, TRUE);
-        }
-    }
-    g_list_free(channels);
-}
-
-G_GNUC_INTERNAL
-void spice_gtk_session_set_pointer_grabbed(SpiceGtkSession *self, gboolean grabbed)
-{
-    g_return_if_fail(SPICE_IS_GTK_SESSION(self));
-
-    self->priv->pointer_grabbed = grabbed;
-    g_object_notify(G_OBJECT(self), "pointer-grabbed");
-}
-
-G_GNUC_INTERNAL
-gboolean spice_gtk_session_get_pointer_grabbed(SpiceGtkSession *self)
-{
-    g_return_val_if_fail(SPICE_IS_GTK_SESSION(self), FALSE);
-
-    return self->priv->pointer_grabbed;
-}
diff --git a/gtk/spice-gtk-session.h b/gtk/spice-gtk-session.h
deleted file mode 100644
index 3b4eac6..0000000
--- a/gtk/spice-gtk-session.h
+++ /dev/null
@@ -1,65 +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/>.
-*/
-#ifndef __SPICE_CLIENT_GTK_SESSION_H__
-#define __SPICE_CLIENT_GTK_SESSION_H__
-
-#include "spice-client.h"
-
-G_BEGIN_DECLS
-
-#define SPICE_TYPE_GTK_SESSION            (spice_gtk_session_get_type ())
-#define SPICE_GTK_SESSION(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), SPICE_TYPE_GTK_SESSION, SpiceGtkSession))
-#define SPICE_GTK_SESSION_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SPICE_TYPE_GTK_SESSION, SpiceGtkSessionClass))
-#define SPICE_IS_GTK_SESSION(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SPICE_TYPE_GTK_SESSION))
-#define SPICE_IS_GTK_SESSION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SPICE_TYPE_GTK_SESSION))
-#define SPICE_GTK_SESSION_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SPICE_TYPE_GTK_SESSION, SpiceGtkSessionClass))
-
-typedef struct _SpiceGtkSession SpiceGtkSession;
-typedef struct _SpiceGtkSessionClass SpiceGtkSessionClass;
-typedef struct _SpiceGtkSessionPrivate SpiceGtkSessionPrivate;
-
-struct _SpiceGtkSession
-{
-    GObject parent;
-    SpiceGtkSessionPrivate *priv;
-    /* Do not add fields to this struct */
-};
-
-struct _SpiceGtkSessionClass
-{
-    GObjectClass parent_class;
-
-    /* signals */
-
-    /*< private >*/
-    /*
-     * If adding fields to this struct, remove corresponding
-     * amount of padding to avoid changing overall struct size
-     */
-    gchar _spice_reserved[SPICE_RESERVED_PADDING];
-};
-
-GType spice_gtk_session_get_type(void);
-
-SpiceGtkSession *spice_gtk_session_get(SpiceSession *session);
-void spice_gtk_session_copy_to_guest(SpiceGtkSession *self);
-void spice_gtk_session_paste_from_guest(SpiceGtkSession *self);
-
-G_END_DECLS
-
-#endif /* __SPICE_CLIENT_GTK_SESSION_H__ */
diff --git a/gtk/spice-gtk-sym-file b/gtk/spice-gtk-sym-file
deleted file mode 100644
index 1574e07..0000000
--- a/gtk/spice-gtk-sym-file
+++ /dev/null
@@ -1,23 +0,0 @@
-spice_display_copy_to_guest
-spice_display_get_grab_keys
-spice_display_get_pixbuf
-spice_display_get_type
-spice_display_key_event_get_type
-spice_display_mouse_ungrab
-spice_display_new
-spice_display_new_with_monitor
-spice_display_paste_from_guest
-spice_display_send_keys
-spice_display_set_grab_keys
-spice_grab_sequence_as_string
-spice_grab_sequence_copy
-spice_grab_sequence_free
-spice_grab_sequence_get_type
-spice_grab_sequence_new
-spice_grab_sequence_new_from_string
-spice_gtk_session_copy_to_guest
-spice_gtk_session_get
-spice_gtk_session_get_type
-spice_gtk_session_paste_from_guest
-spice_usb_device_widget_get_type
-spice_usb_device_widget_new
diff --git a/gtk/spice-marshal.txt b/gtk/spice-marshal.txt
deleted file mode 100644
index 9c76054..0000000
--- a/gtk/spice-marshal.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-VOID:INT,INT
-VOID:INT,INT,INT
-VOID:INT,INT,INT,INT
-VOID:INT,INT,INT,INT,POINTER
-VOID:INT,INT,INT,INT,INT,POINTER
-VOID:POINTER,INT
-BOOLEAN:POINTER,UINT
-BOOLEAN:UINT
-VOID:UINT,POINTER,UINT
-VOID:UINT,UINT,POINTER,UINT
-BOOLEAN:UINT,POINTER,UINT
-BOOLEAN:UINT,UINT
-VOID:OBJECT,OBJECT
-VOID:BOXED,BOXED
diff --git a/gtk/spice-option.c b/gtk/spice-option.c
deleted file mode 100644
index 958e03c..0000000
--- a/gtk/spice-option.c
+++ /dev/null
@@ -1,284 +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 <stdlib.h>
-#include <glib-object.h>
-#include <glib/gi18n.h>
-#include "glib-compat.h"
-#include "spice-session.h"
-#include "spice-util.h"
-#include "spice-channel-priv.h"
-#include "usb-device-manager.h"
-
-static GStrv disable_effects = NULL;
-static gint color_depth = 0;
-static char *ca_file = NULL;
-static char *host_subject = NULL;
-static char *smartcard_db = NULL;
-static char *smartcard_certificates = NULL;
-static char *usbredir_auto_redirect_filter = NULL;
-static char *usbredir_redirect_on_connect = NULL;
-static gboolean smartcard = FALSE;
-static gboolean disable_audio = FALSE;
-static gboolean disable_usbredir = FALSE;
-static gint cache_size = 0;
-static gint glz_window_size = 0;
-static gchar *secure_channels = NULL;
-static gchar *shared_dir = NULL;
-
-G_GNUC_NORETURN
-static void option_version(void)
-{
-    g_print(PACKAGE_STRING "\n");
-    exit(0);
-}
-
-static gboolean option_debug(void)
-{
-    spice_util_set_debug(TRUE);
-    return TRUE;
-}
-
-static gboolean parse_color_depth(const gchar *option_name, const gchar *value,
-                                  gpointer data, GError **error)
-{
-    unsigned long parsed_depth;
-    char *end;
-
-    if (option_name == NULL) {
-        g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, _("missing color depth, must be 16 or 32"));
-        return FALSE;
-    }
-
-    parsed_depth = strtoul(value, &end, 0);
-    if (*end != '\0')
-        goto error;
-
-    if ((parsed_depth != 16) && (parsed_depth != 32))
-        goto error;
-
-    color_depth = parsed_depth;
-
-    return TRUE;
-
-error:
-    g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, _("invalid color depth (%s), must be 16 or 32"), value);
-    return FALSE;
-}
-
-static gboolean parse_disable_effects(const gchar *option_name, const gchar *value,
-                                      gpointer data, GError **error)
-{
-    GStrv it;
-
-    disable_effects = g_strsplit(value, ",", -1);
-    for (it = disable_effects; *it != NULL; it++) {
-        if ((g_strcmp0(*it, "wallpaper") != 0)
-             && (g_strcmp0(*it, "font-smooth") != 0)
-             && (g_strcmp0(*it, "animation") != 0)
-             && (g_strcmp0(*it, "all") != 0)) {
-            /* Translators: do not translate 'wallpaper', 'font-smooth',
-             * 'animation', 'all' as the user must use these values with the
-             * --spice-disable-effects command line option
-             */
-            g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
-                    _("invalid effect name (%s), must be 'wallpaper', 'font-smooth', 'animation' or 'all'"), *it);
-            g_strfreev(disable_effects);
-            disable_effects = NULL;
-            return FALSE;
-        }
-    }
-
-    return TRUE;
-}
-
-static gboolean parse_secure_channels(const gchar *option_name, const gchar *value,
-                                      gpointer data, GError **error)
-{
-    gint i;
-    gchar **channels = g_strsplit(value, ",", -1);
-
-    g_return_val_if_fail(channels != NULL, FALSE);
-
-    for (i = 0; channels[i]; i++) {
-        if (g_strcmp0(channels[i], "all") == 0)
-            continue;
-
-        if (spice_channel_string_to_type(channels[i]) == -1) {
-            gchar *supported = spice_channel_supported_string();
-            g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
-                        _("invalid channel name (%s), valid names: all, %s"),
-                        channels[i], supported);
-            g_free(supported);
-            return FALSE;
-        }
-    }
-
-    g_strfreev(channels);
-
-    secure_channels = g_strdup(value);
-
-    return TRUE;
-}
-
-
-static gboolean parse_usbredir_filter(const gchar *option_name,
-                                      const gchar *value,
-                                      gpointer data, GError **error)
-
-{
-    g_warning("--spice-usbredir-filter is deprecated, please use --spice-usbredir-auto-redirect-filter instead");
-    g_free(usbredir_auto_redirect_filter);
-    usbredir_auto_redirect_filter = g_strdup(value);
-    return TRUE;
-}
-
-
-/**
- * spice_get_option_group: (skip)
- *
- * Returns: (transfer full): a #GOptionGroup for the commandline
- * arguments specific to Spice.  You have to call
- * spice_set_session_option() after to set the options on a
- * #SpiceSession.
- **/
-GOptionGroup* spice_get_option_group(void)
-{
-    const GOptionEntry entries[] = {
-        { "spice-secure-channels", '\0', 0, G_OPTION_ARG_CALLBACK, parse_secure_channels,
-          N_("Force the specified channels to be secured"), "<main,display,inputs,...,all>" },
-        { "spice-disable-effects", '\0', 0, G_OPTION_ARG_CALLBACK, parse_disable_effects,
-          N_("Disable guest display effects"), "<wallpaper,font-smooth,animation,all>" },
-        { "spice-color-depth", '\0', 0, G_OPTION_ARG_CALLBACK, parse_color_depth,
-          N_("Guest display color depth"), "<16,32>" },
-        { "spice-ca-file", '\0', 0, G_OPTION_ARG_FILENAME, &ca_file,
-          N_("Truststore file for secure connections"), N_("<file>") },
-        { "spice-host-subject", '\0', 0, G_OPTION_ARG_STRING, &host_subject,
-          N_("Subject of the host certificate (field=value pairs separated by commas)"), N_("<host-subject>") },
-        { "spice-disable-audio", '\0', 0, G_OPTION_ARG_NONE, &disable_audio,
-          N_("Disable audio support"), NULL },
-        { "spice-smartcard", '\0', 0, G_OPTION_ARG_NONE, &smartcard,
-          N_("Enable smartcard support"), NULL },
-        { "spice-smartcard-certificates", '\0', 0, G_OPTION_ARG_STRING, &smartcard_certificates,
-          N_("Certificates to use for software smartcards (field=values separated by commas)"), N_("<certificates>") },
-        { "spice-smartcard-db", '\0', 0, G_OPTION_ARG_STRING, &smartcard_db,
-          N_("Path to the local certificate database to use for software smartcard certificates"), N_("<certificate-db>") },
-        { "spice-disable-usbredir", '\0', 0, G_OPTION_ARG_NONE, &disable_usbredir,
-          N_("Disable USB redirection support"), NULL },
-        /* Backward compats version of spice-usbredir-auto-redirect-filter */
-        { "spice-usbredir-filter", '\0', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, parse_usbredir_filter,
-          NULL, NULL },
-        { "spice-usbredir-auto-redirect-filter", '\0', 0, G_OPTION_ARG_STRING, &usbredir_auto_redirect_filter,
-          N_("Filter selecting USB devices to be auto-redirected when plugged in"), N_("<filter-string>") },
-        { "spice-usbredir-redirect-on-connect", '\0', 0, G_OPTION_ARG_STRING, &usbredir_redirect_on_connect,
-          N_("Filter selecting USB devices to redirect on connect"), N_("<filter-string>") },
-        { "spice-cache-size", '\0', 0, G_OPTION_ARG_INT, &cache_size,
-          N_("Image cache size"), N_("<bytes>") },
-        { "spice-glz-window-size", '\0', 0, G_OPTION_ARG_INT, &glz_window_size,
-          N_("Glz compression history size"), N_("<bytes>") },
-        { "spice-shared-dir", '\0', 0, G_OPTION_ARG_FILENAME, &shared_dir,
-          N_("Shared directory"), N_("<dir>") },
-
-        { "spice-debug", '\0', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, option_debug,
-          N_("Enable Spice-GTK debugging"), NULL },
-        { "spice-gtk-version", '\0', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, option_version,
-          N_("Display Spice-GTK version information"), NULL },
-        { NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL }
-    };
-    GOptionGroup *grp;
-
-    grp = g_option_group_new("spice", _("Spice Options:"), _("Show Spice Options"), NULL, NULL);
-    g_option_group_add_entries(grp, entries);
-
-    return grp;
-}
-
-/**
- * spice_set_session_option:
- * @session: a #SpiceSession to set option upon
- *
- * Set various properties on @session, according to the commandline
- * arguments given to spice_get_option_group() option group.
- **/
-void spice_set_session_option(SpiceSession *session)
-{
-    g_return_if_fail(SPICE_IS_SESSION(session));
-
-    if (ca_file == NULL) {
-        const char *homedir = g_getenv("HOME");
-        if (!homedir)
-            homedir = g_get_home_dir();
-        ca_file = g_build_filename(homedir, ".spicec", "spice_truststore.pem", NULL);
-        if (!g_file_test(ca_file, G_FILE_TEST_IS_REGULAR))
-            g_clear_pointer(&ca_file, g_free);
-    }
-
-    if (disable_effects) {
-        g_object_set(session, "disable-effects", disable_effects, NULL);
-    }
-
-    if (secure_channels) {
-        GStrv channels;
-        channels = g_strsplit(secure_channels, ",", -1);
-        if (channels)
-            g_object_set(session, "secure-channels", channels, NULL);
-        g_strfreev(channels);
-    }
-
-    if (color_depth)
-        g_object_set(session, "color-depth", color_depth, NULL);
-    if (ca_file)
-        g_object_set(session, "ca-file", ca_file, NULL);
-    if (host_subject)
-        g_object_set(session, "cert-subject", host_subject, NULL);
-    if (smartcard) {
-        g_object_set(session, "enable-smartcard", smartcard, NULL);
-        if (smartcard_certificates) {
-            GStrv certs_strv;
-            certs_strv = g_strsplit(smartcard_certificates, ",", -1);
-            if (certs_strv)
-                g_object_set(session, "smartcard-certificates", certs_strv, NULL);
-            g_strfreev(certs_strv);
-        }
-        if (smartcard_db)
-            g_object_set(session, "smartcard-db", smartcard_db, NULL);
-    }
-    if (usbredir_auto_redirect_filter) {
-        SpiceUsbDeviceManager *m = spice_usb_device_manager_get(session, NULL);
-        if (m)
-            g_object_set(m, "auto-connect-filter",
-                         usbredir_auto_redirect_filter, NULL);
-    }
-    if (usbredir_redirect_on_connect) {
-        SpiceUsbDeviceManager *m = spice_usb_device_manager_get(session, NULL);
-        if (m)
-            g_object_set(m, "redirect-on-connect",
-                         usbredir_redirect_on_connect, NULL);
-    }
-    if (disable_usbredir)
-        g_object_set(session, "enable-usbredir", FALSE, NULL);
-    if (disable_audio)
-        g_object_set(session, "enable-audio", FALSE, NULL);
-    if (cache_size)
-        g_object_set(session, "cache-size", cache_size, NULL);
-    if (glz_window_size)
-        g_object_set(session, "glz-window-size", glz_window_size, NULL);
-    if (shared_dir)
-        g_object_set(session, "shared-dir", shared_dir, NULL);
-}
diff --git a/gtk/spice-option.h b/gtk/spice-option.h
deleted file mode 100644
index ce24f65..0000000
--- a/gtk/spice-option.h
+++ /dev/null
@@ -1,31 +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_OPTION_H
-#define SPICE_OPTION_H
-
-#include <glib.h>
-#include "spice-session.h"
-
-G_BEGIN_DECLS
-
-GOptionGroup* spice_get_option_group(void);
-void spice_set_session_option(SpiceSession *session);
-
-G_END_DECLS
-
-#endif /* SPICE_OPTION_H */
diff --git a/gtk/spice-pulse.c b/gtk/spice-pulse.c
deleted file mode 100644
index 22db893..0000000
--- a/gtk/spice-pulse.c
+++ /dev/null
@@ -1,1354 +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-pulse.h"
-#include "spice-common.h"
-#include "spice-session-priv.h"
-#include "spice-channel-priv.h"
-#include "spice-util-priv.h"
-#include "glib-compat.h"
-
-#include <pulse/glib-mainloop.h>
-#include <pulse/pulseaudio.h>
-#include <pulse/ext-stream-restore.h>
-
-#define SPICE_PULSE_GET_PRIVATE(obj)                                  \
-    (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_PULSE, SpicePulsePrivate))
-
-struct async_task {
-    SpicePulse                 *pulse;
-    SpiceMainChannel           *main_channel;
-    GSimpleAsyncResult         *res;
-    GAsyncReadyCallback        callback;
-    gpointer                   user_data;
-    gboolean                   is_playback;
-    pa_operation               *pa_op;
-    gulong                     cancel_id;
-    GCancellable               *cancellable;
-};
-
-struct stream {
-    pa_sample_spec             spec;
-    pa_stream                  *stream;
-    int                        state;
-    pa_operation               *uncork_op;
-    pa_operation               *cork_op;
-    gboolean                   started;
-    guint                      num_underflow;
-    gboolean                   info_updated;
-    gchar                      *name;
-    pa_ext_stream_restore_info info;
-};
-
-struct _SpicePulsePrivate {
-    SpiceChannel            *pchannel;
-    SpiceChannel            *rchannel;
-
-    pa_glib_mainloop        *mainloop;
-    pa_context              *context;
-    int                     state;
-    struct stream           playback;
-    struct stream           record;
-    guint                   last_delay;
-    guint                   target_delay;
-    struct async_task       *pending_restore_task;
-    GList                   *results;
-};
-
-G_DEFINE_TYPE(SpicePulse, spice_pulse, SPICE_TYPE_AUDIO)
-
-static const char *stream_state_names[] = {
-    [ PA_STREAM_UNCONNECTED ] = "unconnected",
-    [ PA_STREAM_CREATING    ] = "creating",
-    [ PA_STREAM_READY       ] = "ready",
-    [ PA_STREAM_FAILED      ] = "failed",
-    [ PA_STREAM_TERMINATED  ] = "terminated",
-};
-
-static const char *context_state_names[] = {
-    [ PA_CONTEXT_UNCONNECTED  ] = "unconnected",
-    [ PA_CONTEXT_CONNECTING   ] = "connecting",
-    [ PA_CONTEXT_AUTHORIZING  ] = "authorizing",
-    [ PA_CONTEXT_SETTING_NAME ] = "setting_name",
-    [ PA_CONTEXT_READY        ] = "ready",
-    [ PA_CONTEXT_FAILED       ] = "failed",
-    [ PA_CONTEXT_TERMINATED   ] = "terminated",
-};
-#define STATE_NAME(array, state) \
-    ((state < G_N_ELEMENTS(array)) ? array[state] : NULL)
-
-static void stream_stop(SpicePulse *pulse, struct stream *s);
-static gboolean connect_channel(SpiceAudio *audio, SpiceChannel *channel);
-static void channel_weak_notified(gpointer data, GObject *where_the_object_was);
-static void spice_pulse_get_playback_volume_info_async(SpiceAudio *audio, GCancellable *cancellable,
-        SpiceMainChannel *main_channel, GAsyncReadyCallback callback, gpointer user_data);
-static gboolean spice_pulse_get_playback_volume_info_finish(SpiceAudio *audio, GAsyncResult *res,
-        gboolean *mute, guint8 *nchannels, guint16 **volume, GError **error);
-static void spice_pulse_get_record_volume_info_async(SpiceAudio *audio, GCancellable *cancellable,
-        SpiceMainChannel *main_channel, GAsyncReadyCallback callback, gpointer user_data);
-static gboolean spice_pulse_get_record_volume_info_finish(SpiceAudio *audio,GAsyncResult *res,
-        gboolean *mute, guint8 *nchannels, guint16 **volume, GError **error);
-static void stream_restore_read_cb(pa_context *context,
-        const pa_ext_stream_restore_info *info, int eol, void *userdata);
-static void spice_pulse_complete_async_task(struct async_task *task, const gchar *err_msg);
-static void spice_pulse_complete_all_async_tasks(SpicePulse *pulse, const gchar *err_msg);
-
-static void spice_pulse_finalize(GObject *obj)
-{
-    SpicePulse *pulse = SPICE_PULSE(obj);
-    SpicePulsePrivate *p;
-
-    p = pulse->priv;
-
-    if (p->context != NULL)
-        pa_context_unref(p->context);
-
-    if (p->mainloop != NULL)
-        pa_glib_mainloop_free(p->mainloop);
-
-    G_OBJECT_CLASS(spice_pulse_parent_class)->finalize(obj);
-}
-
-static void spice_pulse_dispose(GObject *obj)
-{
-    SpicePulse *pulse = SPICE_PULSE(obj);
-    SpicePulsePrivate *p;
-
-    SPICE_DEBUG("%s", __FUNCTION__);
-    p = pulse->priv;
-
-    if (p->playback.uncork_op)
-        pa_operation_unref(p->playback.uncork_op);
-    p->playback.uncork_op = NULL;
-
-    if (p->playback.cork_op)
-        pa_operation_unref(p->playback.cork_op);
-    p->playback.cork_op = NULL;
-
-    if (p->record.uncork_op)
-        pa_operation_unref(p->record.uncork_op);
-    p->record.uncork_op = NULL;
-
-    if (p->record.cork_op)
-        pa_operation_unref(p->record.cork_op);
-    p->record.cork_op = NULL;
-
-    if (p->results != NULL)
-        spice_pulse_complete_all_async_tasks(pulse, "PulseAudio is being dispose");
-
-    g_clear_pointer(&p->playback.name, g_free);
-    g_clear_pointer(&p->record.name, g_free);
-
-    if (p->pchannel)
-        g_object_weak_unref(G_OBJECT(p->pchannel), channel_weak_notified, pulse);
-    p->pchannel = NULL;
-
-    if (p->rchannel)
-        g_object_weak_unref(G_OBJECT(p->rchannel), channel_weak_notified, pulse);
-    p->rchannel = NULL;
-
-    G_OBJECT_CLASS(spice_pulse_parent_class)->dispose(obj);
-}
-
-static void spice_pulse_init(SpicePulse *pulse)
-{
-    pulse->priv = SPICE_PULSE_GET_PRIVATE(pulse);
-}
-
-static void spice_pulse_class_init(SpicePulseClass *klass)
-{
-    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
-    SpiceAudioClass *audio_class = SPICE_AUDIO_CLASS(klass);
-
-    audio_class->connect_channel = connect_channel;
-    audio_class->get_playback_volume_info_async = spice_pulse_get_playback_volume_info_async;
-    audio_class->get_playback_volume_info_finish = spice_pulse_get_playback_volume_info_finish;
-    audio_class->get_record_volume_info_async = spice_pulse_get_record_volume_info_async;
-    audio_class->get_record_volume_info_finish = spice_pulse_get_record_volume_info_finish;
-
-    gobject_class->finalize = spice_pulse_finalize;
-    gobject_class->dispose = spice_pulse_dispose;
-
-    g_type_class_add_private(klass, sizeof(SpicePulsePrivate));
-}
-
-/* ------------------------------------------------------------------ */
-static void pulse_uncork_cb(pa_stream *pastream, int success, void *data)
-{
-    struct stream *s = data;
-
-    if (!success)
-        g_warning("pulseaudio uncork operation failed");
-
-    pa_operation_unref(s->uncork_op);
-    s->uncork_op = NULL;
-}
-
-static void stream_uncork(SpicePulse *pulse, struct stream *s)
-{
-    SpicePulsePrivate *p = pulse->priv;
-    pa_operation *o = NULL;
-
-    g_return_if_fail(s->stream);
-
-    if (s->cork_op) {
-        pa_operation_cancel(s->cork_op);
-        pa_operation_unref(s->cork_op);
-        s->cork_op = NULL;
-    }
-
-    if (pa_stream_is_corked(s->stream) && !s->uncork_op) {
-        if (!(o = pa_stream_cork(s->stream, 0, pulse_uncork_cb, s))) {
-            g_warning("pa_stream_uncork() failed: %s",
-                      pa_strerror(pa_context_errno(p->context)));
-        }
-        s->uncork_op = o;
-    }
-}
-
-static void pulse_flush_cb(pa_stream *pastream, int success, void *data)
-{
-    struct stream *s = data;
-
-    if (!success)
-        g_warning("pulseaudio flush operation failed");
-
-    pa_operation_unref(s->cork_op);
-    s->cork_op = NULL;
-}
-
-static void pulse_cork_flush_cb(pa_stream *pastream, int success, void *data)
-{
-    struct stream *s = data;
-
-    if (!success)
-        g_warning("pulseaudio cork operation failed");
-
-    pa_operation_unref(s->cork_op);
-
-    if (!(s->cork_op = pa_stream_flush(s->stream, pulse_flush_cb, s))) {
-        g_warning("pa_stream_flush() failed");
-    }
-}
-
-static void pulse_cork_cb(pa_stream *pastream, int success, void *data)
-{
-    struct stream *s = data;
-
-    SPICE_DEBUG("%s: cork started", __FUNCTION__);
-    if (!success)
-        g_warning("pulseaudio cork operation failed");
-
-    pa_operation_unref(s->cork_op);
-    s->cork_op = NULL;
-}
-
-static void stream_cork(SpicePulse *pulse, struct stream *s, gboolean with_flush)
-{
-    SpicePulsePrivate *p = pulse->priv;
-    pa_operation *o = NULL;
-
-    if (s->uncork_op) {
-        pa_operation_cancel(s->uncork_op);
-        pa_operation_unref(s->uncork_op);
-        s->uncork_op = NULL;
-    }
-
-    if (!pa_stream_is_corked(s->stream) && !s->cork_op) {
-        if (!(o = pa_stream_cork(s->stream, 1,
-                                 with_flush ? pulse_cork_flush_cb :
-                                              pulse_cork_cb,
-                                 s))) {
-            g_warning("pa_stream_cork() failed: %s",
-                      pa_strerror(pa_context_errno(p->context)));
-        }
-        s->cork_op = o;
-    }
-}
-
-static void stream_stop(SpicePulse *pulse, struct stream *s)
-{
-    SpicePulsePrivate *p = pulse->priv;
-
-    if (pa_stream_disconnect(s->stream) < 0) {
-        g_warning("pa_stream_disconnect() failed: %s",
-                  pa_strerror(pa_context_errno(p->context)));
-    }
-    pa_stream_unref(s->stream);
-    s->stream = NULL;
-}
-
-static void stream_state_callback(pa_stream *s, void *userdata)
-{
-    SpicePulse *pulse = userdata;
-    SpicePulsePrivate *p;
-
-    p = pulse->priv;
-
-    g_return_if_fail(p != NULL);
-    g_return_if_fail(s != NULL);
-
-    switch (pa_stream_get_state(s)) {
-        case PA_STREAM_CREATING:
-        case PA_STREAM_TERMINATED:
-        case PA_STREAM_READY:
-            break;
-        case PA_STREAM_FAILED:
-        default:
-            g_warning("Stream error: %s", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
-    }
-}
-
-static void stream_underflow_cb(pa_stream *s, void *userdata)
-{
-    SpicePulse *pulse = userdata;
-    SpicePulsePrivate *p;
-
-    SPICE_DEBUG("PA stream underflow!!");
-
-    p = pulse->priv;
-    g_return_if_fail(p != NULL);
-    p->playback.num_underflow++;
-#ifdef PULSE_ADJUST_LATENCY
-    const pa_buffer_attr *buffer_attr;
-    pa_buffer_attr new_buffer_attr;
-    pa_operation *op;
-
-    buffer_attr = pa_stream_get_buffer_attr(s);
-    g_return_if_fail(buffer_attr != NULL);
-
-    new_buffer_attr = *buffer_attr;
-    new_buffer_attr.tlength *= 2;
-    new_buffer_attr.minreq *= 2;
-    op = pa_stream_set_buffer_attr(s, &new_buffer_attr, NULL, NULL);
-    pa_operation_unref(op);
-#endif
-}
-
-static void stream_update_latency_callback(pa_stream *s, void *userdata)
-{
-    SpicePulse *pulse = userdata;
-    pa_usec_t usec;
-    int negative = 0;
-    SpicePulsePrivate *p;
-
-    p = pulse->priv;
-
-    g_return_if_fail(s != NULL);
-    g_return_if_fail(p != NULL);
-
-    if (!p->playback.stream || !p->playback.started)
-        return;
-
-    if (pa_stream_get_latency(s, &usec, &negative) < 0) {
-        g_warning("Failed to get latency: %s", pa_strerror(pa_context_errno(p->context)));
-        return;
-    }
-
-    g_return_if_fail(negative == FALSE);
-    p->last_delay = usec / PA_USEC_PER_MSEC;
-    spice_playback_channel_set_delay(SPICE_PLAYBACK_CHANNEL(p->pchannel), usec / 1000);
-    if (pa_stream_is_corked(p->playback.stream)) {
-        if (p->last_delay >= p->target_delay) {
-            SPICE_DEBUG("%s: uncork playback. delay %u target %u",  __FUNCTION__, p->last_delay, p->target_delay);
-            stream_uncork(pulse, &p->playback);
-        } else {
-            SPICE_DEBUG("%s: still corked. delay %u target %u",  __FUNCTION__, p->last_delay, p->target_delay);
-        }
-    }
-}
-
-static void create_playback(SpicePulse *pulse)
-{
-    SpicePulsePrivate *p = pulse->priv;
-    pa_stream_flags_t flags;
-    pa_buffer_attr buffer_attr = { 0, };
-
-    g_return_if_fail(p != NULL);
-    g_return_if_fail(p->context != NULL);
-    g_return_if_fail(p->playback.stream == NULL);
-    g_return_if_fail(pa_context_get_state(p->context) == PA_CONTEXT_READY);
-
-    p->playback.state = PA_STREAM_READY;
-    p->playback.stream = pa_stream_new(p->context, "playback",
-                                       &p->playback.spec, NULL);
-    pa_stream_set_state_callback(p->playback.stream, stream_state_callback, pulse);
-    pa_stream_set_underflow_callback(p->playback.stream, stream_underflow_cb, pulse);
-    pa_stream_set_latency_update_callback(p->playback.stream, stream_update_latency_callback, pulse);
-
-    buffer_attr.maxlength = -1;
-    buffer_attr.tlength = pa_usec_to_bytes(p->target_delay * PA_USEC_PER_MSEC, &p->playback.spec);
-    buffer_attr.prebuf = -1;
-    buffer_attr.minreq = -1;
-    flags = PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE;
-
-    if (pa_stream_connect_playback(p->playback.stream,
-                                   NULL, &buffer_attr, flags, NULL, NULL) < 0) {
-        g_warning("pa_stream_connect_playback() failed: %s",
-                  pa_strerror(pa_context_errno(p->context)));
-    }
-}
-
-static void playback_start(SpicePlaybackChannel *channel, gint format, gint channels,
-                           gint frequency, gpointer data)
-{
-    SpicePulse *pulse = data;
-    SpicePulsePrivate *p = pulse->priv;
-    pa_context_state_t state;
-    guint latency;
-
-    g_return_if_fail(p != NULL);
-
-    p->playback.started = TRUE;
-    p->playback.num_underflow = 0;
-    g_object_get(p->pchannel, "min-latency", &latency, NULL);
-
-    if (p->playback.stream &&
-        (p->playback.spec.rate != frequency ||
-         p->playback.spec.channels != channels ||
-         p->target_delay != latency)) {
-        stream_stop(pulse, &p->playback);
-    }
-
-    g_return_if_fail(format == SPICE_AUDIO_FMT_S16);
-    p->playback.spec.format   = PA_SAMPLE_S16LE;
-    p->playback.spec.rate     = frequency;
-    p->playback.spec.channels = channels;
-    p->target_delay = latency;
-    p->last_delay = 0;
-
-    state = pa_context_get_state(p->context);
-    switch (state) {
-    case PA_CONTEXT_READY:
-        if (p->state != state) {
-            SPICE_DEBUG("%s: pulse context ready", __FUNCTION__);
-        }
-        if (p->playback.stream == NULL) {
-            create_playback(pulse);
-        } else
-            stream_uncork(pulse, &p->playback);
-        break;
-    default:
-        if (p->state != state) {
-            SPICE_DEBUG("%s: pulse context not ready (%s)",
-                        __FUNCTION__, STATE_NAME(context_state_names, state));
-        }
-        break;
-    }
-    p->state = state;
-}
-
-static void playback_data(SpicePlaybackChannel *channel,
-                          gpointer *audio, gint size,
-                          gpointer data)
-{
-    SpicePulse *pulse = data;
-    SpicePulsePrivate *p = pulse->priv;
-    pa_stream_state_t state;
-
-    if (!p->playback.stream)
-        return;
-
-    state = pa_stream_get_state(p->playback.stream);
-    switch (state) {
-    case PA_STREAM_CREATING:
-        SPICE_DEBUG("stream creating, dropping data");
-        break;
-    case PA_STREAM_READY:
-        if (p->playback.state != state) {
-            SPICE_DEBUG("%s: pulse playback stream ready", __FUNCTION__);
-        }
-        if (pa_stream_write(p->playback.stream, audio, size, NULL, 0, PA_SEEK_RELATIVE) < 0) {
-            g_warning("pa_stream_write() failed: %s",
-                      pa_strerror(pa_context_errno(p->context)));
-        }
-        break;
-    default:
-        if (p->playback.state != state) {
-            SPICE_DEBUG("%s: pulse playback stream not ready (%s)",
-                        __FUNCTION__, STATE_NAME(stream_state_names, state));
-        }
-        break;
-    }
-    p->playback.state = state;
-}
-
-static void playback_stop(SpicePulse *pulse)
-{
-    SpicePulsePrivate *p = pulse->priv;
-
-    SPICE_DEBUG("%s: #underflow %u", __FUNCTION__, p->playback.num_underflow);
-
-    p->playback.started = FALSE;
-    if (!p->playback.stream)
-        return;
-
-    stream_cork(pulse, &p->playback, TRUE);
-}
-
-static void stream_read_callback(pa_stream *s, size_t length, void *data)
-{
-    SpicePulse *pulse = data;
-    SpicePulsePrivate *p = pulse->priv;
-
-    g_return_if_fail(p != NULL);
-
-    while (pa_stream_readable_size(s) > 0) {
-        const void *snddata;
-
-        if (pa_stream_peek(s, &snddata, &length) < 0) {
-            g_warning("pa_stream_peek() failed: %s",
-                      pa_strerror(pa_context_errno(p->context)));
-            return;
-        }
-
-        g_return_if_fail(snddata);
-        g_return_if_fail(length > 0);
-
-        if (p->rchannel != NULL)
-            spice_record_send_data(SPICE_RECORD_CHANNEL(p->rchannel),
-                                   /* FIXME: server side doesn't care about ts?
-                                      what is the unit? ms apparently */
-                                   (gpointer)snddata, length, 0);
-
-        if (pa_stream_drop(s) < 0) {
-            g_warning("pa_stream_drop() failed: %s",
-                      pa_strerror(pa_context_errno(p->context)));
-            return;
-        }
-    }
-}
-
-static void create_record(SpicePulse *pulse)
-{
-    SpicePulsePrivate *p = pulse->priv;
-    pa_buffer_attr buffer_attr = { 0, };
-    pa_stream_flags_t flags;
-
-    g_return_if_fail(p != NULL);
-    g_return_if_fail(p->context != NULL);
-    g_return_if_fail(p->record.stream == NULL);
-    g_return_if_fail(pa_context_get_state(p->context) == PA_CONTEXT_READY);
-
-    p->record.state = PA_STREAM_READY;
-    p->record.stream = pa_stream_new(p->context, "record",
-                                     &p->record.spec, NULL);
-    pa_stream_set_read_callback(p->record.stream, stream_read_callback, pulse);
-    pa_stream_set_state_callback(p->record.stream, stream_state_callback, pulse);
-
-    /* FIXME: we might want customizable latency */
-    buffer_attr.maxlength = -1;
-    buffer_attr.prebuf = -1;
-    buffer_attr.fragsize = buffer_attr.tlength = pa_usec_to_bytes(20 * PA_USEC_PER_MSEC, &p->record.spec);
-    buffer_attr.minreq = (uint32_t) -1;
-    flags = PA_STREAM_ADJUST_LATENCY;
-
-    if (pa_stream_connect_record(p->record.stream, NULL, &buffer_attr, flags) < 0) {
-        g_warning("pa_stream_connect_record() failed: %s",
-                  pa_strerror(pa_context_errno(p->context)));
-    }
-}
-
-static void record_start(SpiceRecordChannel *channel, gint format, gint channels,
-                         gint frequency, gpointer data)
-{
-    SpicePulse *pulse = data;
-    SpicePulsePrivate *p = pulse->priv;
-    pa_context_state_t state;
-
-    p->record.started = TRUE;
-
-    if (p->record.stream &&
-        (p->record.spec.rate != frequency ||
-         p->record.spec.channels != channels)) {
-        stream_stop(pulse, &p->record);
-    }
-
-    g_return_if_fail(format == SPICE_AUDIO_FMT_S16);
-    p->record.spec.format = PA_SAMPLE_S16LE;
-    p->record.spec.rate = frequency;
-    p->record.spec.channels = channels;
-
-    state = pa_context_get_state(p->context);
-    switch (state) {
-    case PA_CONTEXT_READY:
-        if (p->state != state) {
-            SPICE_DEBUG("%s: pulse context ready", __FUNCTION__);
-        }
-        if (p->record.stream == NULL) {
-            create_record(pulse);
-        } else
-            stream_uncork(pulse, &p->record);
-        break;
-    default:
-        if (p->state != state) {
-            g_warning("%s: pulse context not ready (%s)",
-                      __FUNCTION__, STATE_NAME(context_state_names, state));
-        }
-        break;
-    }
-    p->state = state;
-}
-
-static void record_stop(SpicePulse *pulse)
-{
-    SpicePulsePrivate *p = pulse->priv;
-
-    SPICE_DEBUG("%s", __FUNCTION__);
-
-    p->record.started = FALSE;
-    if (!p->record.stream)
-        return;
-
-    stream_stop(pulse, &p->record);
-}
-
-static void playback_volume_changed(GObject *object, GParamSpec *pspec, gpointer data)
-{
-    SpicePulse *pulse = data;
-    SpicePulsePrivate *p = pulse->priv;
-    guint16 *volume;
-    guint nchannels;
-    pa_operation *op;
-    pa_cvolume v;
-    guint i;
-
-    g_object_get(object,
-                 "volume", &volume,
-                 "nchannels", &nchannels,
-                 NULL);
-
-    pa_cvolume_init(&v);
-    v.channels = p->playback.spec.channels;
-    for (i = 0; i < nchannels; ++i) {
-        v.values[i] = (PA_VOLUME_NORM - PA_VOLUME_MUTED) * volume[i] / G_MAXUINT16;
-        SPICE_DEBUG("playback volume changed %u", v.values[i]);
-    }
-
-    if (!p->playback.stream ||
-        pa_stream_get_index(p->playback.stream) == PA_INVALID_INDEX)
-        return;
-
-    op = pa_context_set_sink_input_volume(p->context,
-        pa_stream_get_index(p->playback.stream),
-        &v, NULL, NULL);
-    if (!op)
-        g_warning("set_sink_input_volume() failed: %s",
-                  pa_strerror(pa_context_errno(p->context)));
-    else
-        pa_operation_unref(op);
-}
-
-static void playback_mute_changed(GObject *object, GParamSpec *pspec, gpointer data)
-{
-    SpicePulse *pulse = data;
-    SpicePulsePrivate *p = pulse->priv;
-    gboolean mute;
-    pa_operation *op;
-
-    g_object_get(object, "mute", &mute, NULL);
-    SPICE_DEBUG("playback mute changed %u", mute);
-
-    if (!p->playback.stream ||
-        pa_stream_get_index(p->playback.stream) == PA_INVALID_INDEX)
-        return;
-
-    op = pa_context_set_sink_input_mute(p->context,
-        pa_stream_get_index(p->playback.stream),
-        mute, NULL, NULL);
-    if (!op)
-        g_warning("set_sink_input_mute() failed: %s",
-                  pa_strerror(pa_context_errno(p->context)));
-    else
-        pa_operation_unref(op);
-}
-
-static void playback_min_latency_changed(GObject *object, GParamSpec *pspec, gpointer data)
-{
-
-    SpicePulse *pulse = data;
-    SpicePulsePrivate *p = pulse->priv;
-    guint min_latency;
-
-    g_object_get(object, "min-latency", &min_latency, NULL);
-    p->target_delay = min_latency;
-
-    if (p->last_delay < p->target_delay) {
-        spice_debug("%s: corking", __FUNCTION__);
-        if (p->playback.stream)
-            stream_cork(pulse, &p->playback, FALSE);
-    } else {
-        spice_debug("%s: not corking. The current delay satisfies the requirement", __FUNCTION__);
-    }
-}
-
-static void record_mute_changed(GObject *object, GParamSpec *pspec, gpointer data)
-{
-    SpicePulse *pulse = data;
-    SpicePulsePrivate *p = pulse->priv;
-    gboolean mute;
-    pa_operation *op;
-
-    g_object_get(object, "mute", &mute, NULL);
-    SPICE_DEBUG("record mute changed %u", mute);
-
-    if (!p->record.stream ||
-        pa_stream_get_device_index(p->record.stream) == PA_INVALID_INDEX)
-        return;
-
-#if PA_CHECK_VERSION(1,0,0)
-    op = pa_context_set_source_output_mute(p->context,
-        pa_stream_get_index(p->record.stream),
-#else
-    op = pa_context_set_source_mute_by_index(p->context,
-        pa_stream_get_device_index(p->record.stream),
-#endif
-        mute, NULL, NULL);
-    if (!op)
-        g_warning("set_source_output_mute() failed: %s",
-                  pa_strerror(pa_context_errno(p->context)));
-    else
-        pa_operation_unref(op);
-}
-
-static void record_volume_changed(GObject *object, GParamSpec *pspec, gpointer data)
-{
-    SpicePulse *pulse = data;
-    SpicePulsePrivate *p = pulse->priv;
-    guint16 *volume;
-    guint nchannels;
-    pa_operation *op;
-    pa_cvolume v;
-    guint i;
-
-    g_object_get(object,
-                 "volume", &volume,
-                 "nchannels", &nchannels,
-                 NULL);
-
-    pa_cvolume_init(&v);
-    v.channels = p->record.spec.channels;
-    for (i = 0; i < nchannels; ++i) {
-        v.values[i] = (PA_VOLUME_NORM - PA_VOLUME_MUTED) * volume[i] / G_MAXUINT16;
-        SPICE_DEBUG("record volume changed %u", v.values[i]);
-    }
-
-    if (!p->record.stream ||
-        pa_stream_get_device_index(p->record.stream) == PA_INVALID_INDEX)
-        return;
-
-#if PA_CHECK_VERSION(1,0,0)
-    op = pa_context_set_source_output_volume(p->context,
-        pa_stream_get_index(p->record.stream),
-#else
-    op = pa_context_set_source_volume_by_index(p->context,
-        pa_stream_get_device_index(p->record.stream),
-#endif
-        &v, NULL, NULL);
-    if (!op)
-        g_warning("set_source_output_volume() failed: %s",
-                  pa_strerror(pa_context_errno(p->context)));
-    else
-        pa_operation_unref(op);
-}
-
-static void
-channel_weak_notified(gpointer data,
-                      GObject *where_the_object_was)
-{
-    SpicePulse *pulse = SPICE_PULSE(data);
-    SpicePulsePrivate *p = pulse->priv;
-
-    if (where_the_object_was == (GObject *)p->pchannel) {
-        SPICE_DEBUG("playback closed");
-        playback_stop(pulse);
-        p->pchannel = NULL;
-    } else if (where_the_object_was == (GObject *)p->rchannel) {
-        SPICE_DEBUG("record closed");
-        record_stop(pulse);
-        p->rchannel = NULL;
-    }
-}
-
-static gboolean connect_channel(SpiceAudio *audio, SpiceChannel *channel)
-{
-    SpicePulse *pulse = SPICE_PULSE(audio);
-    SpicePulsePrivate *p = pulse->priv;
-
-    if (SPICE_IS_PLAYBACK_CHANNEL(channel)) {
-        g_return_val_if_fail(p->pchannel == NULL, FALSE);
-
-        p->pchannel = channel;
-        g_object_weak_ref(G_OBJECT(p->pchannel), channel_weak_notified, audio);
-        spice_g_signal_connect_object(channel, "playback-start",
-                                      G_CALLBACK(playback_start), pulse, 0);
-        spice_g_signal_connect_object(channel, "playback-data",
-                                      G_CALLBACK(playback_data), pulse, 0);
-        spice_g_signal_connect_object(channel, "playback-stop",
-                                      G_CALLBACK(playback_stop), pulse, G_CONNECT_SWAPPED);
-        spice_g_signal_connect_object(channel, "notify::volume",
-                                      G_CALLBACK(playback_volume_changed), pulse, 0);
-        spice_g_signal_connect_object(channel, "notify::mute",
-                                      G_CALLBACK(playback_mute_changed), pulse, 0);
-        spice_g_signal_connect_object(channel, "notify::min-latency",
-                                      G_CALLBACK(playback_min_latency_changed), pulse, 0);
-
-        return TRUE;
-    }
-
-    if (SPICE_IS_RECORD_CHANNEL(channel)) {
-        g_return_val_if_fail(p->rchannel == NULL, FALSE);
-
-        p->rchannel = channel;
-        g_object_weak_ref(G_OBJECT(p->rchannel), channel_weak_notified, audio);
-        spice_g_signal_connect_object(channel, "record-start",
-                                      G_CALLBACK(record_start), pulse, 0);
-        spice_g_signal_connect_object(channel, "record-stop",
-                                      G_CALLBACK(record_stop), pulse, G_CONNECT_SWAPPED);
-        spice_g_signal_connect_object(channel, "notify::volume",
-                                      G_CALLBACK(record_volume_changed), pulse, 0);
-        spice_g_signal_connect_object(channel, "notify::mute",
-                                      G_CALLBACK(record_mute_changed), pulse, 0);
-
-        return TRUE;
-    }
-
-    return FALSE;
-}
-
-static void context_state_callback(pa_context *c, void *userdata)
-{
-    SpicePulse *pulse = userdata;
-    SpicePulsePrivate *p;
-
-    p = pulse->priv;
-
-    g_return_if_fail(p != NULL);
-    g_return_if_fail(c != NULL);
-    switch (pa_context_get_state(c)) {
-    case PA_CONTEXT_CONNECTING:
-    case PA_CONTEXT_AUTHORIZING:
-    case PA_CONTEXT_SETTING_NAME:
-    case PA_CONTEXT_UNCONNECTED:
-        break;
-
-    case PA_CONTEXT_READY: {
-        if (!p->record.stream && p->record.started)
-            create_record(SPICE_PULSE(userdata));
-
-        if (!p->playback.stream && p->playback.started)
-            create_playback(SPICE_PULSE(userdata));
-
-        if (p->pending_restore_task != NULL &&
-                p->pending_restore_task->pa_op == NULL) {
-            pa_operation *op = pa_ext_stream_restore_read(p->context,
-                                                          stream_restore_read_cb,
-                                                          pulse);
-            if (!op)
-                goto context_fail;
-            p->pending_restore_task->pa_op = op;
-        }
-        break;
-    }
-
-    case PA_CONTEXT_FAILED:
-        g_warning("PulseAudio context failed %s",
-                  pa_strerror(pa_context_errno(p->context)));
-        goto context_fail;
-
-    case PA_CONTEXT_TERMINATED:
-    default:
-        SPICE_DEBUG("PulseAudio context terminated");
-        goto context_fail;
-    }
-
-    return;
-
-context_fail:
-    if (p->pending_restore_task != NULL) {
-        const gchar *errmsg = pa_strerror(pa_context_errno(p->context));
-        errmsg = (errmsg != NULL) ? errmsg : "PulseAudio context terminated";
-        spice_pulse_complete_all_async_tasks(pulse, errmsg);
-    }
-}
-
-SpicePulse *spice_pulse_new(SpiceSession *session, GMainContext *context,
-                            const char *name)
-{
-    SpicePulse *pulse;
-    SpicePulsePrivate *p;
-
-    pulse = g_object_new(SPICE_TYPE_PULSE,
-                         "session", session,
-                         "main-context", context,
-                         NULL);
-    p = pulse->priv;
-
-    p->mainloop = pa_glib_mainloop_new(context);
-    p->state = PA_CONTEXT_READY;
-    p->context = pa_context_new(pa_glib_mainloop_get_api(p->mainloop), name);
-    pa_context_set_state_callback(p->context, context_state_callback, pulse);
-    if (pa_context_connect(p->context, NULL, 0, NULL) < 0) {
-        g_warning("pa_context_connect() failed: %s",
-            pa_strerror(pa_context_errno(p->context)));
-        goto error;
-    }
-
-    p->playback.name = g_strconcat("sink-input-by-application-name:",
-                                   g_get_application_name(), NULL);
-    p->record.name = g_strconcat("source-output-by-application-name:",
-                                 g_get_application_name(), NULL);
-    return pulse;
-
-error:
-    g_object_unref(pulse);
-    return  NULL;
-}
-
-static gboolean free_async_task(gpointer user_data)
-{
-    struct async_task *task = user_data;
-
-    if (task == NULL)
-        return G_SOURCE_REMOVE;
-
-    if (task->pa_op != NULL) {
-        pa_operation_cancel(task->pa_op);
-        pa_operation_unref(task->pa_op);
-        task->pa_op = NULL;
-    }
-
-    if (task->pulse) {
-        if (task->pulse->priv->pending_restore_task == task) {
-            task->pulse->priv->pending_restore_task = NULL;
-        }
-        g_object_unref(task->pulse);
-    }
-
-    if (task->res)
-        g_object_unref(task->res);
-
-    if (task->main_channel)
-        g_object_unref(task->main_channel);
-
-    if (task->pa_op != NULL)
-        pa_operation_unref(task->pa_op);
-
-    if (task->cancel_id != 0) {
-        g_cancellable_disconnect(task->cancellable, task->cancel_id);
-        g_clear_object(&task->cancellable);
-    }
-
-    g_free(task);
-    return G_SOURCE_REMOVE;
-}
-
-static void cancel_task(GCancellable *cancellable, gpointer user_data)
-{
-    struct async_task *task = user_data;
-    g_return_if_fail(task != NULL);
-
-#if GLIB_CHECK_VERSION(2,40,0)
-    free_async_task(task);
-#else
-    /* This must be done now otherwise pulseaudio may return to a
-     * cancelled task operation before free_async_task is called */
-    if (task->pa_op != NULL) {
-        pa_operation_cancel(task->pa_op);
-        pa_operation_unref(task->pa_op);
-        task->pa_op = NULL;
-    }
-
-    /* Clear the pending_restore_task reference to avoid triggering a
-     * pa_operation when context state is in READY state */
-    if (task->pulse->priv->pending_restore_task == task) {
-        task->pulse->priv->pending_restore_task = NULL;
-    }
-
-#if !GLIB_CHECK_VERSION(2,32,0)
-    /* g_simple_async_result_set_check_cancellable is not present. Set an error
-     * in the GSimpleAsyncResult in case of _finish functions is called */
-    g_simple_async_result_set_error(task->res,
-                                    SPICE_CLIENT_ERROR,
-                                    SPICE_CLIENT_ERROR_FAILED,
-                                    "Operation was cancelled");
-#endif
-    /* FIXME: https://bugzilla.gnome.org/show_bug.cgi?id=705395
-     * Free the memory in idle */
-    g_idle_add(free_async_task, task);
-#endif
-}
-
-static void complete_task(SpicePulse *pulse, struct async_task *task, const gchar *err_msg)
-{
-    SpicePulsePrivate *p = pulse->priv;
-
-    /* If we do have any err_msg, we failed */
-    if (err_msg != NULL) {
-        g_simple_async_result_set_op_res_gboolean(task->res, FALSE);
-        g_simple_async_result_set_error(task->res,
-                                        SPICE_CLIENT_ERROR,
-                                        SPICE_CLIENT_ERROR_FAILED,
-                                        "restore-info failed due %s",
-                                        err_msg);
-    /* Volume-info does not change if stream is not found */
-    } else if ((task->is_playback == TRUE && p->playback.info_updated == FALSE) ||
-               (task->is_playback == FALSE && p->record.info_updated == FALSE)) {
-        g_simple_async_result_set_op_res_gboolean(task->res, FALSE);
-        g_simple_async_result_set_error(task->res,
-                                        SPICE_CLIENT_ERROR,
-                                        SPICE_CLIENT_ERROR_FAILED,
-                                        "Stream not found by pulse");
-    } else {
-        g_simple_async_result_set_op_res_gboolean(task->res, TRUE);
-    }
-
-    /* As all async calls to PulseAudio are done with glib mainloop, it is
-     * safe to complete the operation synchronously here. */
-    g_simple_async_result_complete(task->res);
-}
-
-static void spice_pulse_complete_async_task(struct async_task *task, const gchar *err_msg)
-{
-    SpicePulsePrivate *p;
-
-    g_return_if_fail(task != NULL);
-    p = task->pulse->priv;
-
-    complete_task(task->pulse, task, err_msg);
-    if (p->results != NULL) {
-        p->results = g_list_remove(p->results, task);
-        SPICE_DEBUG("Number of async task is %d", g_list_length(p->results));
-    }
-    free_async_task(task);
-}
-
-static void spice_pulse_complete_all_async_tasks(SpicePulse *pulse, const gchar *err_msg)
-{
-    SpicePulsePrivate *p;
-    GList *it;
-
-    g_return_if_fail(pulse != NULL);
-    p = pulse->priv;
-
-    /* Complete all tasks in list */
-    for(it = p->results; it != NULL; it = it->next) {
-        struct async_task *task = it->data;
-        complete_task(pulse, task, err_msg);
-        free_async_task(task);
-    }
-    g_list_free(p->results);
-    p->results = NULL;
-    SPICE_DEBUG("All async tasks completed");
-}
-
-static void stream_restore_read_cb(pa_context *context,
-                                   const pa_ext_stream_restore_info *info,
-                                   int eol,
-                                   void *userdata)
-{
-    SpicePulsePrivate *p = SPICE_PULSE(userdata)->priv;
-    struct stream *pstream = NULL;
-
-    if (eol ||
-            (p->playback.info_updated == TRUE &&
-             p->record.info_updated == TRUE)) {
-        /* We only have one pa_operation running the stream-restore-info
-         * which retrieves volume-info from both Playback and Record channels;
-         * We can complete all async tasks now that this operation ended.
-         * (or we already have the volume-info we want)
-         * Note: the following function cancel the current pa_operation */
-        spice_pulse_complete_all_async_tasks(SPICE_PULSE(userdata), NULL);
-        return;
-    }
-
-    if (g_strcmp0(info->name, p->playback.name) == 0) {
-        pstream = &p->playback;
-    } else if (g_strcmp0(info->name, p->record.name) == 0) {
-        pstream = &p->record;
-    } else {
-        /* This is not the stream you are looking for. */
-        return;
-    }
-
-    if (info->channel_map.channels == 0) {
-        SPICE_DEBUG("Number of channels stored is zero. Ignore. (%s)", info->name);
-        return;
-    }
-
-    pstream->info_updated = TRUE;
-    pstream->info.name = pstream->name;
-    pstream->info.mute = info->mute;
-    pstream->info.channel_map = info->channel_map;
-    pstream->info.volume = info->volume;
-}
-
-#if PA_CHECK_VERSION(1,0,0)
-static void source_output_info_cb(pa_context *context,
-                                  const pa_source_output_info *info,
-                                  int eol,
-                                  void *userdata)
-#else
-static void source_info_cb(pa_context *context,
-                           const pa_source_info *info,
-                           int eol,
-                           void *userdata)
-#endif
-{
-    struct async_task *task = userdata;
-    SpicePulsePrivate *p = task->pulse->priv;
-    struct stream *pstream = &p->record;
-
-    if (eol) {
-        spice_pulse_complete_async_task(task, NULL);
-        return;
-    }
-
-    pstream->info_updated = TRUE;
-    pstream->info.name = pstream->name;
-    pstream->info.mute = info->mute;
-    pstream->info.channel_map = info->channel_map;
-    pstream->info.volume = info->volume;
-}
-
-static void sink_input_info_cb(pa_context *context,
-                               const pa_sink_input_info *info,
-                               int eol,
-                               void *userdata)
-{
-    struct async_task *task = userdata;
-    SpicePulsePrivate *p = task->pulse->priv;
-    struct stream *pstream = &p->playback;
-
-    if (eol) {
-        spice_pulse_complete_async_task(task, NULL);
-        return;
-    }
-
-    pstream->info_updated = TRUE;
-    pstream->info.name = pstream->name;
-    pstream->info.mute = info->mute;
-    pstream->info.channel_map = info->channel_map;
-    pstream->info.volume = info->volume;
-}
-
-/* to avoid code duplication */
-static void pulse_stream_restore_info_async(gboolean is_playback,
-                                            SpiceAudio *audio,
-                                            GCancellable *cancellable,
-                                            SpiceMainChannel *main_channel,
-                                            GAsyncReadyCallback callback,
-                                            gpointer user_data)
-{
-    SpicePulsePrivate *p = SPICE_PULSE(audio)->priv;
-    GSimpleAsyncResult *simple;
-    struct async_task *task = g_malloc0(sizeof(struct async_task));
-    pa_operation *op = NULL;
-
-    simple = g_simple_async_result_new(G_OBJECT(audio),
-                                       callback,
-                                       user_data,
-                                       pulse_stream_restore_info_async);
-#if GLIB_CHECK_VERSION(2,32,0)
-    g_simple_async_result_set_check_cancellable (simple, cancellable);
-#endif
-
-    task->res = simple;
-    task->pulse = g_object_ref(audio);
-    task->callback = callback;
-    task->user_data = user_data;
-    task->is_playback = is_playback;
-    task->main_channel = g_object_ref(main_channel);
-    task->pa_op = NULL;
-
-    if (cancellable) {
-        task->cancellable = g_object_ref(cancellable);
-        task->cancel_id = g_cancellable_connect(cancellable, G_CALLBACK(cancel_task), task, NULL);
-    }
-
-    /* If Playback/Record stream is created we use pulse API to get volume-info
-     * from those streams directly. If the stream is not created, retrieve last
-     * volume/mute values from Pulse database using the application name;
-     * If we already have retrieved volume-info from Pulse database then it is
-     * safe to return the volume-info we already have in <stream>info */
-
-    if (is_playback == TRUE &&
-            p->playback.stream != NULL &&
-            pa_stream_get_index(p->playback.stream) != PA_INVALID_INDEX) {
-        SPICE_DEBUG("Playback stream is created - get-sink-input-info");
-        p->playback.info_updated = FALSE;
-        op = pa_context_get_sink_input_info(p->context,
-                                            pa_stream_get_index(p->playback.stream),
-                                            sink_input_info_cb,
-                                            task);
-        if (!op)
-            goto fail;
-        task->pa_op = op;
-
-    } else if (is_playback == FALSE &&
-            p->record.stream != NULL &&
-            pa_stream_get_index(p->record.stream) != PA_INVALID_INDEX) {
-        SPICE_DEBUG("Record stream is created - get-source-output-info");
-        p->record.info_updated = FALSE;
-#if PA_CHECK_VERSION(1,0,0)
-        op = pa_context_get_source_output_info(p->context,
-                                               pa_stream_get_index(p->record.stream),
-                                               source_output_info_cb,
-                                               task);
-#else
-        op = pa_context_get_source_info_by_index(p->context,
-                                                 pa_stream_get_device_index(p->record.stream),
-                                                 source_info_cb,
-                                                 task);
-#endif
-        if (!op)
-            goto fail;
-        task->pa_op = op;
-
-    } else {
-        if (p->playback.info.name != NULL ||
-                p->record.info.name != NULL) {
-            /* If the pstream->info.name is set then we already have updated
-             * volume information. We can complete the request now */
-            SPICE_DEBUG("Return the volume-information we already have");
-            spice_pulse_complete_async_task(task, NULL);
-            return;
-        }
-
-        if (p->results == NULL) {
-            SPICE_DEBUG("Streams are not created - ext-stream-restore");
-            p->playback.info_updated = FALSE;
-            p->record.info_updated = FALSE;
-
-            if (pa_context_get_state(p->context) == PA_CONTEXT_READY) {
-                /* Restore value from pulse db */
-                op = pa_ext_stream_restore_read(p->context, stream_restore_read_cb, audio);
-                if (!op)
-                    goto fail;
-                task->pa_op = op;
-            } else {
-                /* It is possible that we want to get volume-info before the
-                 * context is in READY state. In this case, we wait for the
-                 * context state change to READY. */
-                p->pending_restore_task = task;
-            }
-        }
-    }
-
-    p->results = g_list_append(p->results, task);
-    SPICE_DEBUG ("Number of async task is %d", g_list_length(p->results));
-    return;
-
-fail:
-    if (!op) {
-        g_simple_async_report_error_in_idle(G_OBJECT(audio),
-                                            callback,
-                                            user_data,
-                                            SPICE_CLIENT_ERROR,
-                                            SPICE_CLIENT_ERROR_FAILED,
-                                            "Volume-Info failed: %s",
-                                            pa_strerror(pa_context_errno(p->context)));
-        free_async_task(task);
-    }
-}
-
-/* to avoid code duplication */
-static gboolean pulse_stream_restore_info_finish(gboolean is_playback,
-                                                 SpiceAudio *audio,
-                                                 GAsyncResult *res,
-                                                 gboolean *mute,
-                                                 guint8 *nchannels,
-                                                 guint16 **volume,
-                                                 GError **error)
-{
-    SpicePulsePrivate *p = SPICE_PULSE(audio)->priv;
-    struct stream *pstream = (is_playback) ? &p->playback : &p->record;
-    GSimpleAsyncResult *simple = (GSimpleAsyncResult *) res;
-
-    g_return_val_if_fail(g_simple_async_result_is_valid(res,
-        G_OBJECT(audio), pulse_stream_restore_info_async), FALSE);
-
-    if (g_simple_async_result_propagate_error(simple, error)) {
-        /* set out args that should have new alloc'ed memory to NULL */
-        if (volume != NULL) {
-            *volume = NULL;
-        }
-        return FALSE;
-    }
-
-    if (mute != NULL) {
-        *mute = (pstream->info.mute) ? TRUE : FALSE;
-    }
-
-    if (nchannels != NULL) {
-        *nchannels = pstream->info.channel_map.channels;
-    }
-
-    if (volume != NULL) {
-        gint i;
-        *volume = g_new(guint16, pstream->info.channel_map.channels);
-        for (i = 0; i < pstream->info.channel_map.channels; i++) {
-            (*volume)[i] = MIN(pstream->info.volume.values[i], G_MAXUINT16);
-            SPICE_DEBUG("(%s) volume at channel %d is %u",
-                        (is_playback) ? "playback" : "record", i, (*volume)[i]);
-        }
-    }
-
-    return g_simple_async_result_get_op_res_gboolean(simple);
-}
-
-static void spice_pulse_get_playback_volume_info_async(SpiceAudio *audio,
-                                                       GCancellable *cancellable,
-                                                       SpiceMainChannel *main_channel,
-                                                       GAsyncReadyCallback callback,
-                                                       gpointer user_data)
-{
-    pulse_stream_restore_info_async(TRUE, audio, cancellable, main_channel, callback, user_data);
-}
-
-static gboolean spice_pulse_get_playback_volume_info_finish(SpiceAudio *audio,
-                                                            GAsyncResult *res,
-                                                            gboolean *mute,
-                                                            guint8 *nchannels,
-                                                            guint16 **volume,
-                                                            GError **error)
-{
-    return pulse_stream_restore_info_finish(TRUE, audio, res, mute,
-                                            nchannels, volume, error);
-}
-
-static void spice_pulse_get_record_volume_info_async(SpiceAudio *audio,
-                                                     GCancellable *cancellable,
-                                                     SpiceMainChannel *main_channel,
-                                                     GAsyncReadyCallback callback,
-                                                     gpointer user_data)
-{
-    pulse_stream_restore_info_async(FALSE, audio, cancellable, main_channel, callback, user_data);
-}
-
-static gboolean spice_pulse_get_record_volume_info_finish(SpiceAudio *audio,
-                                                          GAsyncResult *res,
-                                                          gboolean *mute,
-                                                          guint8 *nchannels,
-                                                          guint16 **volume,
-                                                          GError **error)
-{
-    return pulse_stream_restore_info_finish(FALSE, audio, res, mute,
-                                            nchannels, volume, error);
-}
diff --git a/gtk/spice-pulse.h b/gtk/spice-pulse.h
deleted file mode 100644
index 819647e..0000000
--- a/gtk/spice-pulse.h
+++ /dev/null
@@ -1,57 +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_CLIENT_PULSE_H__
-#define __SPICE_CLIENT_PULSE_H__
-
-#include "spice-client.h"
-#include "spice-audio.h"
-
-G_BEGIN_DECLS
-
-#define SPICE_TYPE_PULSE            (spice_pulse_get_type())
-#define SPICE_PULSE(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), SPICE_TYPE_PULSE, SpicePulse))
-#define SPICE_PULSE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), SPICE_TYPE_PULSE, SpicePulseClass))
-#define SPICE_IS_PULSE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), SPICE_TYPE_PULSE))
-#define SPICE_IS_PULSE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SPICE_TYPE_PULSE))
-#define SPICE_PULSE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), SPICE_TYPE_PULSE, SpicePulseClass))
-
-
-typedef struct _SpicePulse SpicePulse;
-typedef struct _SpicePulseClass SpicePulseClass;
-typedef struct _SpicePulsePrivate SpicePulsePrivate;
-
-struct _SpicePulse {
-    SpiceAudio parent;
-    SpicePulsePrivate *priv;
-    /* Do not add fields to this struct */
-};
-
-struct _SpicePulseClass {
-    SpiceAudioClass parent_class;
-    /* Do not add fields to this struct */
-};
-
-GType           spice_pulse_get_type(void);
-
-SpicePulse *spice_pulse_new(SpiceSession *session,
-                            GMainContext *context,
-                            const char *name);
-
-G_END_DECLS
-
-#endif /* __SPICE_CLIENT_PULSE_H__ */
diff --git a/gtk/spice-session-priv.h b/gtk/spice-session-priv.h
deleted file mode 100644
index 049973a..0000000
--- a/gtk/spice-session-priv.h
+++ /dev/null
@@ -1,104 +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_CLIENT_SESSION_PRIV_H__
-#define __SPICE_CLIENT_SESSION_PRIV_H__
-
-#include "config.h"
-
-#include <glib.h>
-#include <gio/gio.h>
-
-#ifdef USE_PHODAV
-#include <libphodav/phodav.h>
-#else
-typedef struct _PhodavServer PhodavServer;
-#endif
-
-#include "desktop-integration.h"
-#include "spice-session.h"
-#include "spice-gtk-session.h"
-#include "spice-channel-cache.h"
-#include "decode.h"
-
-G_BEGIN_DECLS
-
-#define WEBDAV_MAGIC_SIZE 16
-
-SpiceSession *spice_session_new_from_session(SpiceSession *session);
-
-void spice_session_set_connection_id(SpiceSession *session, int id);
-int spice_session_get_connection_id(SpiceSession *session);
-gboolean spice_session_get_client_provided_socket(SpiceSession *session);
-
-GSocketConnection* spice_session_channel_open_host(SpiceSession *session, SpiceChannel *channel,
-                                                   gboolean *use_tls, GError **error);
-void spice_session_channel_new(SpiceSession *session, SpiceChannel *channel);
-void spice_session_channel_migrate(SpiceSession *session, SpiceChannel *channel);
-
-void spice_session_set_mm_time(SpiceSession *session, guint32 time);
-guint32 spice_session_get_mm_time(SpiceSession *session);
-
-void spice_session_switching_disconnect(SpiceSession *session);
-void spice_session_start_migrating(SpiceSession *session,
-                                   gboolean full_migration);
-void spice_session_abort_migration(SpiceSession *session);
-void spice_session_set_migration_state(SpiceSession *session, SpiceSessionMigration state);
-
-void spice_session_set_port(SpiceSession *session, int port, gboolean tls);
-void spice_session_get_pubkey(SpiceSession *session, guint8 **pubkey, guint *size);
-guint spice_session_get_verify(SpiceSession *session);
-const gchar* spice_session_get_username(SpiceSession *session);
-const gchar* spice_session_get_password(SpiceSession *session);
-const gchar* spice_session_get_host(SpiceSession *session);
-const gchar* spice_session_get_cert_subject(SpiceSession *session);
-const gchar* spice_session_get_ciphers(SpiceSession *session);
-const gchar* spice_session_get_ca_file(SpiceSession *session);
-void spice_session_get_ca(SpiceSession *session, guint8 **ca, guint *size);
-
-void spice_session_set_caches_hints(SpiceSession *session,
-                                    uint32_t pci_ram_size,
-                                    uint32_t n_display_channels);
-void spice_session_get_caches(SpiceSession *session,
-                              display_cache **images,
-                              SpiceGlzDecoderWindow **glz_window);
-void spice_session_palettes_clear(SpiceSession *session);
-void spice_session_images_clear(SpiceSession *session);
-void spice_session_migrate_end(SpiceSession *session);
-gboolean spice_session_migrate_after_main_init(SpiceSession *session);
-SpiceChannel* spice_session_lookup_channel(SpiceSession *session, gint id, gint type);
-void spice_session_set_uuid(SpiceSession *session, guint8 uuid[16]);
-void spice_session_set_name(SpiceSession *session, const gchar *name);
-gboolean spice_session_is_playback_active(SpiceSession *session);
-guint32 spice_session_get_playback_latency(SpiceSession *session);
-void spice_session_sync_playback_latency(SpiceSession *session);
-const gchar* spice_session_get_shared_dir(SpiceSession *session);
-void spice_session_set_shared_dir(SpiceSession *session, const gchar *dir);
-gboolean spice_session_get_audio_enabled(SpiceSession *session);
-gboolean spice_session_get_smartcard_enabled(SpiceSession *session);
-gboolean spice_session_get_usbredir_enabled(SpiceSession *session);
-
-const guint8* spice_session_get_webdav_magic(SpiceSession *session);
-PhodavServer *spice_session_get_webdav_server(SpiceSession *session);
-PhodavServer* channel_webdav_server_new(SpiceSession *session);
-guint spice_session_get_n_display_channels(SpiceSession *session);
-void spice_session_set_main_channel(SpiceSession *session, SpiceChannel *channel);
-gboolean spice_session_set_migration_session(SpiceSession *session, SpiceSession *mig_session);
-SpiceAudio *spice_audio_get(SpiceSession *session, GMainContext *context);
-G_END_DECLS
-
-#endif /* __SPICE_CLIENT_SESSION_PRIV_H__ */
diff --git a/gtk/spice-session.c b/gtk/spice-session.c
deleted file mode 100644
index 778d82a..0000000
--- a/gtk/spice-session.c
+++ /dev/null
@@ -1,2728 +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 <gio/gio.h>
-#include <glib.h>
-#ifdef G_OS_UNIX
-#include <gio/gunixsocketaddress.h>
-#endif
-#include "common/ring.h"
-
-#include "spice-client.h"
-#include "spice-common.h"
-#include "spice-channel-priv.h"
-#include "spice-util-priv.h"
-#include "spice-session-priv.h"
-#include "gio-coroutine.h"
-#include "glib-compat.h"
-#include "wocky-http-proxy.h"
-#include "spice-uri-priv.h"
-#include "channel-playback-priv.h"
-#include "spice-audio.h"
-
-struct channel {
-    SpiceChannel      *channel;
-    RingItem          link;
-};
-
-#define IMAGES_CACHE_SIZE_DEFAULT (1024 * 1024 * 80)
-#define MIN_GLZ_WINDOW_SIZE_DEFAULT (1024 * 1024 * 12)
-#define MAX_GLZ_WINDOW_SIZE_DEFAULT MIN((LZ_MAX_WINDOW_SIZE * 4), 1024 * 1024 * 64)
-
-struct _SpiceSessionPrivate {
-    char              *host;
-    char              *unix_path;
-    char              *port;
-    char              *tls_port;
-    char              *username;
-    char              *password;
-    char              *ca_file;
-    char              *ciphers;
-    GByteArray        *pubkey;
-    GByteArray        *ca;
-    char              *cert_subject;
-    guint             verify;
-    gboolean          read_only;
-    SpiceURI          *proxy;
-    gchar             *shared_dir;
-    gboolean          share_dir_ro;
-
-    /* whether to enable audio */
-    gboolean          audio;
-
-    /* whether to enable smartcard event forwarding to the server */
-    gboolean          smartcard;
-
-    /* list of certificates to use for the software smartcard reader if
-     * enabled. For now, it has to contain exactly 3 certificates for
-     * the software reader to be functional
-     */
-    GStrv             smartcard_certificates;
-
-    /* path to the local certificate database to use to lookup the
-     * certificates stored in 'certificates'. If NULL, libcacard will
-     * fallback to using a default database.
-     */
-    char *            smartcard_db;
-
-    /* whether to enable USB redirection */
-    gboolean          usbredir;
-
-    /* Set when a usbredir channel has requested the keyboard grab to be
-       temporarily released (because it is going to invoke policykit) */
-    gboolean          inhibit_keyboard_grab;
-
-    GStrv             disable_effects;
-    GStrv             secure_channels;
-    gint              color_depth;
-
-    int               connection_id;
-    int               protocol;
-    SpiceChannel      *cmain; /* weak reference */
-    Ring              channels;
-    guint32           mm_time;
-    gboolean          client_provided_sockets;
-    guint64           mm_time_at_clock;
-    SpiceSession      *migration;
-    GList             *migration_left;
-    SpiceSessionMigration migration_state;
-    gboolean          full_migration; /* seamless migration indicator */
-    guint             disconnecting;
-    gboolean          migrate_wait_init;
-    guint             after_main_init;
-    gboolean          for_migration;
-
-    display_cache     *images;
-    display_cache     *palettes;
-    SpiceGlzDecoderWindow *glz_window;
-    int               images_cache_size;
-    int               glz_window_size;
-    uint32_t          pci_ram_size;
-    uint32_t          n_display_channels;
-    guint8            uuid[16];
-    gchar             *name;
-
-    /* associated objects */
-    SpiceAudio        *audio_manager;
-    SpiceUsbDeviceManager *usb_manager;
-    SpicePlaybackChannel *playback_channel;
-    PhodavServer      *webdav;
-};
-
-
-/**
- * SECTION:spice-session
- * @short_description: handles connection details, and active channels
- * @title: Spice Session
- * @section_id:
- * @see_also: #SpiceChannel, and the GTK widget #SpiceDisplay
- * @stability: Stable
- * @include: spice-session.h
- *
- * The #SpiceSession class handles all the #SpiceChannel connections.
- * It's also the class that contains connections informations, such as
- * #SpiceSession:host and #SpiceSession:port.
- *
- * You can simply set the property #SpiceSession:uri to something like
- * "spice://127.0.0.1?port=5930" to configure your connection details.
- *
- * You may want to connect to #SpiceSession::channel-new signal, to be
- * informed of the availability of channels and to interact with
- * them.
- *
- * For example, when the #SpiceInputsChannel is available and get the
- * event #SPICE_CHANNEL_OPENED, you can send key events with
- * spice_inputs_key_press(). When the #SpiceMainChannel is available,
- * you can start sharing the clipboard...  .
- *
- *
- * Once #SpiceSession properties set, you can call
- * spice_session_connect() to start connecting and communicating with
- * a Spice server.
- */
-
-/* ------------------------------------------------------------------ */
-/* gobject glue                                                       */
-
-#define SPICE_SESSION_GET_PRIVATE(obj) \
-    (G_TYPE_INSTANCE_GET_PRIVATE ((obj), SPICE_TYPE_SESSION, SpiceSessionPrivate))
-
-G_DEFINE_TYPE (SpiceSession, spice_session, G_TYPE_OBJECT);
-
-/* Properties */
-enum {
-    PROP_0,
-    PROP_HOST,
-    PROP_PORT,
-    PROP_TLS_PORT,
-    PROP_PASSWORD,
-    PROP_CA_FILE,
-    PROP_CIPHERS,
-    PROP_IPV4,
-    PROP_IPV6,
-    PROP_PROTOCOL,
-    PROP_URI,
-    PROP_CLIENT_SOCKETS,
-    PROP_PUBKEY,
-    PROP_CERT_SUBJECT,
-    PROP_VERIFY,
-    PROP_MIGRATION_STATE,
-    PROP_AUDIO,
-    PROP_SMARTCARD,
-    PROP_SMARTCARD_CERTIFICATES,
-    PROP_SMARTCARD_DB,
-    PROP_USBREDIR,
-    PROP_INHIBIT_KEYBOARD_GRAB,
-    PROP_DISABLE_EFFECTS,
-    PROP_COLOR_DEPTH,
-    PROP_READ_ONLY,
-    PROP_CACHE_SIZE,
-    PROP_GLZ_WINDOW_SIZE,
-    PROP_UUID,
-    PROP_NAME,
-    PROP_CA,
-    PROP_PROXY,
-    PROP_SECURE_CHANNELS,
-    PROP_SHARED_DIR,
-    PROP_SHARE_DIR_RO,
-    PROP_USERNAME,
-    PROP_UNIX_PATH,
-};
-
-/* signals */
-enum {
-    SPICE_SESSION_CHANNEL_NEW,
-    SPICE_SESSION_CHANNEL_DESTROY,
-    SPICE_SESSION_MM_TIME_RESET,
-    SPICE_SESSION_LAST_SIGNAL,
-};
-
-static guint signals[SPICE_SESSION_LAST_SIGNAL];
-
-static void spice_session_channel_destroy(SpiceSession *session, SpiceChannel *channel);
-
-static void update_proxy(SpiceSession *self, const gchar *str)
-{
-    SpiceSessionPrivate *s = self->priv;
-    SpiceURI *proxy = NULL;
-    GError *error = NULL;
-
-    if (str == NULL)
-        str = g_getenv("SPICE_PROXY");
-    if (str == NULL || *str == 0) {
-        g_clear_object(&s->proxy);
-        return;
-    }
-
-    proxy = spice_uri_new();
-    if (!spice_uri_parse(proxy, str, &error))
-        g_clear_object(&proxy);
-    if (error) {
-        g_warning("%s", error->message);
-        g_clear_error(&error);
-    }
-
-    if (proxy != NULL) {
-        g_clear_object(&s->proxy);
-        s->proxy = proxy;
-    }
-}
-
-static void spice_session_init(SpiceSession *session)
-{
-    SpiceSessionPrivate *s;
-    gchar *channels;
-
-    SPICE_DEBUG("New session (compiled from package " PACKAGE_STRING ")");
-    s = session->priv = SPICE_SESSION_GET_PRIVATE(session);
-
-    channels = spice_channel_supported_string();
-    SPICE_DEBUG("Supported channels: %s", channels);
-    g_free(channels);
-
-    ring_init(&s->channels);
-    s->images = cache_new((GDestroyNotify)pixman_image_unref);
-    s->glz_window = glz_decoder_window_new();
-    update_proxy(session, NULL);
-}
-
-static void
-session_disconnect(SpiceSession *self, gboolean keep_main)
-{
-    SpiceSessionPrivate *s;
-    struct channel *item;
-    RingItem *ring, *next;
-
-    s = self->priv;
-
-    for (ring = ring_get_head(&s->channels); ring != NULL; ring = next) {
-        next = ring_next(&s->channels, ring);
-        item = SPICE_CONTAINEROF(ring, struct channel, link);
-
-        if (keep_main && item->channel == s->cmain) {
-            spice_channel_disconnect(item->channel, SPICE_CHANNEL_NONE);
-        } else {
-            spice_session_channel_destroy(self, item->channel);
-        }
-    }
-
-    s->connection_id = 0;
-
-    g_free(s->name);
-    s->name = NULL;
-    memset(s->uuid, 0, sizeof(s->uuid));
-
-    spice_session_abort_migration(self);
-}
-
-static void
-spice_session_dispose(GObject *gobject)
-{
-    SpiceSession *session = SPICE_SESSION(gobject);
-    SpiceSessionPrivate *s = session->priv;
-
-    SPICE_DEBUG("session dispose");
-
-    session_disconnect(session, FALSE);
-
-    g_warn_if_fail(s->migration == NULL);
-    g_warn_if_fail(s->migration_left == NULL);
-    g_warn_if_fail(s->after_main_init == 0);
-    g_warn_if_fail(s->disconnecting == 0);
-
-    g_clear_object(&s->audio_manager);
-    g_clear_object(&s->usb_manager);
-    g_clear_object(&s->proxy);
-    g_clear_object(&s->webdav);
-
-    /* Chain up to the parent class */
-    if (G_OBJECT_CLASS(spice_session_parent_class)->dispose)
-        G_OBJECT_CLASS(spice_session_parent_class)->dispose(gobject);
-}
-
-static void
-spice_session_finalize(GObject *gobject)
-{
-    SpiceSession *session = SPICE_SESSION(gobject);
-    SpiceSessionPrivate *s = session->priv;
-
-    /* release stuff */
-    g_free(s->unix_path);
-    g_free(s->host);
-    g_free(s->port);
-    g_free(s->tls_port);
-    g_free(s->username);
-    g_free(s->password);
-    g_free(s->ca_file);
-    g_free(s->ciphers);
-    g_free(s->cert_subject);
-    g_strfreev(s->smartcard_certificates);
-    g_free(s->smartcard_db);
-    g_strfreev(s->disable_effects);
-    g_strfreev(s->secure_channels);
-    g_free(s->shared_dir);
-
-    g_clear_pointer(&s->images, cache_unref);
-    glz_decoder_window_destroy(s->glz_window);
-
-    g_clear_pointer(&s->pubkey, g_byte_array_unref);
-    g_clear_pointer(&s->ca, g_byte_array_unref);
-
-    /* Chain up to the parent class */
-    if (G_OBJECT_CLASS(spice_session_parent_class)->finalize)
-        G_OBJECT_CLASS(spice_session_parent_class)->finalize(gobject);
-}
-
-#define URI_SCHEME_SPICE "spice://"
-#define URI_SCHEME_SPICE_UNIX "spice+unix://"
-#define URI_QUERY_START ";?"
-#define URI_QUERY_SEP   ";&"
-
-static gchar* spice_uri_create(SpiceSession *session)
-{
-    SpiceSessionPrivate *s = session->priv;
-
-    if (s->unix_path != NULL) {
-        return g_strdup_printf(URI_SCHEME_SPICE_UNIX "%s", s->unix_path);
-    } else if (s->host != NULL) {
-        g_return_val_if_fail(s->port != NULL || s->tls_port != NULL, NULL);
-
-        GString *str = g_string_new(URI_SCHEME_SPICE);
-
-        g_string_append(str, s->host);
-        g_string_append(str, "?");
-        if (s->port != NULL) {
-            g_string_append_printf(str, "port=%s&", s->port);
-        }
-        if (s->tls_port != NULL) {
-            g_string_append_printf(str, "tls-port=%s", s->tls_port);
-        }
-        return g_string_free(str, FALSE);
-    }
-
-    g_return_val_if_reached(NULL);
-}
-
-static int spice_parse_uri(SpiceSession *session, const char *original_uri)
-{
-    SpiceSessionPrivate *s = session->priv;
-    gchar *host = NULL, *port = NULL, *tls_port = NULL, *uri = NULL, *username = NULL, *password = NULL;
-    gchar *path = NULL;
-    gchar *unescaped_path = NULL;
-    gchar *authority = NULL;
-    gchar *query = NULL;
-    gchar *tmp = NULL;
-
-    g_return_val_if_fail(original_uri != NULL, -1);
-
-    uri = g_strdup(original_uri);
-
-    if (g_str_has_prefix(uri, URI_SCHEME_SPICE_UNIX)) {
-        path = uri + strlen(URI_SCHEME_SPICE_UNIX);
-        goto end;
-    }
-
-    /* Break up the URI into its various parts, scheme, authority,
-     * path (ignored) and query
-     */
-    if (!g_str_has_prefix(uri, URI_SCHEME_SPICE)) {
-        g_warning("Expected a URI scheme of '%s' in URI '%s'",
-                  URI_SCHEME_SPICE, uri);
-        goto fail;
-    }
-    authority = uri + strlen(URI_SCHEME_SPICE);
-
-    tmp = strchr(authority, '@');
-    if (tmp) {
-        tmp[0] = '\0';
-        username = g_uri_unescape_string(authority, NULL);
-        authority = ++tmp;
-        tmp = NULL;
-    }
-
-    path = strchr(authority, '/');
-    if (path) {
-        path[0] = '\0';
-        path++;
-    }
-
-    if (path) {
-        size_t prefix = strcspn(path, URI_QUERY_START);
-        query = path + prefix;
-    } else {
-        size_t prefix = strcspn(authority, URI_QUERY_START);
-        query = authority + prefix;
-    }
-
-    if (query && query[0]) {
-        query[0] = '\0';
-        query++;
-    }
-
-    /* Now process the individual parts */
-
-    if (authority[0] == '[') {
-        tmp = strchr(authority, ']');
-        if (!tmp) {
-            g_warning("Missing closing ']' in authority for URI '%s'", uri);
-            goto fail;
-        }
-        tmp[0] = '\0';
-        tmp++;
-        host = g_strdup(authority + 1);
-        if (tmp[0] == ':')
-            port = g_strdup(tmp + 1);
-    } else {
-        tmp = strchr(authority, ':');
-        if (tmp) {
-            *tmp = '\0';
-            tmp++;
-            port = g_strdup(tmp);
-        }
-        host = g_uri_unescape_string(authority, NULL);
-    }
-
-    if (path && !(g_str_equal(path, "") ||
-                  g_str_equal(path, "/"))) {
-        g_warning("Unexpected path data '%s' for URI '%s'", path, uri);
-        /* don't fail, just ignore */
-    }
-    unescaped_path = g_uri_unescape_string(path, NULL);
-    path = NULL;
-
-    while (query && query[0] != '\0') {
-        gchar key[32], value[128];
-        gchar **target_key;
-
-        int len;
-        if (sscanf(query, "%31[-a-zA-Z0-9]=%n", key, &len) != 1) {
-            spice_warning("Failed to parse key in URI '%s'", query);
-            goto fail;
-        }
-
-        query += len;
-        if (*query == '\0') {
-            spice_warning ("key '%s' without value", key);
-            break;
-        } else if (*query == ';' || *query == '&') {
-            /* another argument */
-            query++;
-            continue;
-        }
-
-        if (sscanf(query, "%127[^;&]%n", value, &len) != 1) {
-            spice_warning("Failed to parse value of key '%s' in URI '%s'", key, query);
-            goto fail;
-        }
-
-        query += len;
-        if (*query)
-            query++;
-
-        target_key = NULL;
-        if (g_str_equal(key, "port")) {
-            target_key = &port;
-        } else if (g_str_equal(key, "tls-port")) {
-            target_key = &tls_port;
-        } else if (g_str_equal(key, "password")) {
-            target_key = &password;
-            g_warning("password may be visible in process listings");
-        } else {
-            g_warning("unknown key in spice URI parsing: '%s'", key);
-            goto fail;
-        }
-        if (target_key) {
-            if (*target_key) {
-                g_warning("Double set of '%s' in URI '%s'", key, uri);
-                goto fail;
-            }
-            *target_key = g_uri_unescape_string(value, NULL);
-        }
-    }
-
-    if (port == NULL && tls_port == NULL) {
-        g_warning("Missing port or tls-port in spice URI '%s'", uri);
-        goto fail;
-    }
-
-end:
-    /* parsed ok -> apply */
-    g_free(uri);
-    g_free(unescaped_path);
-    g_free(s->unix_path);
-    g_free(s->host);
-    g_free(s->port);
-    g_free(s->tls_port);
-    g_free(s->username);
-    g_free(s->password);
-    s->unix_path = g_strdup(path);
-    s->host = host;
-    s->port = port;
-    s->tls_port = tls_port;
-    s->username = username;
-    s->password = password;
-    return 0;
-
-fail:
-    g_free(uri);
-    g_free(unescaped_path);
-    g_free(host);
-    g_free(port);
-    g_free(tls_port);
-    g_free(username);
-    g_free(password);
-    return -1;
-}
-
-static void spice_session_get_property(GObject    *gobject,
-                                       guint       prop_id,
-                                       GValue     *value,
-                                       GParamSpec *pspec)
-{
-    SpiceSession *session = SPICE_SESSION(gobject);
-    SpiceSessionPrivate *s = session->priv;
-
-    switch (prop_id) {
-    case PROP_HOST:
-        g_value_set_string(value, s->host);
-	break;
-    case PROP_UNIX_PATH:
-        g_value_set_string(value, s->unix_path);
-        break;
-    case PROP_PORT:
-        g_value_set_string(value, s->port);
-	break;
-    case PROP_TLS_PORT:
-        g_value_set_string(value, s->tls_port);
-	break;
-    case PROP_USERNAME:
-        g_value_set_string(value, s->username);
-	break;
-    case PROP_PASSWORD:
-        g_value_set_string(value, s->password);
-	break;
-    case PROP_CA_FILE:
-        g_value_set_string(value, s->ca_file);
-	break;
-    case PROP_CIPHERS:
-        g_value_set_string(value, s->ciphers);
-	break;
-    case PROP_PROTOCOL:
-        g_value_set_int(value, s->protocol);
-	break;
-    case PROP_URI:
-        g_value_take_string(value, spice_uri_create(session));
-        break;
-    case PROP_CLIENT_SOCKETS:
-        g_value_set_boolean(value, s->client_provided_sockets);
-	break;
-    case PROP_PUBKEY:
-        g_value_set_boxed(value, s->pubkey);
-	break;
-    case PROP_CA:
-        g_value_set_boxed(value, s->ca);
-	break;
-    case PROP_CERT_SUBJECT:
-        g_value_set_string(value, s->cert_subject);
-	break;
-    case PROP_VERIFY:
-        g_value_set_flags(value, s->verify);
-        break;
-    case PROP_MIGRATION_STATE:
-        g_value_set_enum(value, s->migration_state);
-        break;
-    case PROP_SMARTCARD:
-        g_value_set_boolean(value, s->smartcard);
-        break;
-    case PROP_SMARTCARD_CERTIFICATES:
-        g_value_set_boxed(value, s->smartcard_certificates);
-        break;
-    case PROP_SMARTCARD_DB:
-        g_value_set_string(value, s->smartcard_db);
-        break;
-    case PROP_USBREDIR:
-        g_value_set_boolean(value, s->usbredir);
-        break;
-    case PROP_INHIBIT_KEYBOARD_GRAB:
-        g_value_set_boolean(value, s->inhibit_keyboard_grab);
-        break;
-    case PROP_DISABLE_EFFECTS:
-        g_value_set_boxed(value, s->disable_effects);
-        break;
-    case PROP_SECURE_CHANNELS:
-        g_value_set_boxed(value, s->secure_channels);
-        break;
-    case PROP_COLOR_DEPTH:
-        g_value_set_int(value, s->color_depth);
-        break;
-    case PROP_AUDIO:
-        g_value_set_boolean(value, s->audio);
-        break;
-    case PROP_READ_ONLY:
-        g_value_set_boolean(value, s->read_only);
-        break;
-    case PROP_CACHE_SIZE:
-        g_value_set_int(value, s->images_cache_size);
-        break;
-    case PROP_GLZ_WINDOW_SIZE:
-        g_value_set_int(value, s->glz_window_size);
-        break;
-    case PROP_NAME:
-        g_value_set_string(value, s->name);
-	break;
-    case PROP_UUID:
-        g_value_set_pointer(value, s->uuid);
-	break;
-    case PROP_PROXY:
-        g_value_take_string(value, spice_uri_to_string(s->proxy));
-	break;
-    case PROP_SHARED_DIR:
-        g_value_set_string(value, spice_session_get_shared_dir(session));
-        break;
-    case PROP_SHARE_DIR_RO:
-        g_value_set_boolean(value, s->share_dir_ro);
-        break;
-    default:
-	G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
-	break;
-    }
-}
-
-static void spice_session_set_property(GObject      *gobject,
-                                       guint         prop_id,
-                                       const GValue *value,
-                                       GParamSpec   *pspec)
-{
-    SpiceSession *session = SPICE_SESSION(gobject);
-    SpiceSessionPrivate *s = session->priv;
-    const char *str;
-
-    switch (prop_id) {
-    case PROP_HOST:
-        g_free(s->host);
-        s->host = g_value_dup_string(value);
-        break;
-    case PROP_UNIX_PATH:
-        g_free(s->unix_path);
-        s->unix_path = g_value_dup_string(value);
-        break;
-    case PROP_PORT:
-        g_free(s->port);
-        s->port = g_value_dup_string(value);
-        break;
-    case PROP_TLS_PORT:
-        g_free(s->tls_port);
-        s->tls_port = g_value_dup_string(value);
-        break;
-    case PROP_USERNAME:
-        g_free(s->username);
-        s->username = g_value_dup_string(value);
-        break;
-    case PROP_PASSWORD:
-        g_free(s->password);
-        s->password = g_value_dup_string(value);
-        break;
-    case PROP_CA_FILE:
-        g_free(s->ca_file);
-        s->ca_file = g_value_dup_string(value);
-        break;
-    case PROP_CIPHERS:
-        g_free(s->ciphers);
-        s->ciphers = g_value_dup_string(value);
-        break;
-    case PROP_PROTOCOL:
-        s->protocol = g_value_get_int(value);
-        break;
-    case PROP_URI:
-        str = g_value_get_string(value);
-        if (str != NULL)
-            spice_parse_uri(session, str);
-        break;
-    case PROP_CLIENT_SOCKETS:
-        s->client_provided_sockets = g_value_get_boolean(value);
-        break;
-    case PROP_PUBKEY:
-        if (s->pubkey)
-            g_byte_array_unref(s->pubkey);
-        s->pubkey = g_value_dup_boxed(value);
-        if (s->pubkey)
-            s->verify |= SPICE_SESSION_VERIFY_PUBKEY;
-        else
-            s->verify &= ~SPICE_SESSION_VERIFY_PUBKEY;
-	break;
-    case PROP_CERT_SUBJECT:
-        g_free(s->cert_subject);
-        s->cert_subject = g_value_dup_string(value);
-        if (s->cert_subject)
-            s->verify |= SPICE_SESSION_VERIFY_SUBJECT;
-        else
-            s->verify &= ~SPICE_SESSION_VERIFY_SUBJECT;
-        break;
-    case PROP_VERIFY:
-        s->verify = g_value_get_flags(value);
-        break;
-    case PROP_MIGRATION_STATE:
-        s->migration_state = g_value_get_enum(value);
-        break;
-    case PROP_SMARTCARD:
-        s->smartcard = g_value_get_boolean(value);
-        break;
-    case PROP_SMARTCARD_CERTIFICATES:
-        g_strfreev(s->smartcard_certificates);
-        s->smartcard_certificates = g_value_dup_boxed(value);
-        break;
-    case PROP_SMARTCARD_DB:
-        g_free(s->smartcard_db);
-        s->smartcard_db = g_value_dup_string(value);
-        break;
-    case PROP_USBREDIR:
-        s->usbredir = g_value_get_boolean(value);
-        break;
-    case PROP_INHIBIT_KEYBOARD_GRAB:
-        s->inhibit_keyboard_grab = g_value_get_boolean(value);
-        break;
-    case PROP_DISABLE_EFFECTS:
-        g_strfreev(s->disable_effects);
-        s->disable_effects = g_value_dup_boxed(value);
-        break;
-    case PROP_SECURE_CHANNELS:
-        g_strfreev(s->secure_channels);
-        s->secure_channels = g_value_dup_boxed(value);
-        break;
-    case PROP_COLOR_DEPTH:
-        s->color_depth = g_value_get_int(value);
-        break;
-    case PROP_AUDIO:
-        s->audio = g_value_get_boolean(value);
-        break;
-    case PROP_READ_ONLY:
-        s->read_only = g_value_get_boolean(value);
-        g_coroutine_object_notify(gobject, "read-only");
-        break;
-    case PROP_CACHE_SIZE:
-        s->images_cache_size = g_value_get_int(value);
-        break;
-    case PROP_GLZ_WINDOW_SIZE:
-        s->glz_window_size = g_value_get_int(value);
-        break;
-    case PROP_CA:
-        g_clear_pointer(&s->ca, g_byte_array_unref);
-        s->ca = g_value_dup_boxed(value);
-        break;
-    case PROP_PROXY:
-        update_proxy(session, g_value_get_string(value));
-        break;
-    case PROP_SHARED_DIR:
-        spice_session_set_shared_dir(session, g_value_get_string(value));
-        break;
-    case PROP_SHARE_DIR_RO:
-        s->share_dir_ro = g_value_get_boolean(value);
-        break;
-    default:
-        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
-        break;
-    }
-}
-
-static void spice_session_class_init(SpiceSessionClass *klass)
-{
-    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
-
-    _wocky_http_proxy_get_type();
-    _wocky_https_proxy_get_type();
-
-    gobject_class->dispose      = spice_session_dispose;
-    gobject_class->finalize     = spice_session_finalize;
-    gobject_class->get_property = spice_session_get_property;
-    gobject_class->set_property = spice_session_set_property;
-
-    /**
-     * SpiceSession:host:
-     *
-     * URL of the SPICE host to connect to
-     *
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_HOST,
-         g_param_spec_string("host",
-                             "Host",
-                             "Remote host",
-                             "localhost",
-                             G_PARAM_READWRITE |
-                             G_PARAM_CONSTRUCT |
-                             G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceSession:unix-path:
-     *
-     * Path of the Unix socket to connect to
-     *
-     * Since: 0.28
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_UNIX_PATH,
-         g_param_spec_string("unix-path",
-                             "Unix path",
-                             "Unix path",
-                             NULL,
-                             G_PARAM_READWRITE |
-                             G_PARAM_CONSTRUCT |
-                             G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceSession:port:
-     *
-     * Port to connect to for unencrypted sessions
-     *
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_PORT,
-         g_param_spec_string("port",
-                             "Port",
-                             "Remote port (plaintext)",
-                             NULL,
-                             G_PARAM_READWRITE |
-                             G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceSession:tls-port:
-     *
-     * Port to connect to for TLS sessions
-     *
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_TLS_PORT,
-         g_param_spec_string("tls-port",
-                             "TLS port",
-                             "Remote port (encrypted)",
-                             NULL,
-                             G_PARAM_READWRITE |
-                             G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceSession:username:
-     *
-     * Username to use
-     *
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_USERNAME,
-         g_param_spec_string("username",
-                             "Username",
-                             "Username used for SASL connections",
-                             NULL,
-                             G_PARAM_READWRITE |
-                             G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceSession:password:
-     *
-     * TLS password to use
-     *
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_PASSWORD,
-         g_param_spec_string("password",
-                             "Password",
-                             "",
-                             NULL,
-                             G_PARAM_READWRITE |
-                             G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceSession:ca-file:
-     *
-     * File holding the CA certificates for the host the client is
-     * connecting to
-     *
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_CA_FILE,
-         g_param_spec_string("ca-file",
-                             "CA file",
-                             "File holding the CA certificates",
-                             NULL,
-                             G_PARAM_READWRITE |
-                             G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceSession:ciphers:
-     *
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_CIPHERS,
-         g_param_spec_string("ciphers",
-                             "Ciphers",
-                             "SSL cipher list",
-                             NULL,
-                             G_PARAM_READWRITE |
-                             G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceSession:protocol:
-     *
-     * Version of the SPICE protocol to use
-     *
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_PROTOCOL,
-         g_param_spec_int("protocol",
-                          "Protocol",
-                          "Spice protocol major version",
-                          1, 2, 2,
-                          G_PARAM_READWRITE |
-                          G_PARAM_CONSTRUCT |
-                          G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceSession:uri:
-     *
-     * URI of the SPICE host to connect to. The URI is of the form
-     * spice://hostname?port=XXX or spice://hostname?tls_port=XXX
-     *
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_URI,
-         g_param_spec_string("uri",
-                             "URI",
-                             "Spice connection URI",
-                             NULL,
-                             G_PARAM_READWRITE |
-                             G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceSession:client-sockets:
-     *
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_CLIENT_SOCKETS,
-         g_param_spec_boolean("client-sockets",
-                          "Client sockets",
-                          "Sockets are provided by the client",
-                          FALSE,
-                          G_PARAM_READWRITE |
-                          G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceSession:pubkey:
-     *
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_PUBKEY,
-         g_param_spec_boxed("pubkey",
-                            "Pub Key",
-                            "Public key to check",
-                            G_TYPE_BYTE_ARRAY,
-                            G_PARAM_READWRITE |
-                            G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceSession:cert-subject:
-     *
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_CERT_SUBJECT,
-         g_param_spec_string("cert-subject",
-                             "Cert Subject",
-                             "Certificate subject to check",
-                             NULL,
-                             G_PARAM_READWRITE |
-                             G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceSession:verify:
-     *
-     * #SpiceSessionVerify bit field indicating which parts of the peer
-     * certificate should be checked
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_VERIFY,
-         g_param_spec_flags("verify",
-                            "Verify",
-                            "Certificate verification parameters",
-                            SPICE_TYPE_SESSION_VERIFY,
-                            SPICE_SESSION_VERIFY_HOSTNAME,
-                            G_PARAM_READWRITE |
-                            G_PARAM_CONSTRUCT |
-                            G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceSession:migration-state:
-     *
-     * #SpiceSessionMigration bit field indicating if a migration is in
-     * progress
-     *
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_MIGRATION_STATE,
-         g_param_spec_enum("migration-state",
-                           "Migration state",
-                           "Migration state",
-                           SPICE_TYPE_SESSION_MIGRATION,
-                           SPICE_SESSION_MIGRATION_NONE,
-                           G_PARAM_READABLE |
-                           G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceSession:disable-effects:
-     *
-     * A string array of effects to disable. The settings will
-     * be applied on new display channels. The following effets can be
-     * disabled "wallpaper", "font-smooth", "animation", and "all",
-     * which will disable all the effects. If NULL, don't apply changes.
-     *
-     * Since: 0.7
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_DISABLE_EFFECTS,
-         g_param_spec_boxed ("disable-effects",
-                             "Disable effects",
-                             "Comma-separated effects to disable",
-                             G_TYPE_STRV,
-                             G_PARAM_READWRITE |
-                             G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceSession:color-depth:
-     *
-     * Display color depth to set on new display channels. If 0, don't set.
-     *
-     * Since: 0.7
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_COLOR_DEPTH,
-         g_param_spec_int("color-depth",
-                          "Color depth",
-                          "Display channel color depth",
-                          0, 32, 0,
-                          G_PARAM_READWRITE |
-                          G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceSession:enable-smartcard:
-     *
-     * If set to TRUE, the smartcard channel will be enabled and smartcard
-     * events will be forwarded to the guest
-     *
-     * Since: 0.7
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_SMARTCARD,
-         g_param_spec_boolean("enable-smartcard",
-                          "Enable smartcard event forwarding",
-                          "Forward smartcard events to the SPICE server",
-                          FALSE,
-                          G_PARAM_READWRITE |
-                          G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceSession:enable-audio:
-     *
-     * If set to TRUE, the audio channels will be enabled for
-     * playback and recording.
-     *
-     * Since: 0.8
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_AUDIO,
-         g_param_spec_boolean("enable-audio",
-                          "Enable audio channels",
-                          "Enable audio channels",
-                          TRUE,
-                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
-                          G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceSession:smartcard-certificates:
-     *
-     * This property is used when one wants to simulate a smartcard with no
-     * hardware smartcard reader. If it's set to a NULL-terminated string
-     * array containing the names of 3 valid certificates, these will be
-     * used to simulate a smartcard in the guest
-     * See also spice_smartcard_manager_insert_card()
-     *
-     * Since: 0.7
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_SMARTCARD_CERTIFICATES,
-         g_param_spec_boxed("smartcard-certificates",
-                            "Smartcard certificates",
-                            "Smartcard certificates for software-based smartcards",
-                            G_TYPE_STRV,
-                            G_PARAM_READABLE |
-                            G_PARAM_WRITABLE |
-                            G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceSession:smartcard-db:
-     *
-     * Path to the NSS certificate database containing the certificates to
-     * use to simulate a software smartcard
-     *
-     * Since: 0.7
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_SMARTCARD_DB,
-         g_param_spec_string("smartcard-db",
-                              "Smartcard certificate database",
-                              "Path to the database for smartcard certificates",
-                              NULL,
-                              G_PARAM_READABLE |
-                              G_PARAM_WRITABLE |
-                              G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceSession:enable-usbredir:
-     *
-     * If set to TRUE, the usbredir channel will be enabled and USB devices
-     * can be redirected to the guest
-     *
-     * Since: 0.8
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_USBREDIR,
-         g_param_spec_boolean("enable-usbredir",
-                          "Enable USB device redirection",
-                          "Forward USB devices to the SPICE server",
-                          TRUE,
-                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
-                          G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceSession::inhibit-keyboard-grab:
-     *
-     * This boolean is set by the usbredir channel to indicate to #SpiceDisplay
-     * that the keyboard grab should be temporarily released, because it is
-     * going to invoke policykit. It will get reset when the usbredir channel
-     * is done with polickit.
-     *
-     * Since: 0.8
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_INHIBIT_KEYBOARD_GRAB,
-         g_param_spec_boolean("inhibit-keyboard-grab",
-                        "Inhibit Keyboard Grab",
-                        "Request that SpiceDisplays don't grab the keyboard",
-                        FALSE,
-                        G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceSession:ca:
-     *
-     * CA certificates in PEM format. The text data can contain
-     * several CA certificates identified by:
-     *
-     *  -----BEGIN CERTIFICATE-----
-     *  ... (CA certificate in base64 encoding) ...
-     *  -----END CERTIFICATE-----
-     *
-     * Since: 0.15
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_CA,
-         g_param_spec_boxed("ca",
-                            "CA",
-                            "The CA certificates data",
-                            G_TYPE_BYTE_ARRAY,
-                            G_PARAM_READWRITE |
-                            G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceSession:secure-channels:
-     *
-     * A string array of channel types to be secured.
-     *
-     * Since: 0.20
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_SECURE_CHANNELS,
-         g_param_spec_boxed ("secure-channels",
-                             "Secure channels",
-                             "Array of channel type to secure",
-                             G_TYPE_STRV,
-                             G_PARAM_READWRITE |
-                             G_PARAM_STATIC_STRINGS));
-
-
-    /**
-     * SpiceSession::channel-new:
-     * @session: the session that emitted the signal
-     * @channel: the new #SpiceChannel
-     *
-     * The #SpiceSession::channel-new signal is emitted each time a #SpiceChannel is created.
-     **/
-    signals[SPICE_SESSION_CHANNEL_NEW] =
-        g_signal_new("channel-new",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_FIRST,
-                     G_STRUCT_OFFSET(SpiceSessionClass, channel_new),
-                     NULL, NULL,
-                     g_cclosure_marshal_VOID__OBJECT,
-                     G_TYPE_NONE,
-                     1,
-                     SPICE_TYPE_CHANNEL);
-
-    /**
-     * SpiceSession::channel-destroy:
-     * @session: the session that emitted the signal
-     * @channel: the destroyed #SpiceChannel
-     *
-     * The #SpiceSession::channel-destroy signal is emitted each time a #SpiceChannel is destroyed.
-     **/
-    signals[SPICE_SESSION_CHANNEL_DESTROY] =
-        g_signal_new("channel-destroy",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_FIRST,
-                     G_STRUCT_OFFSET(SpiceSessionClass, channel_destroy),
-                     NULL, NULL,
-                     g_cclosure_marshal_VOID__OBJECT,
-                     G_TYPE_NONE,
-                     1,
-                     SPICE_TYPE_CHANNEL);
-
-    /**
-     * SpiceSession::mm-time-reset:
-     * @session: the session that emitted the signal
-     *
-     * The #SpiceSession::mm-time-reset is emitted when we identify discontinuity in mm-time
-     *
-     * Since 0.20
-     **/
-    signals[SPICE_SESSION_MM_TIME_RESET] =
-        g_signal_new("mm-time-reset",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_FIRST,
-                     0, NULL, NULL,
-                     g_cclosure_marshal_VOID__VOID,
-                     G_TYPE_NONE,
-                     0);
-
-    /**
-     * SpiceSession:read-only:
-     *
-     * Whether this connection is read-only mode.
-     *
-     * Since: 0.8
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_READ_ONLY,
-         g_param_spec_boolean("read-only", "Read-only",
-                              "Whether this connection is read-only mode",
-                              FALSE,
-                              G_PARAM_READWRITE |
-                              G_PARAM_CONSTRUCT |
-                              G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceSession:cache-size:
-     *
-     * Images cache size. If 0, don't set.
-     *
-     * Since: 0.9
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_CACHE_SIZE,
-         g_param_spec_int("cache-size",
-                          "Cache size",
-                          "Images cache size (bytes)",
-                          0, G_MAXINT, 0,
-                          G_PARAM_READWRITE |
-                          G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceSession:glz-window-size:
-     *
-     * Glz window size. If 0, don't set.
-     *
-     * Since: 0.9
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_GLZ_WINDOW_SIZE,
-         g_param_spec_int("glz-window-size",
-                          "Glz window size",
-                          "Glz window size (bytes)",
-                          0, LZ_MAX_WINDOW_SIZE * 4, 0,
-                          G_PARAM_READWRITE |
-                          G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceSession:name:
-     *
-     * Spice server name.
-     *
-     * Since: 0.11
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_NAME,
-         g_param_spec_string("name",
-                             "Name",
-                             "Spice server name",
-                             NULL,
-                             G_PARAM_READABLE |
-                             G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceSession:uuid:
-     *
-     * Spice server uuid.
-     *
-     * Since: 0.11
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_UUID,
-         g_param_spec_pointer("uuid",
-                              "UUID",
-                              "Spice server uuid",
-                              G_PARAM_READABLE |
-                              G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceSession:proxy:
-     *
-     * URI to the proxy server to use when doing network connection.
-     * of the form <![CDATA[ [protocol://]<host>[:port] ]]>
-     *
-     * Since: 0.17
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_PROXY,
-         g_param_spec_string("proxy",
-                             "Proxy",
-                             "The proxy server",
-                             NULL,
-                             G_PARAM_READWRITE |
-                             G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceSession:shared-dir:
-     *
-     * Location of the shared directory
-     *
-     * Since: 0.24
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_SHARED_DIR,
-         g_param_spec_string("shared-dir",
-                             "Shared directory",
-                             "Shared directory",
-                             g_get_user_special_dir(G_USER_DIRECTORY_PUBLIC_SHARE),
-                             G_PARAM_READWRITE |
-                             G_PARAM_CONSTRUCT |
-                             G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceSession:share-dir-ro:
-     *
-     * Whether to share the directory read-only.
-     *
-     * Since: 0.28
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_SHARE_DIR_RO,
-         g_param_spec_boolean("share-dir-ro",
-                              "Share directory read-only",
-                              "Share directory read-only",
-                              FALSE,
-                              G_PARAM_READWRITE |
-                              G_PARAM_CONSTRUCT |
-                              G_PARAM_STATIC_STRINGS));
-
-    g_type_class_add_private(klass, sizeof(SpiceSessionPrivate));
-}
-
-/* ------------------------------------------------------------------ */
-/* public functions                                                   */
-
-/**
- * spice_session_new:
- *
- * Creates a new Spice session.
- *
- * Returns: a new #SpiceSession
- **/
-SpiceSession *spice_session_new(void)
-{
-    return SPICE_SESSION(g_object_new(SPICE_TYPE_SESSION, NULL));
-}
-
-G_GNUC_INTERNAL
-SpiceSession *spice_session_new_from_session(SpiceSession *session)
-{
-    g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
-
-    SpiceSessionPrivate *s = session->priv;
-    SpiceSession *copy;
-    SpiceSessionPrivate *c;
-
-    if (s->client_provided_sockets) {
-        g_warning("migration with client provided fd is not supported yet");
-        return NULL;
-    }
-
-    copy = SPICE_SESSION(g_object_new(SPICE_TYPE_SESSION,
-                                      "host", NULL,
-                                      "ca-file", NULL,
-                                      NULL));
-    c = copy->priv;
-    g_clear_object(&c->proxy);
-
-    g_warn_if_fail(c->host == NULL);
-    g_warn_if_fail(c->unix_path == NULL);
-    g_warn_if_fail(c->tls_port == NULL);
-    g_warn_if_fail(c->username == NULL);
-    g_warn_if_fail(c->password == NULL);
-    g_warn_if_fail(c->ca_file == NULL);
-    g_warn_if_fail(c->ciphers == NULL);
-    g_warn_if_fail(c->cert_subject == NULL);
-    g_warn_if_fail(c->pubkey == NULL);
-    g_warn_if_fail(c->pubkey == NULL);
-    g_warn_if_fail(c->proxy == NULL);
-
-    g_object_get(session,
-                 "host", &c->host,
-                 "unix-path", &c->unix_path,
-                 "tls-port", &c->tls_port,
-                 "username", &c->username,
-                 "password", &c->password,
-                 "ca-file", &c->ca_file,
-                 "ciphers", &c->ciphers,
-                 "cert-subject", &c->cert_subject,
-                 "pubkey", &c->pubkey,
-                 "verify", &c->verify,
-                 "smartcard-certificates", &c->smartcard_certificates,
-                 "smartcard-db", &c->smartcard_db,
-                 "enable-smartcard", &c->smartcard,
-                 "enable-audio", &c->audio,
-                 "enable-usbredir", &c->usbredir,
-                 "ca", &c->ca,
-                 NULL);
-
-    c->client_provided_sockets = s->client_provided_sockets;
-    c->protocol = s->protocol;
-    c->connection_id = s->connection_id;
-    if (s->proxy)
-        c->proxy = g_object_ref(s->proxy);
-
-    return copy;
-}
-
-/**
- * spice_session_connect:
- * @session:
- *
- * Open the session using the #SpiceSession:host and
- * #SpiceSession:port.
- *
- * Returns: %FALSE if the connection failed.
- **/
-gboolean spice_session_connect(SpiceSession *session)
-{
-    SpiceSessionPrivate *s;
-
-    g_return_val_if_fail(SPICE_IS_SESSION(session), FALSE);
-
-    s = session->priv;
-    g_return_val_if_fail(!s->disconnecting, FALSE);
-
-    session_disconnect(session, TRUE);
-
-    s->client_provided_sockets = FALSE;
-
-    if (s->cmain == NULL)
-        s->cmain = spice_channel_new(session, SPICE_CHANNEL_MAIN, 0);
-
-    glz_decoder_window_clear(s->glz_window);
-    return spice_channel_connect(s->cmain);
-}
-
-/**
- * spice_session_open_fd:
- * @session:
- * @fd: a file descriptor (socket) or -1
- *
- * Open the session using the provided @fd socket file
- * descriptor. This is useful if you create the fd yourself, for
- * example to setup a SSH tunnel.
- *
- * Note however that additional sockets will be needed by all the channels
- * created for @session so users of this API should hook into
- * SpiceChannel::open-fd signal for each channel they are interested in, and
- * create and pass a new socket to the channel using #spice_channel_open_fd, in
- * the signal callback.
- *
- * If @fd is -1, a valid fd will be requested later via the
- * SpiceChannel::open-fd signal. Typically, you would want to just pass -1 as
- * @fd this call since you will have to hook to SpiceChannel::open-fd signal
- * anyway.
- *
- * Returns:
- **/
-gboolean spice_session_open_fd(SpiceSession *session, int fd)
-{
-    SpiceSessionPrivate *s;
-
-    g_return_val_if_fail(SPICE_IS_SESSION(session), FALSE);
-    g_return_val_if_fail(fd >= -1, FALSE);
-
-    s = session->priv;
-    g_return_val_if_fail(!s->disconnecting, FALSE);
-
-    session_disconnect(session, TRUE);
-
-    s->client_provided_sockets = TRUE;
-
-    if (s->cmain == NULL)
-        s->cmain = spice_channel_new(session, SPICE_CHANNEL_MAIN, 0);
-
-    glz_decoder_window_clear(s->glz_window);
-    return spice_channel_open_fd(s->cmain, fd);
-}
-
-G_GNUC_INTERNAL
-gboolean spice_session_get_client_provided_socket(SpiceSession *session)
-{
-    g_return_val_if_fail(SPICE_IS_SESSION(session), FALSE);
-
-    SpiceSessionPrivate *s = session->priv;
-
-    return s->client_provided_sockets;
-}
-
-static void cache_clear_all(SpiceSession *self)
-{
-    SpiceSessionPrivate *s = self->priv;
-
-    cache_clear(s->images);
-    glz_decoder_window_clear(s->glz_window);
-}
-
-G_GNUC_INTERNAL
-void spice_session_switching_disconnect(SpiceSession *self)
-{
-    g_return_if_fail(SPICE_IS_SESSION(self));
-
-    SpiceSessionPrivate *s = self->priv;
-    struct channel *item;
-    RingItem *ring, *next;
-
-    g_return_if_fail(s->cmain != NULL);
-
-    /* disconnect/destroy all but main channel */
-
-    for (ring = ring_get_head(&s->channels); ring != NULL; ring = next) {
-        next = ring_next(&s->channels, ring);
-        item = SPICE_CONTAINEROF(ring, struct channel, link);
-
-        if (item->channel == s->cmain)
-            continue;
-        spice_session_channel_destroy(self, item->channel);
-    }
-
-    g_warn_if_fail(!ring_is_empty(&s->channels)); /* ring_get_length() == 1 */
-
-    cache_clear_all(self);
-    s->connection_id = 0;
-}
-
-#define SWAP_STR(x, y) G_STMT_START { \
-    const gchar *tmp;                 \
-    const gchar *a = x;               \
-    const gchar *b = y;               \
-    tmp = a;                          \
-    a = b;                            \
-    b = tmp;                          \
-} G_STMT_END
-
-G_GNUC_INTERNAL
-void spice_session_start_migrating(SpiceSession *session,
-                                   gboolean full_migration)
-{
-    g_return_if_fail(SPICE_IS_SESSION(session));
-
-    SpiceSessionPrivate *s = session->priv;
-    SpiceSessionPrivate *m;
-
-    g_return_if_fail(s->migration != NULL);
-    m = s->migration->priv;
-    g_return_if_fail(m->migration_state == SPICE_SESSION_MIGRATION_CONNECTING);
-
-
-    s->full_migration = full_migration;
-    spice_session_set_migration_state(session, SPICE_SESSION_MIGRATION_MIGRATING);
-
-    /* swapping connection details happens after MIGRATION_CONNECTING state */
-    SWAP_STR(s->host, m->host);
-    SWAP_STR(s->port, m->port);
-    SWAP_STR(s->tls_port, m->tls_port);
-    SWAP_STR(s->unix_path, m->unix_path);
-
-    g_warn_if_fail(ring_get_length(&s->channels) == ring_get_length(&m->channels));
-
-    SPICE_DEBUG("migration channels left:%d (in migration:%d)",
-                ring_get_length(&s->channels), ring_get_length(&m->channels));
-    s->migration_left = spice_session_get_channels(session);
-}
-#undef SWAP_STR
-
-G_GNUC_INTERNAL
-SpiceChannel* spice_session_lookup_channel(SpiceSession *session, gint id, gint type)
-{
-    g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
-
-    RingItem *ring, *next;
-    SpiceSessionPrivate *s = session->priv;
-    struct channel *c;
-
-    for (ring = ring_get_head(&s->channels);
-         ring != NULL; ring = next) {
-        next = ring_next(&s->channels, ring);
-        c = SPICE_CONTAINEROF(ring, struct channel, link);
-        if (c == NULL || c->channel == NULL) {
-            g_warn_if_reached();
-            continue;
-        }
-
-        if (id == spice_channel_get_channel_id(c->channel) &&
-            type == spice_channel_get_channel_type(c->channel))
-            break;
-    }
-    g_return_val_if_fail(ring != NULL, NULL);
-
-    return c->channel;
-}
-
-G_GNUC_INTERNAL
-void spice_session_abort_migration(SpiceSession *session)
-{
-    g_return_if_fail(SPICE_IS_SESSION(session));
-
-    SpiceSessionPrivate *s = session->priv;
-    RingItem *ring, *next;
-    struct channel *c;
-
-    if (s->migration == NULL) {
-        SPICE_DEBUG("no migration in progress");
-        return;
-    }
-
-    SPICE_DEBUG("migration: abort");
-    if (s->migration_state != SPICE_SESSION_MIGRATION_MIGRATING)
-        goto end;
-
-    for (ring = ring_get_head(&s->channels);
-         ring != NULL; ring = next) {
-        next = ring_next(&s->channels, ring);
-        c = SPICE_CONTAINEROF(ring, struct channel, link);
-
-        if (g_list_find(s->migration_left, c->channel))
-            continue;
-
-        spice_channel_swap(c->channel,
-            spice_session_lookup_channel(s->migration,
-                                         spice_channel_get_channel_id(c->channel),
-                                         spice_channel_get_channel_type(c->channel)),
-                                         !s->full_migration);
-    }
-
-end:
-    g_list_free(s->migration_left);
-    s->migration_left = NULL;
-    session_disconnect(s->migration, FALSE);
-    g_object_unref(s->migration);
-    s->migration = NULL;
-
-    s->migrate_wait_init = FALSE;
-    if (s->after_main_init) {
-        g_source_remove(s->after_main_init);
-        s->after_main_init = 0;
-    }
-
-    spice_session_set_migration_state(session, SPICE_SESSION_MIGRATION_NONE);
-}
-
-G_GNUC_INTERNAL
-void spice_session_channel_migrate(SpiceSession *session, SpiceChannel *channel)
-{
-    g_return_if_fail(SPICE_IS_SESSION(session));
-
-    SpiceSessionPrivate *s = session->priv;
-    SpiceChannel *c;
-    gint id, type;
-
-    g_return_if_fail(s->migration != NULL);
-    g_return_if_fail(SPICE_IS_CHANNEL(channel));
-
-    id = spice_channel_get_channel_id(channel);
-    type = spice_channel_get_channel_type(channel);
-    CHANNEL_DEBUG(channel, "migrating channel id:%d type:%d", id, type);
-
-    c = spice_session_lookup_channel(s->migration, id, type);
-    g_return_if_fail(c != NULL);
-
-    if (!g_queue_is_empty(&c->priv->xmit_queue) && s->full_migration) {
-        CHANNEL_DEBUG(channel, "mig channel xmit queue is not empty. type %s", c->priv->name);
-    }
-    spice_channel_swap(channel, c, !s->full_migration);
-    s->migration_left = g_list_remove(s->migration_left, channel);
-
-    if (g_list_length(s->migration_left) == 0) {
-        CHANNEL_DEBUG(channel, "migration: all channel migrated, success");
-        session_disconnect(s->migration, FALSE);
-        g_object_unref(s->migration);
-        s->migration = NULL;
-        spice_session_set_migration_state(session, SPICE_SESSION_MIGRATION_NONE);
-    }
-}
-
-/* main context */
-static gboolean after_main_init(gpointer data)
-{
-    SpiceSession *self = data;
-    SpiceSessionPrivate *s = self->priv;
-    GList *l;
-
-    for (l = s->migration_left; l != NULL; ) {
-        SpiceChannel *channel = l->data;
-        l = l->next;
-
-        spice_session_channel_migrate(self, channel);
-        channel->priv->state = SPICE_CHANNEL_STATE_READY;
-        spice_channel_up(channel);
-    }
-
-    s->after_main_init = 0;
-    return FALSE;
-}
-
-/* coroutine context */
-G_GNUC_INTERNAL
-gboolean spice_session_migrate_after_main_init(SpiceSession *self)
-{
-    g_return_val_if_fail(SPICE_IS_SESSION(self), FALSE);
-
-    SpiceSessionPrivate *s = self->priv;
-
-    if (!s->migrate_wait_init)
-        return FALSE;
-
-    g_return_val_if_fail(g_list_length(s->migration_left) != 0, FALSE);
-    g_return_val_if_fail(s->after_main_init == 0, FALSE);
-
-    s->migrate_wait_init = FALSE;
-    s->after_main_init = g_idle_add(after_main_init, self);
-
-    return TRUE;
-}
-
-/* main context */
-G_GNUC_INTERNAL
-void spice_session_migrate_end(SpiceSession *self)
-{
-    g_return_if_fail(SPICE_IS_SESSION(self));
-
-    SpiceSessionPrivate *s = self->priv;
-    SpiceMsgOut *out;
-    GList *l;
-
-    g_return_if_fail(s->migration);
-    g_return_if_fail(s->migration->priv->cmain);
-    g_return_if_fail(g_list_length(s->migration_left) != 0);
-
-    /* disconnect and reset all channels */
-    for (l = s->migration_left; l != NULL; ) {
-        SpiceChannel *channel = l->data;
-        l = l->next;
-
-        if (!SPICE_IS_MAIN_CHANNEL(channel)) {
-            /* freeze other channels */
-            channel->priv->state = SPICE_CHANNEL_STATE_MIGRATING;
-        }
-
-        /* reset for migration, disconnect */
-        spice_channel_reset(channel, TRUE);
-
-        if (SPICE_IS_MAIN_CHANNEL(channel)) {
-            /* migrate main to target, so we can start talking */
-            spice_session_channel_migrate(self, channel);
-        }
-    }
-
-    cache_clear_all(self);
-
-    /* send MIGRATE_END to target */
-    out = spice_msg_out_new(s->cmain, SPICE_MSGC_MAIN_MIGRATE_END);
-    spice_msg_out_send(out);
-
-    /* now wait after main init for the rest of channels migration */
-    s->migrate_wait_init = TRUE;
-}
-
-/**
- * spice_session_get_read_only:
- * @session: a #SpiceSession
- *
- * Returns: wether the @session is in read-only mode.
- **/
-gboolean spice_session_get_read_only(SpiceSession *self)
-{
-    g_return_val_if_fail(SPICE_IS_SESSION(self), FALSE);
-
-    return self->priv->read_only;
-}
-
-static gboolean session_disconnect_idle(SpiceSession *self)
-{
-    SpiceSessionPrivate *s = self->priv;
-
-    session_disconnect(self, FALSE);
-    s->disconnecting = 0;
-
-    g_object_unref(self);
-
-    return FALSE;
-}
-
-/**
- * spice_session_disconnect:
- * @session:
- *
- * Disconnect the @session, and destroy all channels.
- **/
-void spice_session_disconnect(SpiceSession *session)
-{
-    SpiceSessionPrivate *s;
-
-    g_return_if_fail(SPICE_IS_SESSION(session));
-
-    s = session->priv;
-
-    SPICE_DEBUG("session: disconnecting %d", s->disconnecting);
-    if (s->disconnecting != 0)
-        return;
-
-    g_object_ref(session);
-    s->disconnecting = g_idle_add((GSourceFunc)session_disconnect_idle, session);
-}
-
-/**
- * spice_session_get_channels:
- * @session: a #SpiceSession
- *
- * Get the list of current channels associated with this @session.
- *
- * Returns: (element-type SpiceChannel) (transfer container): a #GList
- *          of unowned #SpiceChannel channels.
- **/
-GList *spice_session_get_channels(SpiceSession *session)
-{
-    SpiceSessionPrivate *s;
-    struct channel *item;
-    GList *list = NULL;
-    RingItem *ring;
-
-    g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
-    g_return_val_if_fail(session->priv != NULL, NULL);
-
-    s = session->priv;
-
-    for (ring = ring_get_head(&s->channels);
-         ring != NULL;
-         ring = ring_next(&s->channels, ring)) {
-        item = SPICE_CONTAINEROF(ring, struct channel, link);
-        list = g_list_append(list, item->channel);
-    }
-    return list;
-}
-
-/**
- * spice_session_has_channel_type:
- * @session: a #SpiceSession
- *
- * See if there is a @type channel in the channels associated with this
- * @session.
- *
- * Returns: TRUE if a @type channel is available otherwise FALSE.
- **/
-gboolean spice_session_has_channel_type(SpiceSession *session, gint type)
-{
-    SpiceSessionPrivate *s;
-    struct channel *item;
-    RingItem *ring;
-
-    g_return_val_if_fail(SPICE_IS_SESSION(session), FALSE);
-    g_return_val_if_fail(session->priv != NULL, FALSE);
-
-    s = session->priv;
-
-    for (ring = ring_get_head(&s->channels);
-         ring != NULL;
-         ring = ring_next(&s->channels, ring)) {
-        item = SPICE_CONTAINEROF(ring, struct channel, link);
-        if (spice_channel_get_channel_type(item->channel) == type) {
-            return TRUE;
-        }
-    }
-    return FALSE;
-}
-
-/* ------------------------------------------------------------------ */
-/* private functions                                                  */
-
-typedef struct spice_open_host spice_open_host;
-
-struct spice_open_host {
-    struct coroutine *from;
-    SpiceSession *session;
-    SpiceChannel *channel;
-    SpiceURI *proxy;
-    int port;
-    GCancellable *cancellable;
-    GError *error;
-    GSocketConnection *connection;
-    GSocketClient *client;
-};
-
-static void socket_client_connect_ready(GObject *source_object, GAsyncResult *result,
-                                        gpointer data)
-{
-    GSocketClient *client = G_SOCKET_CLIENT(source_object);
-    spice_open_host *open_host = data;
-    GSocketConnection *connection = NULL;
-
-    CHANNEL_DEBUG(open_host->channel, "connect ready");
-    connection = g_socket_client_connect_finish(client, result, &open_host->error);
-    if (connection == NULL) {
-        g_warn_if_fail(open_host->error != NULL);
-        goto end;
-    }
-
-    open_host->connection = connection;
-
-end:
-    coroutine_yieldto(open_host->from, NULL);
-}
-
-/* main context */
-static void open_host_connectable_connect(spice_open_host *open_host, GSocketConnectable *connectable)
-{
-    CHANNEL_DEBUG(open_host->channel, "connecting %p...", open_host);
-
-    g_socket_client_connect_async(open_host->client, connectable,
-                                  open_host->cancellable,
-                                  socket_client_connect_ready, open_host);
-}
-
-/* main context */
-static void proxy_lookup_ready(GObject *source_object, GAsyncResult *result,
-                               gpointer data)
-{
-    spice_open_host *open_host = data;
-    SpiceSession *session = open_host->session;
-    SpiceSessionPrivate *s = session->priv;
-    GList *addresses = NULL, *it;
-    GSocketAddress *address;
-
-    SPICE_DEBUG("proxy lookup ready");
-    addresses = g_resolver_lookup_by_name_finish(G_RESOLVER(source_object),
-                                                 result, &open_host->error);
-    if (addresses == NULL || open_host->error) {
-        g_prefix_error(&open_host->error, "SPICE proxy: ");
-        coroutine_yieldto(open_host->from, NULL);
-        return;
-    }
-
-    for (it = addresses; it != NULL; it = it->next) {
-        address = g_proxy_address_new(G_INET_ADDRESS(it->data),
-                                      spice_uri_get_port(open_host->proxy),
-                                      spice_uri_get_scheme(open_host->proxy),
-                                      s->host, open_host->port,
-                                      spice_uri_get_user(open_host->proxy),
-                                      spice_uri_get_password(open_host->proxy));
-        if (address != NULL)
-            break;
-    }
-
-    open_host_connectable_connect(open_host, G_SOCKET_CONNECTABLE(address));
-    g_resolver_free_addresses(addresses);
-    g_object_unref(address);
-}
-
-/* main context */
-static gboolean open_host_idle_cb(gpointer data)
-{
-    spice_open_host *open_host = data;
-    SpiceSessionPrivate *s;
-
-    g_return_val_if_fail(open_host != NULL, FALSE);
-    g_return_val_if_fail(open_host->connection == NULL, FALSE);
-
-    if (spice_channel_get_session(open_host->channel) != open_host->session)
-        return FALSE;
-
-    s = open_host->session->priv;
-    open_host->proxy = s->proxy;
-    if (open_host->error != NULL) {
-        coroutine_yieldto(open_host->from, NULL);
-        return FALSE;
-    }
-
-    if (open_host->proxy) {
-        g_resolver_lookup_by_name_async(g_resolver_get_default(),
-                                        spice_uri_get_hostname(open_host->proxy),
-                                        open_host->cancellable,
-                                        proxy_lookup_ready, open_host);
-    } else {
-        GSocketConnectable *address = NULL;
-
-        if (s->unix_path) {
-            SPICE_DEBUG("open unix path %s", s->unix_path);
-#ifdef G_OS_UNIX
-            address = G_SOCKET_CONNECTABLE(g_unix_socket_address_new(s->unix_path));
-#else
-            g_set_error_literal(&open_host->error, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
-                                "Unix path unsupported on this platform");
-#endif
-        } else {
-            SPICE_DEBUG("open host %s:%d", s->host, open_host->port);
-            address = g_network_address_new(s->host, open_host->port);
-        }
-
-        if (address == NULL || open_host->error != NULL) {
-            coroutine_yieldto(open_host->from, NULL);
-            return FALSE;
-        }
-
-        open_host_connectable_connect(open_host, address);
-        g_object_unref(address);
-    }
-
-    if (open_host->proxy != NULL) {
-        gchar *str = spice_uri_to_string(open_host->proxy);
-        SPICE_DEBUG("(with proxy %s)", str);
-        g_free(str);
-    }
-
-    return FALSE;
-}
-
-#define SOCKET_TIMEOUT 10
-
-/* coroutine context */
-G_GNUC_INTERNAL
-GSocketConnection* spice_session_channel_open_host(SpiceSession *session, SpiceChannel *channel,
-                                                   gboolean *use_tls, GError **error)
-{
-    g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
-
-    SpiceSessionPrivate *s = session->priv;
-    SpiceChannelPrivate *c = channel->priv;
-    spice_open_host open_host = { 0, };
-    gchar *port, *endptr;
-
-    // FIXME: make open_host() cancellable
-    open_host.from = coroutine_self();
-    open_host.session = session;
-    open_host.channel = channel;
-
-    const char *name = spice_channel_type_to_string(c->channel_type);
-    if (spice_strv_contains(s->secure_channels, "all") ||
-        spice_strv_contains(s->secure_channels, name))
-        *use_tls = TRUE;
-
-    if (s->unix_path) {
-        if (*use_tls) {
-            CHANNEL_DEBUG(channel, "No TLS for Unix sockets");
-            return NULL;
-        }
-    } else {
-        port = *use_tls ? s->tls_port : s->port;
-        if (port == NULL) {
-            g_debug("Missing port value, not attempting %s connection.",
-                    *use_tls?"TLS":"unencrypted");
-            return NULL;
-        }
-
-        open_host.port = strtol(port, &endptr, 10);
-        if (*port == '\0' || *endptr != '\0' ||
-            open_host.port <= 0 || open_host.port > G_MAXUINT16) {
-            g_warning("Invalid port value %s", port);
-            return NULL;
-        }
-    }
-    if (*use_tls) {
-        CHANNEL_DEBUG(channel, "Using TLS, port %d", open_host.port);
-    } else {
-        CHANNEL_DEBUG(channel, "Using plain text, port %d", open_host.port);
-    }
-
-    open_host.client = g_socket_client_new();
-    g_socket_client_set_enable_proxy(open_host.client, s->proxy != NULL);
-    g_socket_client_set_timeout(open_host.client, SOCKET_TIMEOUT);
-
-    g_idle_add(open_host_idle_cb, &open_host);
-    /* switch to main loop and wait for connection */
-    coroutine_yield(NULL);
-
-    if (open_host.error != NULL) {
-        CHANNEL_DEBUG(channel, "open host: %s", open_host.error->message);
-        g_propagate_error(error, open_host.error);
-    } else if (open_host.connection != NULL) {
-        GSocket *socket;
-        socket = g_socket_connection_get_socket(open_host.connection);
-        g_socket_set_timeout(socket, 0);
-        g_socket_set_blocking(socket, FALSE);
-        g_socket_set_keepalive(socket, TRUE);
-    }
-
-    g_clear_object(&open_host.client);
-    return open_host.connection;
-}
-
-
-G_GNUC_INTERNAL
-void spice_session_channel_new(SpiceSession *session, SpiceChannel *channel)
-{
-    g_return_if_fail(SPICE_IS_SESSION(session));
-    g_return_if_fail(SPICE_IS_CHANNEL(channel));
-
-    SpiceSessionPrivate *s = session->priv;
-    struct channel *item;
-
-
-    item = g_new0(struct channel, 1);
-    item->channel = channel;
-    ring_add(&s->channels, &item->link);
-
-    if (SPICE_IS_MAIN_CHANNEL(channel)) {
-        gboolean all = spice_strv_contains(s->disable_effects, "all");
-
-        g_object_set(channel,
-                     "disable-wallpaper", all || spice_strv_contains(s->disable_effects, "wallpaper"),
-                     "disable-font-smooth", all || spice_strv_contains(s->disable_effects, "font-smooth"),
-                     "disable-animation", all || spice_strv_contains(s->disable_effects, "animation"),
-                     NULL);
-        if (s->color_depth != 0)
-            g_object_set(channel, "color-depth", s->color_depth, NULL);
-
-        CHANNEL_DEBUG(channel, "new main channel, switching");
-        s->cmain = channel;
-    } else if (SPICE_IS_PLAYBACK_CHANNEL(channel)) {
-        g_warn_if_fail(s->playback_channel == NULL);
-        s->playback_channel = SPICE_PLAYBACK_CHANNEL(channel);
-    }
-
-    g_signal_emit(session, signals[SPICE_SESSION_CHANNEL_NEW], 0, channel);
-}
-
-static void spice_session_channel_destroy(SpiceSession *session, SpiceChannel *channel)
-{
-    g_return_if_fail(SPICE_IS_SESSION(session));
-    g_return_if_fail(SPICE_IS_CHANNEL(channel));
-
-    SpiceSessionPrivate *s = session->priv;
-    struct channel *item = NULL;
-    RingItem *ring;
-
-    if (s->migration_left)
-        s->migration_left = g_list_remove(s->migration_left, channel);
-
-    for (ring = ring_get_head(&s->channels); ring != NULL;
-         ring = ring_next(&s->channels, ring)) {
-        item = SPICE_CONTAINEROF(ring, struct channel, link);
-        if (item->channel == channel)
-            break;
-    }
-
-    g_return_if_fail(ring != NULL);
-
-    if (channel == s->cmain) {
-        CHANNEL_DEBUG(channel, "the session lost the main channel");
-        s->cmain = NULL;
-    }
-
-    ring_remove(&item->link);
-    free(item);
-
-    g_signal_emit(session, signals[SPICE_SESSION_CHANNEL_DESTROY], 0, channel);
-
-    g_clear_object(&channel->priv->session);
-    spice_channel_disconnect(channel, SPICE_CHANNEL_NONE);
-    g_object_unref(channel);
-}
-
-G_GNUC_INTERNAL
-void spice_session_set_connection_id(SpiceSession *session, int id)
-{
-    g_return_if_fail(SPICE_IS_SESSION(session));
-
-    SpiceSessionPrivate *s = session->priv;
-
-    s->connection_id = id;
-}
-
-G_GNUC_INTERNAL
-int spice_session_get_connection_id(SpiceSession *session)
-{
-    g_return_val_if_fail(SPICE_IS_SESSION(session), -1);
-
-    SpiceSessionPrivate *s = session->priv;
-
-    return s->connection_id;
-}
-
-G_GNUC_INTERNAL
-guint32 spice_session_get_mm_time(SpiceSession *session)
-{
-    g_return_val_if_fail(SPICE_IS_SESSION(session), 0);
-
-    SpiceSessionPrivate *s = session->priv;
-
-    /* FIXME: we may want to estimate the drift of clocks, and well,
-       do something better than this trivial approach */
-    return s->mm_time + (g_get_monotonic_time() - s->mm_time_at_clock) / 1000;
-}
-
-#define MM_TIME_DIFF_RESET_THRESH 500 // 0.5 sec
-
-G_GNUC_INTERNAL
-void spice_session_set_mm_time(SpiceSession *session, guint32 time)
-{
-    g_return_if_fail(SPICE_IS_SESSION(session));
-
-    SpiceSessionPrivate *s = session->priv;
-    guint32 old_time;
-
-    old_time = spice_session_get_mm_time(session);
-
-    s->mm_time = time;
-    s->mm_time_at_clock = g_get_monotonic_time();
-    SPICE_DEBUG("set mm time: %u", spice_session_get_mm_time(session));
-    if (time > old_time + MM_TIME_DIFF_RESET_THRESH ||
-        time < old_time) {
-        SPICE_DEBUG("%s: mm-time-reset, old %u, new %u", __FUNCTION__, old_time, s->mm_time);
-        g_coroutine_signal_emit(session, signals[SPICE_SESSION_MM_TIME_RESET], 0);
-    }
-}
-
-G_GNUC_INTERNAL
-void spice_session_set_port(SpiceSession *session, int port, gboolean tls)
-{
-    const char *prop = tls ? "tls-port" : "port";
-    char *tmp;
-
-    g_return_if_fail(SPICE_IS_SESSION(session));
-
-    /* old spicec client doesn't accept port == 0, see Migrate::start */
-    tmp = port > 0 ? g_strdup_printf("%d", port) : NULL;
-    g_object_set(session, prop, tmp, NULL);
-    g_free(tmp);
-}
-
-G_GNUC_INTERNAL
-void spice_session_get_pubkey(SpiceSession *session, guint8 **pubkey, guint *size)
-{
-    g_return_if_fail(SPICE_IS_SESSION(session));
-    g_return_if_fail(pubkey != NULL);
-    g_return_if_fail(size != NULL);
-
-    SpiceSessionPrivate *s = session->priv;
-
-    *pubkey = s->pubkey ? s->pubkey->data : NULL;
-    *size = s->pubkey ? s->pubkey->len : 0;
-}
-
-G_GNUC_INTERNAL
-void spice_session_get_ca(SpiceSession *session, guint8 **ca, guint *size)
-{
-    g_return_if_fail(SPICE_IS_SESSION(session));
-    g_return_if_fail(ca != NULL);
-    g_return_if_fail(size != NULL);
-
-    SpiceSessionPrivate *s = session->priv;
-
-    *ca = s->ca ? s->ca->data : NULL;
-    *size = s->ca ? s->ca->len : 0;
-}
-
-G_GNUC_INTERNAL
-guint spice_session_get_verify(SpiceSession *session)
-{
-    g_return_val_if_fail(SPICE_IS_SESSION(session), 0);
-
-    SpiceSessionPrivate *s = session->priv;
-
-    return s->verify;
-}
-
-G_GNUC_INTERNAL
-void spice_session_set_migration_state(SpiceSession *session, SpiceSessionMigration state)
-{
-    g_return_if_fail(SPICE_IS_SESSION(session));
-
-    SpiceSessionPrivate *s = session->priv;
-
-    if (state == SPICE_SESSION_MIGRATION_CONNECTING)
-        s->for_migration = true;
-
-    s->migration_state = state;
-    g_coroutine_object_notify(G_OBJECT(session), "migration-state");
-}
-
-G_GNUC_INTERNAL
-const gchar* spice_session_get_username(SpiceSession *session)
-{
-    g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
-
-    SpiceSessionPrivate *s = session->priv;
-
-    return s->username;
-}
-
-G_GNUC_INTERNAL
-const gchar* spice_session_get_password(SpiceSession *session)
-{
-    g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
-
-    SpiceSessionPrivate *s = session->priv;
-
-    return s->password;
-}
-
-G_GNUC_INTERNAL
-const gchar* spice_session_get_host(SpiceSession *session)
-{
-    g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
-
-    SpiceSessionPrivate *s = session->priv;
-
-    return s->host;
-}
-
-G_GNUC_INTERNAL
-const gchar* spice_session_get_cert_subject(SpiceSession *session)
-{
-    g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
-
-    SpiceSessionPrivate *s = session->priv;
-
-    return s->cert_subject;
-}
-
-G_GNUC_INTERNAL
-const gchar* spice_session_get_ciphers(SpiceSession *session)
-{
-    g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
-
-    SpiceSessionPrivate *s = session->priv;
-
-    return s->ciphers;
-}
-
-G_GNUC_INTERNAL
-const gchar* spice_session_get_ca_file(SpiceSession *session)
-{
-    g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
-
-    SpiceSessionPrivate *s = session->priv;
-
-    return s->ca_file;
-}
-
-G_GNUC_INTERNAL
-void spice_session_get_caches(SpiceSession *session,
-                              display_cache **images,
-                              SpiceGlzDecoderWindow **glz_window)
-{
-    g_return_if_fail(SPICE_IS_SESSION(session));
-
-    SpiceSessionPrivate *s = session->priv;
-
-    if (images)
-        *images = s->images;
-    if (glz_window)
-        *glz_window = s->glz_window;
-}
-
-G_GNUC_INTERNAL
-void spice_session_set_caches_hints(SpiceSession *session,
-                                    uint32_t pci_ram_size,
-                                    uint32_t n_display_channels)
-{
-    g_return_if_fail(SPICE_IS_SESSION(session));
-
-    SpiceSessionPrivate *s = session->priv;
-
-    s->pci_ram_size = pci_ram_size;
-    s->n_display_channels = n_display_channels;
-
-    /* TODO: when setting cache and window size, we should consider the client's
-     *       available memory and the number of display channels */
-    if (s->images_cache_size == 0) {
-        s->images_cache_size = IMAGES_CACHE_SIZE_DEFAULT;
-    }
-
-    if (s->glz_window_size == 0) {
-        s->glz_window_size = MIN(MAX_GLZ_WINDOW_SIZE_DEFAULT, pci_ram_size / 2);
-        s->glz_window_size = MAX(MIN_GLZ_WINDOW_SIZE_DEFAULT, s->glz_window_size);
-    }
-}
-
-G_GNUC_INTERNAL
-guint spice_session_get_n_display_channels(SpiceSession *session)
-{
-    g_return_val_if_fail(session != NULL, 0);
-
-    return session->priv->n_display_channels;
-}
-
-G_GNUC_INTERNAL
-void spice_session_set_uuid(SpiceSession *session, guint8 uuid[16])
-{
-    g_return_if_fail(SPICE_IS_SESSION(session));
-
-    SpiceSessionPrivate *s = session->priv;
-
-    memcpy(s->uuid, uuid, sizeof(s->uuid));
-
-    g_coroutine_object_notify(G_OBJECT(session), "uuid");
-}
-
-G_GNUC_INTERNAL
-void spice_session_set_name(SpiceSession *session, const gchar *name)
-{
-    g_return_if_fail(SPICE_IS_SESSION(session));
-
-    SpiceSessionPrivate *s = session->priv;
-
-    g_free(s->name);
-    s->name = g_strdup(name);
-
-    g_coroutine_object_notify(G_OBJECT(session), "name");
-}
-
-G_GNUC_INTERNAL
-void spice_session_sync_playback_latency(SpiceSession *session)
-{
-    g_return_if_fail(SPICE_IS_SESSION(session));
-
-    SpiceSessionPrivate *s = session->priv;
-
-    if (s->playback_channel &&
-        spice_playback_channel_is_active(s->playback_channel)) {
-        spice_playback_channel_sync_latency(s->playback_channel);
-    } else {
-        SPICE_DEBUG("%s: not implemented when there isn't audio playback", __FUNCTION__);
-    }
-}
-
-G_GNUC_INTERNAL
-gboolean spice_session_is_playback_active(SpiceSession *session)
-{
-    g_return_val_if_fail(SPICE_IS_SESSION(session), FALSE);
-
-    SpiceSessionPrivate *s = session->priv;
-
-    return (s->playback_channel &&
-        spice_playback_channel_is_active(s->playback_channel));
-}
-
-G_GNUC_INTERNAL
-guint32 spice_session_get_playback_latency(SpiceSession *session)
-{
-    g_return_val_if_fail(SPICE_IS_SESSION(session), 0);
-
-    SpiceSessionPrivate *s = session->priv;
-
-    if (s->playback_channel &&
-        spice_playback_channel_is_active(s->playback_channel)) {
-        return spice_playback_channel_get_latency(s->playback_channel);
-    } else {
-        SPICE_DEBUG("%s: not implemented when there isn't audio playback", __FUNCTION__);
-        return 0;
-    }
-}
-
-G_GNUC_INTERNAL
-const gchar* spice_session_get_shared_dir(SpiceSession *session)
-{
-    g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
-
-    SpiceSessionPrivate *s = session->priv;
-
-    return s->shared_dir;
-}
-
-G_GNUC_INTERNAL
-void spice_session_set_shared_dir(SpiceSession *session, const gchar *dir)
-{
-    g_return_if_fail(SPICE_IS_SESSION(session));
-
-    SpiceSessionPrivate *s = session->priv;
-
-    g_free(s->shared_dir);
-    s->shared_dir = g_strdup(dir);
-}
-
-/**
- * spice_session_get_proxy_uri:
- * @session: a #SpiceSession
- *
- * Returns: (transfer none): the session proxy #SpiceURI or %NULL.
- * Since: 0.24
- **/
-SpiceURI *spice_session_get_proxy_uri(SpiceSession *session)
-{
-    SpiceSessionPrivate *s;
-
-    g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
-    g_return_val_if_fail(session->priv != NULL, NULL);
-
-    s = session->priv;
-
-    return s->proxy;
-}
-
-/**
- * spice_audio_get:
- * @session: the #SpiceSession to connect to
- * @context: (allow-none): a #GMainContext to attach to (or %NULL for default).
- *
- * Gets the #SpiceAudio associated with the passed in #SpiceSession.
- * A new #SpiceAudio instance will be created the first time this
- * function is called for a certain #SpiceSession.
- *
- * Note that this function returns a weak reference, which should not be used
- * after the #SpiceSession itself has been unref-ed by the caller.
- *
- * Returns: (transfer none): a weak reference to a #SpiceAudio
- * instance or %NULL if failed.
- **/
-SpiceAudio *spice_audio_get(SpiceSession *session, GMainContext *context)
-{
-    static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
-    SpiceAudio *self;
-
-    g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
-
-    g_static_mutex_lock(&mutex);
-    self = session->priv->audio_manager;
-    if (self == NULL) {
-        self = spice_audio_new(session, context, NULL);
-        session->priv->audio_manager = self;
-    }
-    g_static_mutex_unlock(&mutex);
-
-    return self;
-}
-
-/**
- * spice_usb_device_manager_get:
- * @session: #SpiceSession for which to get the #SpiceUsbDeviceManager
- *
- * Gets the #SpiceUsbDeviceManager associated with the passed in #SpiceSession.
- * A new #SpiceUsbDeviceManager instance will be created the first time this
- * function is called for a certain #SpiceSession.
- *
- * Note that this function returns a weak reference, which should not be used
- * after the #SpiceSession itself has been unref-ed by the caller.
- *
- * Returns: (transfer none): a weak reference to the #SpiceUsbDeviceManager associated with the passed in #SpiceSession
- */
-SpiceUsbDeviceManager *spice_usb_device_manager_get(SpiceSession *session,
-                                                    GError **err)
-{
-    SpiceUsbDeviceManager *self;
-    static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
-
-    g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
-    g_return_val_if_fail(err == NULL || *err == NULL, NULL);
-
-    g_static_mutex_lock(&mutex);
-    self = session->priv->usb_manager;
-    if (self == NULL) {
-        self = g_initable_new(SPICE_TYPE_USB_DEVICE_MANAGER, NULL, err,
-                              "session", session, NULL);
-        session->priv->usb_manager = self;
-    }
-    g_static_mutex_unlock(&mutex);
-
-    return self;
-}
-
-G_GNUC_INTERNAL
-gboolean spice_session_get_audio_enabled(SpiceSession *session)
-{
-    g_return_val_if_fail(SPICE_IS_SESSION(session), FALSE);
-
-    return session->priv->audio;
-}
-
-G_GNUC_INTERNAL
-gboolean spice_session_get_usbredir_enabled(SpiceSession *session)
-{
-    g_return_val_if_fail(SPICE_IS_SESSION(session), FALSE);
-
-    return session->priv->usbredir;
-}
-
-G_GNUC_INTERNAL
-gboolean spice_session_get_smartcard_enabled(SpiceSession *session)
-{
-    g_return_val_if_fail(SPICE_IS_SESSION(session), FALSE);
-
-    return session->priv->smartcard;
-}
-
-G_GNUC_INTERNAL
-PhodavServer* spice_session_get_webdav_server(SpiceSession *session)
-{
-    SpiceSessionPrivate *priv;
-
-    g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
-    priv = session->priv;
-
-#ifdef USE_PHODAV
-    static GMutex mutex;
-
-    const gchar *shared_dir = spice_session_get_shared_dir(session);
-    if (shared_dir == NULL) {
-        g_debug("No shared dir set, not creating webdav server");
-        return NULL;
-    }
-
-    g_mutex_lock(&mutex);
-
-    if (priv->webdav)
-        goto end;
-
-    priv->webdav = phodav_server_new(shared_dir);
-    g_object_bind_property(session,  "share-dir-ro",
-                           priv->webdav, "read-only",
-                           G_BINDING_SYNC_CREATE|G_BINDING_BIDIRECTIONAL);
-    g_object_bind_property(session,  "shared-dir",
-                           priv->webdav, "root",
-                           G_BINDING_SYNC_CREATE|G_BINDING_BIDIRECTIONAL);
-
-end:
-    g_mutex_unlock(&mutex);
-#endif
-
-    return priv->webdav;
-}
-
-/**
- * spice_session_is_for_migration:
- * @session: a Spice session
- *
- * During seamless migration, channels may be created to establish a
- * connection with the target, but they are temporary and should only
- * handle migration steps. In order to avoid other interactions with
- * the client, channels should check this value.
- *
- * Returns: %TRUE if the session is a copy created during migration
- * Since: 0.27
- **/
-gboolean spice_session_is_for_migration(SpiceSession *session)
-{
-    g_return_val_if_fail(SPICE_IS_SESSION(session), FALSE);
-
-    return session->priv->for_migration;
-}
-
-G_GNUC_INTERNAL
-void spice_session_set_main_channel(SpiceSession *session, SpiceChannel *channel)
-{
-    g_return_if_fail(SPICE_IS_SESSION(session));
-    g_return_if_fail(SPICE_IS_CHANNEL(channel));
-    g_return_if_fail(session->priv->cmain == NULL);
-
-    session->priv->cmain = channel;
-}
-
-G_GNUC_INTERNAL
-gboolean spice_session_set_migration_session(SpiceSession *session, SpiceSession *mig_session)
-{
-    g_return_val_if_fail(SPICE_IS_SESSION(session), FALSE);
-    g_return_val_if_fail(SPICE_IS_SESSION(mig_session), FALSE);
-    g_return_val_if_fail(session->priv->migration == NULL, FALSE);
-
-    session->priv->migration = mig_session;
-
-    return TRUE;
-}
diff --git a/gtk/spice-session.h b/gtk/spice-session.h
deleted file mode 100644
index 750af29..0000000
--- a/gtk/spice-session.h
+++ /dev/null
@@ -1,103 +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_CLIENT_SESSION_H__
-#define __SPICE_CLIENT_SESSION_H__
-
-#include <glib-object.h>
-#include "spice-types.h"
-#include "spice-uri.h"
-#include "spice-glib-enums.h"
-#include "spice-util.h"
-
-G_BEGIN_DECLS
-
-#define SPICE_TYPE_SESSION            (spice_session_get_type ())
-#define SPICE_SESSION(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), SPICE_TYPE_SESSION, SpiceSession))
-#define SPICE_SESSION_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SPICE_TYPE_SESSION, SpiceSessionClass))
-#define SPICE_IS_SESSION(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SPICE_TYPE_SESSION))
-#define SPICE_IS_SESSION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SPICE_TYPE_SESSION))
-#define SPICE_SESSION_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SPICE_TYPE_SESSION, SpiceSessionClass))
-
-/**
- * SpiceSessionVerify:
- * @SPICE_SESSION_VERIFY_PUBKEY: verify certificate public key matching
- * @SPICE_SESSION_VERIFY_HOSTNAME: verify certificate hostname matching
- * @SPICE_SESSION_VERIFY_SUBJECT: verify certificate subject matching
- *
- * Peer certificate verification parameters flags.
- **/
-typedef enum {
-    SPICE_SESSION_VERIFY_PUBKEY   = (1 << 0),
-    SPICE_SESSION_VERIFY_HOSTNAME = (1 << 1),
-    SPICE_SESSION_VERIFY_SUBJECT  = (1 << 2),
-} SpiceSessionVerify;
-
-/**
- * SpiceSessionMigration:
- * @SPICE_SESSION_MIGRATION_NONE: no migration going on
- * @SPICE_SESSION_MIGRATION_SWITCHING: the session is switching host (destroy and reconnect)
- * @SPICE_SESSION_MIGRATION_MIGRATING: the session is migrating seamlessly (reconnect)
- * @SPICE_SESSION_MIGRATION_CONNECTING: the migration is connecting to destination (Since: 0.27)
- *
- * Session migration state.
- **/
-typedef enum {
-    SPICE_SESSION_MIGRATION_NONE,
-    SPICE_SESSION_MIGRATION_SWITCHING,
-    SPICE_SESSION_MIGRATION_MIGRATING,
-    SPICE_SESSION_MIGRATION_CONNECTING,
-} SpiceSessionMigration;
-
-struct _SpiceSession
-{
-    GObject parent;
-    SpiceSessionPrivate *priv;
-    /* Do not add fields to this struct */
-};
-
-struct _SpiceSessionClass
-{
-    GObjectClass parent_class;
-
-    /* signals */
-    void (*channel_new)(SpiceSession *session, SpiceChannel *channel);
-    void (*channel_destroy)(SpiceSession *session, SpiceChannel *channel);
-
-    /*< private >*/
-    /*
-     * If adding fields to this struct, remove corresponding
-     * amount of padding to avoid changing overall struct size
-     */
-    gchar _spice_reserved[SPICE_RESERVED_PADDING];
-};
-
-GType spice_session_get_type(void);
-
-SpiceSession *spice_session_new(void);
-gboolean spice_session_connect(SpiceSession *session);
-gboolean spice_session_open_fd(SpiceSession *session, int fd);
-void spice_session_disconnect(SpiceSession *session);
-GList *spice_session_get_channels(SpiceSession *session);
-gboolean spice_session_has_channel_type(SpiceSession *session, gint type);
-gboolean spice_session_get_read_only(SpiceSession *session);
-SpiceURI *spice_session_get_proxy_uri(SpiceSession *session);
-gboolean spice_session_is_for_migration(SpiceSession *session);
-
-G_END_DECLS
-
-#endif /* __SPICE_CLIENT_SESSION_H__ */
diff --git a/gtk/spice-types.h b/gtk/spice-types.h
deleted file mode 100644
index f149094..0000000
--- a/gtk/spice-types.h
+++ /dev/null
@@ -1,35 +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_CLIENT_TYPES_H__
-#define __SPICE_CLIENT_TYPES_H__
-
-G_BEGIN_DECLS
-
-/* SpiceSession */
-typedef struct _SpiceSession SpiceSession;
-typedef struct _SpiceSessionClass SpiceSessionClass;
-typedef struct _SpiceSessionPrivate SpiceSessionPrivate;
-
-/* SpiceChannel */
-typedef struct _SpiceChannel SpiceChannel;
-typedef struct _SpiceChannelClass SpiceChannelClass;
-typedef struct _SpiceChannelPrivate SpiceChannelPrivate;
-
-G_END_DECLS
-
-#endif /* __SPICE_CLIENT_TYPES_H__ */
diff --git a/gtk/spice-uri-priv.h b/gtk/spice-uri-priv.h
deleted file mode 100644
index 54351de..0000000
--- a/gtk/spice-uri-priv.h
+++ /dev/null
@@ -1,30 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-  Copyright (C) 2012 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_URI_PRIV_H__
-#define __SPICE_URI_PRIV_H__
-
-#include "spice-uri.h"
-
-G_BEGIN_DECLS
-
-SpiceURI* spice_uri_new(void);
-gboolean spice_uri_parse(SpiceURI* self, const gchar* uri, GError** error);
-
-G_END_DECLS
-
-#endif /* __SPICE_URI_PRIV_H__ */
diff --git a/gtk/spice-uri.c b/gtk/spice-uri.c
deleted file mode 100644
index 82aefdb..0000000
--- a/gtk/spice-uri.c
+++ /dev/null
@@ -1,462 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2012 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 <stdlib.h>
-#include <string.h>
-
-#include "glib-compat.h"
-#include "spice-client.h"
-#include "spice-uri.h"
-
-/**
- * SECTION:spice-uri
- * @short_description: URIs handling
- * @title: SpiceURI
- * @section_id:
- * @stability: Stable
- * @include: spice-uri.h
- *
- * A SpiceURI represents a (parsed) URI.
- * Since: 0.24
- */
-
-struct _SpiceURI {
-    GObject parent_instance;
-    gchar *scheme;
-    gchar *hostname;
-    guint port;
-    gchar *user;
-    gchar *password;
-};
-
-struct _SpiceURIClass {
-    GObjectClass parent_class;
-};
-
-G_DEFINE_TYPE(SpiceURI, spice_uri, G_TYPE_OBJECT);
-
-enum  {
-    SPICE_URI_DUMMY_PROPERTY,
-    SPICE_URI_SCHEME,
-    SPICE_URI_USER,
-    SPICE_URI_PASSWORD,
-    SPICE_URI_HOSTNAME,
-    SPICE_URI_PORT
-};
-
-G_GNUC_INTERNAL
-SpiceURI* spice_uri_new(void)
-{
-    SpiceURI * self = NULL;
-    self = (SpiceURI*)g_object_new(SPICE_TYPE_URI, NULL);
-    return self;
-}
-
-G_GNUC_INTERNAL
-gboolean spice_uri_parse(SpiceURI *self, const gchar *_uri, GError **error)
-{
-    gchar *dup, *uri;
-    gboolean success = FALSE;
-    size_t len;
-
-    g_return_val_if_fail(self != NULL, FALSE);
-    g_return_val_if_fail(_uri != NULL, FALSE);
-
-    uri = dup = g_strdup(_uri);
-    /* FIXME: use GUri when it is ready... only support http atm */
-    /* the code is voluntarily not parsing thoroughly the uri */
-    if (g_ascii_strncasecmp("http://", uri, 7) == 0) {
-        uri += 7;
-        spice_uri_set_scheme(self, "http");
-        spice_uri_set_port(self, 3128);
-    } else if (g_ascii_strncasecmp("https://", uri, 8) == 0) {
-        uri += 8;
-        spice_uri_set_scheme(self, "https");
-        spice_uri_set_port(self, 3129);
-    } else {
-        spice_uri_set_scheme(self, "http");
-        spice_uri_set_port(self, 3128);
-    }
-    /* remove trailing slash */
-    len = strlen(uri);
-    for (; len > 0; len--)
-        if (uri[len-1] == '/')
-            uri[len-1] = '\0';
-        else
-            break;
-
-
-    /* yes, that parser is bad, we need GUri... */
-    if (strstr(uri, "@")) {
-        gchar *saveptr = NULL, *saveptr2 = NULL;
-        gchar *next = strstr(uri, "@") + 1;
-        gchar *auth = strtok_r(uri, "@", &saveptr);
-        const gchar *user = strtok_r(auth, ":", &saveptr2);
-        const gchar *pass = strtok_r(NULL, ":", &saveptr2);
-        spice_uri_set_user(self, user);
-        spice_uri_set_password(self, pass);
-        uri = next;
-    }
-
-    /* max 2 parts, host:port */
-    gchar **uriv = g_strsplit(uri, ":", 2);
-    const gchar *uri_port = NULL;
-
-    if (uriv[0] == NULL || strlen(uriv[0]) == 0) {
-        g_set_error(error, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
-                    "Invalid hostname in uri address");
-        goto end;
-    }
-
-    spice_uri_set_hostname(self, uriv[0]);
-    if (uriv[0] != NULL)
-        uri_port = uriv[1];
-
-    if (uri_port != NULL) {
-        char *endptr;
-        guint port = strtoul(uri_port, &endptr, 10);
-        if (*endptr != '\0') {
-            g_set_error(error, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
-                        "Invalid uri port: %s", uri_port);
-            goto end;
-        }
-        spice_uri_set_port(self, port);
-    }
-
-    success = TRUE;
-
-end:
-    g_free(dup);
-    g_strfreev(uriv);
-    return success;
-}
-
-/**
- * spice_uri_get_scheme:
- * @uri: a #SpiceURI
- *
- * Gets @uri's scheme.
- *
- * Returns: @uri's scheme.
- * Since: 0.24
- **/
-const gchar* spice_uri_get_scheme(SpiceURI *self)
-{
-    g_return_val_if_fail(SPICE_IS_URI(self), NULL);
-    return self->scheme;
-}
-
-/**
- * spice_uri_set_scheme:
- * @uri: a #SpiceURI
- * @scheme: the scheme
- *
- * Sets @uri's scheme to @scheme.
- * Since: 0.24
- **/
-void spice_uri_set_scheme(SpiceURI *self, const gchar *scheme)
-{
-    g_return_if_fail(SPICE_IS_URI(self));
-
-    g_free(self->scheme);
-    self->scheme = g_strdup(scheme);
-    g_object_notify((GObject *)self, "scheme");
-}
-
-/**
- * spice_uri_get_hostname:
- * @uri: a #SpiceURI
- *
- * Gets @uri's hostname.
- *
- * Returns: @uri's hostname.
- * Since: 0.24
- **/
-const gchar* spice_uri_get_hostname(SpiceURI *self)
-{
-    g_return_val_if_fail(SPICE_IS_URI(self), NULL);
-    return self->hostname;
-}
-
-
-/**
- * spice_uri_set_hostname:
- * @uri: a #SpiceURI
- * @hostname: the hostname
- *
- * Sets @uri's hostname to @hostname.
- * Since: 0.24
- **/
-void spice_uri_set_hostname(SpiceURI *self, const gchar *hostname)
-{
-    g_return_if_fail(SPICE_IS_URI(self));
-
-    g_free(self->hostname);
-    self->hostname = g_strdup(hostname);
-    g_object_notify((GObject *)self, "hostname");
-}
-
-/**
- * spice_uri_get_port:
- * @uri: a #SpiceURI
- *
- * Gets @uri's port.
- *
- * Returns: @uri's port.
- * Since: 0.24
- **/
-guint spice_uri_get_port(SpiceURI *self)
-{
-    g_return_val_if_fail(SPICE_IS_URI(self), 0);
-    return self->port;
-}
-
-/**
- * spice_uri_set_port:
- * @uri: a #SpiceURI
- * @port: the port
- *
- * Sets @uri's port to @port.
- * Since: 0.24
- **/
-void spice_uri_set_port(SpiceURI *self, guint port)
-{
-    g_return_if_fail(SPICE_IS_URI(self));
-    self->port = port;
-    g_object_notify((GObject *)self, "port");
-}
-
-static void spice_uri_get_property(GObject *object, guint property_id,
-                                     GValue *value, GParamSpec *pspec)
-{
-    SpiceURI *self;
-    self = G_TYPE_CHECK_INSTANCE_CAST(object, SPICE_TYPE_URI, SpiceURI);
-
-    switch (property_id) {
-    case SPICE_URI_SCHEME:
-        g_value_set_string(value, spice_uri_get_scheme(self));
-        break;
-    case SPICE_URI_HOSTNAME:
-        g_value_set_string(value, spice_uri_get_hostname(self));
-        break;
-    case SPICE_URI_PORT:
-        g_value_set_uint(value, spice_uri_get_port(self));
-        break;
-    case SPICE_URI_USER:
-        g_value_set_string(value, spice_uri_get_user(self));
-        break;
-    case SPICE_URI_PASSWORD:
-        g_value_set_string(value, spice_uri_get_password(self));
-        break;
-    default:
-        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
-        break;
-    }
-}
-
-
-static void spice_uri_set_property(GObject *object, guint property_id,
-                                     const GValue *value, GParamSpec *pspec)
-{
-    SpiceURI * self;
-    self = G_TYPE_CHECK_INSTANCE_CAST(object, SPICE_TYPE_URI, SpiceURI);
-
-    switch (property_id) {
-    case SPICE_URI_SCHEME:
-        spice_uri_set_scheme(self, g_value_get_string(value));
-        break;
-    case SPICE_URI_HOSTNAME:
-        spice_uri_set_hostname(self, g_value_get_string(value));
-        break;
-    case SPICE_URI_USER:
-        spice_uri_set_user(self, g_value_get_string(value));
-        break;
-    case SPICE_URI_PASSWORD:
-        spice_uri_set_password(self, g_value_get_string(value));
-        break;
-    case SPICE_URI_PORT:
-        spice_uri_set_port(self, g_value_get_uint(value));
-        break;
-    default:
-        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
-        break;
-    }
-}
-
-static void spice_uri_finalize(GObject* obj)
-{
-    SpiceURI *self;
-
-    self = G_TYPE_CHECK_INSTANCE_CAST(obj, SPICE_TYPE_URI, SpiceURI);
-    g_free(self->scheme);
-    g_free(self->hostname);
-    g_free(self->user);
-    g_free(self->password);
-
-    G_OBJECT_CLASS (spice_uri_parent_class)->finalize (obj);
-}
-
-static void spice_uri_init (SpiceURI *self)
-{
-}
-
-
-static void spice_uri_class_init(SpiceURIClass *klass)
-{
-    spice_uri_parent_class = g_type_class_peek_parent (klass);
-
-    G_OBJECT_CLASS (klass)->get_property = spice_uri_get_property;
-    G_OBJECT_CLASS (klass)->set_property = spice_uri_set_property;
-    G_OBJECT_CLASS (klass)->finalize = spice_uri_finalize;
-
-    g_object_class_install_property(G_OBJECT_CLASS (klass),
-                                    SPICE_URI_SCHEME,
-                                    g_param_spec_string ("scheme",
-                                                         "scheme",
-                                                         "scheme",
-                                                         NULL,
-                                                         G_PARAM_STATIC_STRINGS |
-                                                         G_PARAM_READWRITE));
-
-    g_object_class_install_property(G_OBJECT_CLASS (klass),
-                                    SPICE_URI_HOSTNAME,
-                                    g_param_spec_string ("hostname",
-                                                         "hostname",
-                                                         "hostname",
-                                                         NULL,
-                                                         G_PARAM_STATIC_STRINGS |
-                                                         G_PARAM_READWRITE));
-
-    g_object_class_install_property(G_OBJECT_CLASS (klass),
-                                    SPICE_URI_PORT,
-                                    g_param_spec_uint ("port",
-                                                       "port",
-                                                       "port",
-                                                       0, G_MAXUINT, 0,
-                                                       G_PARAM_STATIC_STRINGS |
-                                                       G_PARAM_READWRITE));
-
-    g_object_class_install_property(G_OBJECT_CLASS (klass),
-                                    SPICE_URI_USER,
-                                    g_param_spec_string ("user",
-                                                         "user",
-                                                         "user",
-                                                         NULL,
-                                                         G_PARAM_STATIC_STRINGS |
-                                                         G_PARAM_READWRITE));
-
-    g_object_class_install_property(G_OBJECT_CLASS (klass),
-                                    SPICE_URI_PASSWORD,
-                                    g_param_spec_string ("password",
-                                                         "password",
-                                                         "password",
-                                                         NULL,
-                                                         G_PARAM_STATIC_STRINGS |
-                                                         G_PARAM_READWRITE));
-}
-
-/**
- * spice_uri_to_string:
- * @uri: a #SpiceURI
- *
- * Returns a string representing @uri.
- *
- * Returns: a string representing @uri, which the caller must free.
- * Since: 0.24
- **/
-gchar* spice_uri_to_string(SpiceURI* self)
-{
-    g_return_val_if_fail(SPICE_IS_URI(self), NULL);
-
-    if (self->scheme == NULL || self->hostname == NULL)
-        return NULL;
-
-    if (self->user || self->password)
-        return g_strdup_printf("%s://%s:%s@%s:%u",
-                               self->scheme,
-                               self->user, self->password,
-                               self->hostname, self->port);
-    else
-        return g_strdup_printf("%s://%s:%u",
-                               self->scheme, self->hostname, self->port);
-}
-
-/**
- * spice_uri_get_user:
- * @uri: a #SpiceURI
- *
- * Gets @uri's user.
- *
- * Returns: @uri's user.
- * Since: 0.24
- **/
-const gchar* spice_uri_get_user(SpiceURI *self)
-{
-    g_return_val_if_fail(SPICE_IS_URI(self), NULL);
-    return self->user;
-}
-
-/**
- * spice_uri_set_user:
- * @uri: a #SpiceURI
- * @user: the user, or %NULL.
- *
- * Sets @uri's user to @user.
- * Since: 0.24
- **/
-void spice_uri_set_user(SpiceURI *self, const gchar *user)
-{
-    g_return_if_fail(SPICE_IS_URI(self));
-
-    g_free(self->user);
-    self->user = g_strdup(user);
-    g_object_notify((GObject *)self, "user");
-}
-
-/**
- * spice_uri_get_password:
- * @uri: a #SpiceURI
- *
- * Gets @uri's password.
- *
- * Returns: @uri's password.
- * Since: 0.24
- **/
-const gchar* spice_uri_get_password(SpiceURI *self)
-{
-    g_return_val_if_fail(SPICE_IS_URI(self), NULL);
-    return self->password;
-}
-
-/**
- * spice_uri_set_password:
- * @uri: a #SpiceURI
- * @password: the password, or %NULL.
- *
- * Sets @uri's password to @password.
- * Since: 0.24
- **/
-void spice_uri_set_password(SpiceURI *self, const gchar *password)
-{
-    g_return_if_fail(SPICE_IS_URI(self));
-
-    g_free(self->password);
-    self->password = g_strdup(password);
-    g_object_notify((GObject *)self, "password");
-}
diff --git a/gtk/spice-uri.h b/gtk/spice-uri.h
deleted file mode 100644
index 9e8d590..0000000
--- a/gtk/spice-uri.h
+++ /dev/null
@@ -1,52 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2012 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_URI_H__
-#define __SPICE_URI_H__
-
-#include <glib-object.h>
-
-G_BEGIN_DECLS
-
-#define SPICE_TYPE_URI (spice_uri_get_type ())
-#define SPICE_URI(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SPICE_TYPE_URI, SpiceURI))
-#define SPICE_URI_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SPICE_TYPE_URI, SpiceURIClass))
-#define SPICE_IS_URI(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SPICE_TYPE_URI))
-#define SPICE_IS_URI_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SPICE_TYPE_URI))
-#define SPICE_URI_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SPICE_TYPE_URI, SpiceURIClass))
-
-typedef struct _SpiceURI SpiceURI;
-typedef struct _SpiceURIClass SpiceURIClass;
-typedef struct _SpiceURIPrivate SpiceURIPrivate;
-
-GType spice_uri_get_type(void) G_GNUC_CONST;
-
-const gchar* spice_uri_get_scheme(SpiceURI* uri);
-void spice_uri_set_scheme(SpiceURI* uri, const gchar* scheme);
-const gchar* spice_uri_get_hostname(SpiceURI* uri);
-void spice_uri_set_hostname(SpiceURI* uri, const gchar* hostname);
-guint spice_uri_get_port(SpiceURI* uri);
-void spice_uri_set_port(SpiceURI* uri, guint port);
-gchar *spice_uri_to_string(SpiceURI* uri);
-const gchar* spice_uri_get_user(SpiceURI* uri);
-void spice_uri_set_user(SpiceURI* uri, const gchar* user);
-const gchar* spice_uri_get_password(SpiceURI* uri);
-void spice_uri_set_password(SpiceURI* uri, const gchar* password);
-
-G_END_DECLS
-
-#endif /* __SPICE_URI_H__ */
diff --git a/gtk/spice-util-priv.h b/gtk/spice-util-priv.h
deleted file mode 100644
index c0ea8d9..0000000
--- a/gtk/spice-util-priv.h
+++ /dev/null
@@ -1,52 +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_UTIL_PRIV_H
-#define SPICE_UTIL_PRIV_H
-
-#include <glib.h>
-#include "spice-util.h"
-
-G_BEGIN_DECLS
-
-#define UUID_FMT "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x"
-
-gboolean spice_strv_contains(const GStrv strv, const gchar *str);
-const gchar* spice_yes_no(gboolean value);
-guint16 spice_make_scancode(guint scancode, gboolean release);
-gchar* spice_unix2dos(const gchar *str, gssize len, GError **error);
-gchar* spice_dos2unix(const gchar *str, gssize len, GError **error);
-void spice_mono_edge_highlight(unsigned width, unsigned hight,
-                               const guint8 *and, const guint8 *xor, guint8 *dest);
-
-#if GLIB_CHECK_VERSION(2,32,0)
-#define STATIC_MUTEX            GMutex
-#define STATIC_MUTEX_INIT(m)    g_mutex_init(&(m))
-#define STATIC_MUTEX_CLEAR(m)   g_mutex_clear(&(m))
-#define STATIC_MUTEX_LOCK(m)    g_mutex_lock(&(m))
-#define STATIC_MUTEX_UNLOCK(m)  g_mutex_unlock(&(m))
-#else
-#define STATIC_MUTEX            GStaticMutex
-#define STATIC_MUTEX_INIT(m)    g_static_mutex_init(&(m))
-#define STATIC_MUTEX_CLEAR(m)   g_static_mutex_free(&(m))
-#define STATIC_MUTEX_LOCK(m)    g_static_mutex_lock(&(m))
-#define STATIC_MUTEX_UNLOCK(m)  g_static_mutex_unlock(&(m))
-#endif
-
-G_END_DECLS
-
-#endif /* SPICE_UTIL_PRIV_H */
diff --git a/gtk/spice-util.c b/gtk/spice-util.c
deleted file mode 100644
index bec237b..0000000
--- a/gtk/spice-util.c
+++ /dev/null
@@ -1,497 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2010 Red Hat, Inc.
-   Copyright © 2006-2010 Collabora Ltd. <http://www.collabora.co.uk/>
-
-   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 <stdbool.h>
-#include <stdlib.h>
-#include <string.h>
-#include <glib.h>
-#include <glib-object.h>
-#include "spice-util-priv.h"
-#include "spice-util.h"
-#include "spice-util-priv.h"
-
-/**
- * SECTION:spice-util
- * @short_description: version and debugging functions
- * @title: Utilities
- * @section_id:
- * @stability: Stable
- * @include: spice-util.h
- *
- * Various functions for debugging and informational purposes.
- */
-
-static GOnce debug_once = G_ONCE_INIT;
-
-static void spice_util_enable_debug_messages(void)
-{
-#if GLIB_CHECK_VERSION(2, 31, 0)
-    const gchar *doms = g_getenv("G_MESSAGES_DEBUG");
-    if (!doms) {
-        g_setenv("G_MESSAGES_DEBUG", G_LOG_DOMAIN, 1);
-    } else if (g_str_equal(doms, "all")) {
-	return;
-    } else if (!strstr(doms, G_LOG_DOMAIN)) {
-        gchar *newdoms = g_strdup_printf("%s %s", doms, G_LOG_DOMAIN);
-        g_setenv("G_MESSAGES_DEBUG", newdoms, 1);
-        g_free(newdoms);
-    }
-#endif
-}
-
-/**
- * spice_util_set_debug:
- * @enabled: %TRUE or %FALSE
- *
- * Enable or disable Spice-GTK debugging messages.
- **/
-void spice_util_set_debug(gboolean enabled)
-{
-    /* Make sure debug_once has been initialised
-     * with the value of SPICE_DEBUG already, otherwise
-     * spice_util_get_debug() may overwrite the value
-     * that was just set using spice_util_set_debug()
-     */
-    spice_util_get_debug();
-
-    if (enabled) {
-        spice_util_enable_debug_messages();
-    }
-
-    debug_once.retval = GINT_TO_POINTER(enabled);
-}
-
-static gpointer getenv_debug(gpointer data)
-{
-    gboolean debug;
-
-    debug = (g_getenv("SPICE_DEBUG") != NULL);
-    if (debug)
-        spice_util_enable_debug_messages();
-
-    return GINT_TO_POINTER(debug);
-}
-
-gboolean spice_util_get_debug(void)
-{
-    g_once(&debug_once, getenv_debug, NULL);
-
-    return GPOINTER_TO_INT(debug_once.retval);
-}
-
-/**
- * spice_util_get_version_string:
- *
- * Returns: Spice-GTK version as a const string.
- **/
-const gchar *spice_util_get_version_string(void)
-{
-    return VERSION;
-}
-
-G_GNUC_INTERNAL
-gboolean spice_strv_contains(const GStrv strv, const gchar *str)
-{
-    int i;
-
-    if (strv == NULL)
-        return FALSE;
-
-    for (i = 0; strv[i] != NULL; i++)
-        if (g_str_equal(strv[i], str))
-            return TRUE;
-
-    return FALSE;
-}
-
-/**
- * spice_uuid_to_string:
- * @uuid: UUID byte array
- *
- * Creates a string representation of @uuid, of the form
- * "06e023d5-86d8-420e-8103-383e4566087a"
- *
- * Returns: A string that should be freed with g_free().
- * Since: 0.22
- **/
-gchar* spice_uuid_to_string(const guint8 uuid[16])
-{
-    return g_strdup_printf(UUID_FMT, uuid[0], uuid[1],
-                           uuid[2], uuid[3], uuid[4], uuid[5],
-                           uuid[6], uuid[7], uuid[8], uuid[9],
-                           uuid[10], uuid[11], uuid[12], uuid[13],
-                           uuid[14], uuid[15]);
-}
-
-typedef struct {
-    GObject *instance;
-    GObject *observer;
-    GClosure *closure;
-    gulong handler_id;
-} WeakHandlerCtx;
-
-static WeakHandlerCtx *
-whc_new (GObject *instance,
-         GObject *observer)
-{
-    WeakHandlerCtx *ctx = g_slice_new0 (WeakHandlerCtx);
-
-    ctx->instance = instance;
-    ctx->observer = observer;
-
-    return ctx;
-}
-
-static void
-whc_free (WeakHandlerCtx *ctx)
-{
-    g_slice_free (WeakHandlerCtx, ctx);
-}
-
-static void observer_destroyed_cb (gpointer, GObject *);
-static void closure_invalidated_cb (gpointer, GClosure *);
-
-/*
- * If signal handlers are removed before the object is destroyed, this
- * callback will never get triggered.
- */
-static void
-instance_destroyed_cb (gpointer ctx_,
-                       GObject *where_the_instance_was)
-{
-    WeakHandlerCtx *ctx = ctx_;
-
-    /* No need to disconnect the signal here, the instance has gone away. */
-    g_object_weak_unref (ctx->observer, observer_destroyed_cb, ctx);
-    g_closure_remove_invalidate_notifier (ctx->closure, ctx,
-                                          closure_invalidated_cb);
-    whc_free (ctx);
-}
-
-/* Triggered when the observer is destroyed. */
-static void
-observer_destroyed_cb (gpointer ctx_,
-                       GObject *where_the_observer_was)
-{
-    WeakHandlerCtx *ctx = ctx_;
-
-    g_closure_remove_invalidate_notifier (ctx->closure, ctx,
-                                          closure_invalidated_cb);
-    g_signal_handler_disconnect (ctx->instance, ctx->handler_id);
-    g_object_weak_unref (ctx->instance, instance_destroyed_cb, ctx);
-    whc_free (ctx);
-}
-
-/* Triggered when either object is destroyed or the handler is disconnected. */
-static void
-closure_invalidated_cb (gpointer ctx_,
-                        GClosure *where_the_closure_was)
-{
-    WeakHandlerCtx *ctx = ctx_;
-
-    g_object_weak_unref (ctx->instance, instance_destroyed_cb, ctx);
-    g_object_weak_unref (ctx->observer, observer_destroyed_cb, ctx);
-    whc_free (ctx);
-}
-
-/* Copied from tp_g_signal_connect_object. See documentation. */
-/**
-  * spice_g_signal_connect_object: (skip)
-  * @instance: the instance to connect to.
-  * @detailed_signal: a string of the form "signal-name::detail".
-  * @c_handler: the #GCallback to connect.
-  * @gobject: the object to pass as data to @c_handler.
-  * @connect_flags: a combination of #GConnectFlags.
-  *
-  * Similar to g_signal_connect_object() but will delete connection
-  * when any of the objects is destroyed.
-  *
-  * Returns: the handler id.
-  */
-gulong spice_g_signal_connect_object (gpointer instance,
-                                      const gchar *detailed_signal,
-                                      GCallback c_handler,
-                                      gpointer gobject,
-                                      GConnectFlags connect_flags)
-{
-    GObject *instance_obj = G_OBJECT (instance);
-    WeakHandlerCtx *ctx = whc_new (instance_obj, gobject);
-
-    g_return_val_if_fail (G_TYPE_CHECK_INSTANCE (instance), 0);
-    g_return_val_if_fail (detailed_signal != NULL, 0);
-    g_return_val_if_fail (c_handler != NULL, 0);
-    g_return_val_if_fail (G_IS_OBJECT (gobject), 0);
-    g_return_val_if_fail (
-                          (connect_flags & ~(G_CONNECT_AFTER|G_CONNECT_SWAPPED)) == 0, 0);
-
-    if (connect_flags & G_CONNECT_SWAPPED)
-        ctx->closure = g_cclosure_new_object_swap (c_handler, gobject);
-    else
-        ctx->closure = g_cclosure_new_object (c_handler, gobject);
-
-    ctx->handler_id = g_signal_connect_closure (instance, detailed_signal,
-                                                ctx->closure, (connect_flags & G_CONNECT_AFTER) ? TRUE : FALSE);
-
-    g_object_weak_ref (instance_obj, instance_destroyed_cb, ctx);
-    g_object_weak_ref (gobject, observer_destroyed_cb, ctx);
-    g_closure_add_invalidate_notifier (ctx->closure, ctx,
-                                       closure_invalidated_cb);
-
-    return ctx->handler_id;
-}
-
-G_GNUC_INTERNAL
-const gchar* spice_yes_no(gboolean value)
-{
-    return value ? "yes" : "no";
-}
-
-G_GNUC_INTERNAL
-guint16 spice_make_scancode(guint scancode, gboolean release)
-{
-    SPICE_DEBUG("%s: %s scancode %d",
-                __FUNCTION__, release ? "release" : "", scancode);
-
-    if (release) {
-        if (scancode < 0x100)
-            return scancode | 0x80;
-        else
-            return 0x80e0 | ((scancode - 0x100) << 8);
-    } else {
-        if (scancode < 0x100)
-            return scancode;
-        else
-            return 0xe0 | ((scancode - 0x100) << 8);
-    }
-
-    g_return_val_if_reached(0);
-}
-
-typedef enum {
-    NEWLINE_TYPE_LF,
-    NEWLINE_TYPE_CR_LF
-} NewlineType;
-
-static gssize get_line(const gchar *str, gsize len,
-                       NewlineType type, gsize *nl_len,
-                       GError **error)
-{
-    const gchar *p, *endl;
-    gsize nl = 0;
-
-    endl = (type == NEWLINE_TYPE_CR_LF) ? "\r\n" : "\n";
-    p = g_strstr_len(str, len, endl);
-    if (p) {
-        len = p - str;
-        nl = strlen(endl);
-    }
-
-    *nl_len = nl;
-    return len;
-}
-
-
-static gchar* spice_convert_newlines(const gchar *str, gssize len,
-                                     NewlineType from,
-                                     NewlineType to,
-                                     GError **error)
-{
-    GError *err = NULL;
-    gssize length;
-    gsize nl;
-    GString *output;
-    gboolean free_segment = FALSE;
-    gint i;
-
-    g_return_val_if_fail(str != NULL, NULL);
-    g_return_val_if_fail(len >= -1, NULL);
-    g_return_val_if_fail(error == NULL || *error == NULL, NULL);
-    /* only 2 supported combinations */
-    g_return_val_if_fail((from == NEWLINE_TYPE_LF &&
-                          to == NEWLINE_TYPE_CR_LF) ||
-                         (from == NEWLINE_TYPE_CR_LF &&
-                          to == NEWLINE_TYPE_LF), NULL);
-
-    if (len == -1)
-        len = strlen(str);
-    /* sometime we get \0 terminated strings, skip that, or it fails
-       to utf8 validate line with \0 end */
-    else if (len > 0 && str[len-1] == 0)
-        len -= 1;
-
-    /* allocate worst case, if it's small enough, we don't care much,
-     * if it's big, malloc will put us in mmap'd region, and we can
-     * over allocate.
-     */
-    output = g_string_sized_new(len * 2 + 1);
-
-    for (i = 0; i < len; i += length + nl) {
-        length = get_line(str + i, len - i, from, &nl, &err);
-        if (length < 0)
-            break;
-
-        g_string_append_len(output, str + i, length);
-
-        if (nl) {
-            /* let's not double \r if it's already in the line */
-            if (to == NEWLINE_TYPE_CR_LF &&
-                output->str[output->len - 1] != '\r')
-                g_string_append_c(output, '\r');
-
-            g_string_append_c(output, '\n');
-        }
-    }
-
-    if (err) {
-        g_propagate_error(error, err);
-        free_segment = TRUE;
-    }
-
-    return g_string_free(output, free_segment);
-}
-
-G_GNUC_INTERNAL
-gchar* spice_dos2unix(const gchar *str, gssize len, GError **error)
-{
-    return spice_convert_newlines(str, len,
-                                  NEWLINE_TYPE_CR_LF,
-                                  NEWLINE_TYPE_LF,
-                                  error);
-}
-
-G_GNUC_INTERNAL
-gchar* spice_unix2dos(const gchar *str, gssize len, GError **error)
-{
-    return spice_convert_newlines(str, len,
-                                  NEWLINE_TYPE_LF,
-                                  NEWLINE_TYPE_CR_LF,
-                                  error);
-}
-
-static bool buf_is_ones(unsigned size, const guint8 *data)
-{
-    int i;
-
-    for (i = 0 ; i < size; ++i) {
-        if (data[i] != 0xff) {
-            return false;
-        }
-    }
-    return true;
-}
-
-static bool is_edge_helper(const guint8 *xor, int bpl, int x, int y)
-{
-    return (xor[bpl * y + (x / 8)] & (0x80 >> (x % 8))) > 0;
-}
-
-static bool is_edge(unsigned width, unsigned height, const guint8 *xor, int bpl, int x, int y)
-{
-    if (x == 0 || x == width -1 || y == 0 || y == height - 1) {
-        return 0;
-    }
-#define P(x, y) is_edge_helper(xor, bpl, x, y)
-    return !P(x, y) && (P(x - 1, y + 1) || P(x, y + 1) || P(x + 1, y + 1) ||
-                        P(x - 1, y)     ||                P(x + 1, y)     ||
-                        P(x - 1, y - 1) || P(x, y - 1) || P(x + 1, y - 1));
-#undef P
-}
-
-/* Mono cursors have two places, "and" and "xor". If a bit is 1 in both, it
- * means invertion of the corresponding pixel in the display. Since X11 (and
- * gdk) doesn't do invertion, instead we do edge detection and turn the
- * sorrounding edge pixels black, and the invert-me pixels white. To
- * illustrate:
- *
- *  and   xor      dest RGB (1=0xffffff, 0=0x000000)
- *
- *                        dest alpha (1=0xff, 0=0x00)
- *
- * 11111 00000     00000  00000
- * 11111 00000     00000  01110
- * 11111 00100 =>  00100  01110
- * 11111 00100     00100  01110
- * 11111 00000     00000  01110
- * 11111 00000     00000  00000
- *
- * See tests/util.c for more tests
- *
- * Notes:
- *  Assumes width >= 8 (i.e. bytes per line is at least 1)
- *  Assumes edges are not on the boundary (first/last line/column) for simplicity
- *
- */
-G_GNUC_INTERNAL
-void spice_mono_edge_highlight(unsigned width, unsigned height,
-                               const guint8 *and, const guint8 *xor, guint8 *dest)
-{
-    int bpl = (width + 7) / 8;
-    bool and_ones = buf_is_ones(height * bpl, and);
-    int x, y, bit;
-    const guint8 *xor_base = xor;
-
-    for (y = 0; y < height; y++) {
-        bit = 0x80;
-        for (x = 0; x < width; x++, dest += 4) {
-            if (is_edge(width, height, xor_base, bpl, x, y) && and_ones) {
-                dest[0] = 0x00;
-                dest[1] = 0x00;
-                dest[2] = 0x00;
-                dest[3] = 0xff;
-                goto next_bit;
-            }
-            if (and[x/8] & bit) {
-                if (xor[x/8] & bit) {
-                    dest[0] = 0xff;
-                    dest[1] = 0xff;
-                    dest[2] = 0xff;
-                    dest[3] = 0xff;
-                } else {
-                    /* unchanged -> transparent */
-                    dest[0] = 0x00;
-                    dest[1] = 0x00;
-                    dest[2] = 0x00;
-                    dest[3] = 0x00;
-                }
-            } else {
-                if (xor[x/8] & bit) {
-                    /* set -> white */
-                    dest[0] = 0xff;
-                    dest[1] = 0xff;
-                    dest[2] = 0xff;
-                    dest[3] = 0xff;
-                } else {
-                    /* clear -> black */
-                    dest[0] = 0x00;
-                    dest[1] = 0x00;
-                    dest[2] = 0x00;
-                    dest[3] = 0xff;
-                }
-            }
-        next_bit:
-            bit >>= 1;
-            if (bit == 0) {
-                bit = 0x80;
-            }
-        }
-        and += bpl;
-        xor += bpl;
-    }
-}
diff --git a/gtk/spice-util.h b/gtk/spice-util.h
deleted file mode 100644
index 3f429a0..0000000
--- a/gtk/spice-util.h
+++ /dev/null
@@ -1,63 +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_UTIL_H
-#define SPICE_UTIL_H
-
-#include <glib-object.h>
-
-G_BEGIN_DECLS
-
-void spice_util_set_debug(gboolean enabled);
-gboolean spice_util_get_debug(void);
-const gchar *spice_util_get_version_string(void);
-gulong spice_g_signal_connect_object(gpointer instance,
-                                     const gchar *detailed_signal,
-                                     GCallback c_handler,
-                                     gpointer gobject,
-                                     GConnectFlags connect_flags);
-gchar* spice_uuid_to_string(const guint8 uuid[16]);
-
-#define SPICE_DEBUG(fmt, ...)                                   \
-    do {                                                        \
-        if (G_UNLIKELY(spice_util_get_debug()))                 \
-            g_debug(G_STRLOC " " fmt, ## __VA_ARGS__);          \
-    } while (0)
-
-#define SPICE_RESERVED_PADDING (10 * sizeof(void*))
-
-/* need to be in a public header, glib-compat.h is private */
-#ifndef SPICE_GNUC_DEPRECATED_FOR
-#if    __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)
-#define SPICE_GNUC_DEPRECATED_FOR(f)                        \
-  __attribute__((deprecated("Use " #f " instead")))
-#else
-#define SPICE_GNUC_DEPRECATED_FOR(f)        G_GNUC_DEPRECATED
-#endif /* __GNUC__ */
-#endif
-
-#ifndef SPICE_NO_DEPRECATED
-#define SPICE_DEPRECATED_FOR(f)  SPICE_GNUC_DEPRECATED_FOR(f)
-#define SPICE_DEPRECATED  G_GNUC_DEPRECATED
-#else
-#define SPICE_DEPRECATED_FOR(f)
-#define SPICE_DEPRECATED
-#endif
-
-G_END_DECLS
-
-#endif /* SPICE_UTIL_H */
diff --git a/gtk/spice-version.h.in b/gtk/spice-version.h.in
deleted file mode 100644
index 4276a23..0000000
--- a/gtk/spice-version.h.in
+++ /dev/null
@@ -1,70 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-  Copyright (C) 2014 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_VERSION_H__
-#define __SPICE_VERSION_H__
-
-/**
- * SECTION:spice-version
- * @short_description: Spice-Gtk version checking
- *
- * Spice-Gtk provides macros to check the version of the library
- * at compile-time
- */
-
-/**
- * SPICE_GTK_MAJOR_VERSION:
- *
- * Spice-Gtk major version component (e.g. 1 if version is 1.2.3)
- * Since: 0.24
- */
-#define SPICE_GTK_MAJOR_VERSION              (@SPICE_GTK_MAJOR_VERSION@)
-
-/**
- * SPICE_GTK_MINOR_VERSION:
- *
- * Spice-Gtk minor version component (e.g. 2 if version is 1.2.3)
- * Since: 0.24
- */
-#define SPICE_GTK_MINOR_VERSION              (@SPICE_GTK_MINOR_VERSION@)
-
-/**
- * SPICE_GTK_MICRO_VERSION:
- *
- * Spice-Gtk micro version component (e.g. 3 if version is 1.2.3)
- * Since: 0.24
- */
-#define SPICE_GTK_MICRO_VERSION              (@SPICE_GTK_MICRO_VERSION@)
-
-/**
- * SPICE_GTK_CHECK_VERSION:
- * @major: required major version
- * @minor: required minor version
- * @micro: required micro version
- *
- * Compile-time version checking. Evaluates to %TRUE if the version
- * of Spice-Gtk is greater than the required one.
- * Since: 0.24
- */
-#define SPICE_GTK_CHECK_VERSION(major, minor, micro)                    \
-        (SPICE_GTK_MAJOR_VERSION > (major) ||                           \
-         (SPICE_GTK_MAJOR_VERSION == (major) && SPICE_GTK_MINOR_VERSION > (minor)) || \
-         (SPICE_GTK_MAJOR_VERSION == (major) && SPICE_GTK_MINOR_VERSION == (minor) && \
-          SPICE_GTK_MICRO_VERSION >= (micro)))
-
-
-#endif /* __SPICE_VERSION_H__ */
diff --git a/gtk/spice-widget-cairo.c b/gtk/spice-widget-cairo.c
deleted file mode 100644
index 96af076..0000000
--- a/gtk/spice-widget-cairo.c
+++ /dev/null
@@ -1,160 +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 "gtk-compat.h"
-#include "spice-widget.h"
-#include "spice-widget-priv.h"
-#include "spice-gtk-session-priv.h"
-
-
-G_GNUC_INTERNAL
-int spicex_image_create(SpiceDisplay *display)
-{
-    SpiceDisplayPrivate *d = display->priv;
-
-    if (d->ximage != NULL)
-        return 0;
-
-    if (d->format == SPICE_SURFACE_FMT_16_555 ||
-        d->format == SPICE_SURFACE_FMT_16_565) {
-        d->convert = TRUE;
-        d->data = g_malloc0(d->area.width * d->area.height * 4);
-
-        d->ximage = cairo_image_surface_create_for_data
-            (d->data, CAIRO_FORMAT_RGB24, d->area.width, d->area.height, d->area.width * 4);
-
-    } else {
-        d->convert = FALSE;
-
-        d->ximage = cairo_image_surface_create_for_data
-            (d->data, CAIRO_FORMAT_RGB24, d->width, d->height, d->stride);
-    }
-
-    return 0;
-}
-
-G_GNUC_INTERNAL
-void spicex_image_destroy(SpiceDisplay *display)
-{
-    SpiceDisplayPrivate *d = display->priv;
-
-    if (d->ximage) {
-        cairo_surface_destroy(d->ximage);
-        d->ximage = NULL;
-    }
-    if (d->convert && d->data) {
-        g_free(d->data);
-        d->data = NULL;
-    }
-    d->convert = FALSE;
-}
-
-G_GNUC_INTERNAL
-void spicex_draw_event(SpiceDisplay *display, cairo_t *cr)
-{
-    SpiceDisplayPrivate *d = display->priv;
-    cairo_rectangle_int_t rect;
-    cairo_region_t *region;
-    double s;
-    int x, y;
-    int ww, wh;
-    int w, h;
-
-    spice_display_get_scaling(display, &s, &x, &y, &w, &h);
-
-    gdk_drawable_get_size(gtk_widget_get_window(GTK_WIDGET(display)), &ww, &wh);
-
-    /* We need to paint the bg color around the image */
-    rect.x = 0;
-    rect.y = 0;
-    rect.width = ww;
-    rect.height = wh;
-    region = cairo_region_create_rectangle(&rect);
-
-    /* Optionally cut out the inner area where the pixmap
-       will be drawn. This avoids 'flashing' since we're
-       not double-buffering. */
-    if (d->ximage) {
-        rect.x = x;
-        rect.y = y;
-        rect.width = w;
-        rect.height = h;
-        cairo_region_subtract_rectangle(region, &rect);
-    }
-
-    gdk_cairo_region (cr, region);
-    cairo_region_destroy (region);
-
-    /* Need to set a real solid color, because the default is usually
-       transparent these days, and non-double buffered windows can't
-       render transparently */
-    cairo_set_source_rgb (cr, 0, 0, 0);
-    cairo_fill(cr);
-
-    /* Draw the display */
-    if (d->ximage) {
-        cairo_translate(cr, x, y);
-        cairo_rectangle(cr, 0, 0, w, h);
-        cairo_scale(cr, s, s);
-        if (!d->convert)
-            cairo_translate(cr, -d->area.x, -d->area.y);
-        cairo_set_source_surface(cr, d->ximage, 0, 0);
-        cairo_fill(cr);
-
-        if (d->mouse_mode == SPICE_MOUSE_MODE_SERVER &&
-            d->mouse_guest_x != -1 && d->mouse_guest_y != -1 &&
-            !d->show_cursor &&
-            spice_gtk_session_get_pointer_grabbed(d->gtk_session)) {
-            GdkPixbuf *image = d->mouse_pixbuf;
-            if (image != NULL) {
-                gdk_cairo_set_source_pixbuf(cr, image,
-                                            d->mouse_guest_x - d->mouse_hotspot.x,
-                                            d->mouse_guest_y - d->mouse_hotspot.y);
-                cairo_paint(cr);
-            }
-        }
-    }
-}
-
-#if ! GTK_CHECK_VERSION (2, 91, 0)
-G_GNUC_INTERNAL
-void spicex_expose_event(SpiceDisplay *display, GdkEventExpose *expose)
-{
-    cairo_t *cr;
-
-    cr = gdk_cairo_create(gtk_widget_get_window(GTK_WIDGET(display)));
-    cairo_rectangle(cr,
-                    expose->area.x,
-                    expose->area.y,
-                    expose->area.width,
-                    expose->area.height);
-    cairo_clip(cr);
-
-    spicex_draw_event(display, cr);
-
-    cairo_destroy(cr);
-}
-#endif
-
-G_GNUC_INTERNAL
-gboolean spicex_is_scaled(SpiceDisplay *display)
-{
-    SpiceDisplayPrivate *d = display->priv;
-    return d->allow_scaling;
-}
diff --git a/gtk/spice-widget-priv.h b/gtk/spice-widget-priv.h
deleted file mode 100644
index 0e1f661..0000000
--- a/gtk/spice-widget-priv.h
+++ /dev/null
@@ -1,141 +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_WIDGET_PRIV_H__
-#define __SPICE_WIDGET_PRIV_H__
-
-G_BEGIN_DECLS
-
-#include "config.h"
-
-#ifdef WITH_X11
-#include <X11/Xlib.h>
-#include <X11/extensions/XShm.h>
-#include <gdk/gdkx.h>
-#endif
-
-#ifdef WIN32
-#include <windows.h>
-#endif
-
-#include "spice-widget.h"
-#include "spice-common.h"
-#include "spice-gtk-session.h"
-
-#define SPICE_DISPLAY_GET_PRIVATE(obj)                                  \
-    (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_DISPLAY, SpiceDisplayPrivate))
-
-struct _SpiceDisplayPrivate {
-    gint                    channel_id;
-    gint                    monitor_id;
-
-    /* options */
-    bool                    keyboard_grab_enable;
-    gboolean                keyboard_grab_inhibit;
-    bool                    mouse_grab_enable;
-    bool                    resize_guest_enable;
-
-    /* state */
-    gboolean                ready;
-    gboolean                monitor_ready;
-    enum SpiceSurfaceFmt    format;
-    gint                    width, height, stride;
-    gint                    shmid;
-    gpointer                data_origin; /* the original display image data */
-    gpointer                data; /* converted if necessary to 32 bits */
-
-    GdkRectangle            area;
-    /* window border */
-    gint                    ww, wh, mx, my;
-
-    bool                    convert;
-    bool                    have_mitshm;
-    gboolean                allow_scaling;
-    gboolean                only_downscale;
-    gboolean                disable_inputs;
-
-    /* TODO: make a display object instead? */
-#ifdef WITH_X11
-    Display                 *dpy;
-    XVisualInfo             *vi;
-    XImage                  *ximage;
-    XShmSegmentInfo         *shminfo;
-    GC                      gc;
-#else
-    cairo_surface_t         *ximage;
-#endif
-
-    SpiceSession            *session;
-    SpiceGtkSession         *gtk_session;
-    SpiceMainChannel        *main;
-    SpiceChannel            *display;
-    SpiceCursorChannel      *cursor;
-    SpiceInputsChannel      *inputs;
-    SpiceSmartcardChannel   *smartcard;
-
-    enum SpiceMouseMode     mouse_mode;
-    int                     mouse_grab_active;
-    bool                    mouse_have_pointer;
-    GdkCursor               *mouse_cursor;
-    GdkPixbuf               *mouse_pixbuf;
-    GdkPoint                mouse_hotspot;
-    GdkCursor               *show_cursor;
-    int                     mouse_last_x;
-    int                     mouse_last_y;
-    int                     mouse_guest_x;
-    int                     mouse_guest_y;
-
-    bool                    keyboard_grab_active;
-    bool                    keyboard_have_focus;
-
-    const guint16          *keycode_map;
-    size_t                  keycode_maplen;
-    uint32_t                key_state[512 / 32];
-    int                     key_delayed_scancode;
-    guint                   key_delayed_id;
-    SpiceGrabSequence         *grabseq; /* the configured key sequence */
-    gboolean                *activeseq; /* the currently pressed keys */
-    gboolean                seq_pressed;
-    gboolean                keyboard_grab_released;
-    gint                    mark;
-#ifdef WIN32
-    HHOOK                   keyboard_hook;
-    int                     win_mouse[3];
-    int                     win_mouse_speed;
-#endif
-    guint                   keypress_delay;
-    gint                    zoom_level;
-#ifdef GDK_WINDOWING_X11
-    int                     x11_accel_numerator;
-    int                     x11_accel_denominator;
-    int                     x11_threshold;
-#endif
-};
-
-int      spicex_image_create                 (SpiceDisplay *display);
-void     spicex_image_destroy                (SpiceDisplay *display);
-#if GTK_CHECK_VERSION (2, 91, 0)
-void     spicex_draw_event                   (SpiceDisplay *display, cairo_t *cr);
-#else
-void     spicex_expose_event                 (SpiceDisplay *display, GdkEventExpose *ev);
-#endif
-gboolean spicex_is_scaled                    (SpiceDisplay *display);
-void     spice_display_get_scaling           (SpiceDisplay *display, double *s, int *x, int *y, int *w, int *h);
-
-G_END_DECLS
-
-#endif
diff --git a/gtk/spice-widget-x11.c b/gtk/spice-widget-x11.c
deleted file mode 100644
index 3f2ce94..0000000
--- a/gtk/spice-widget-x11.c
+++ /dev/null
@@ -1,280 +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-widget.h"
-#include "spice-widget-priv.h"
-
-#ifdef HAVE_SYS_SHM_H
-#include <sys/shm.h>
-#endif
-
-#ifdef HAVE_SYS_IPC_H
-#include <sys/ipc.h>
-#endif
-
-static bool no_mitshm;
-
-static struct format_table {
-    enum SpiceSurfaceFmt  spice;
-    XVisualInfo           xvisual;
-} format_table[] = {
-    {
-        .spice = SPICE_SURFACE_FMT_32_ARGB, /* FIXME: is that correct xvisual? */
-        .xvisual = {
-            .depth      = 24,
-            .red_mask   = 0xff0000,
-            .green_mask = 0x00ff00,
-            .blue_mask  = 0x0000ff,
-        },
-    },{
-        .spice = SPICE_SURFACE_FMT_32_xRGB,
-        .xvisual = {
-            .depth      = 24,
-            .red_mask   = 0xff0000,
-            .green_mask = 0x00ff00,
-            .blue_mask  = 0x0000ff,
-        },
-    },{
-        .spice = SPICE_SURFACE_FMT_16_555,
-        .xvisual = {
-            .depth      = 16,
-            .red_mask   = 0x7c00,
-            .green_mask = 0x03e0,
-            .blue_mask  = 0x001f,
-        },
-    },{
-        .spice = SPICE_SURFACE_FMT_16_565,
-        .xvisual = {
-            .depth      = 16,
-            .red_mask   = 0xf800,
-            .green_mask = 0x07e0,
-            .blue_mask  = 0x001f,
-        },
-    }
-};
-
-static XVisualInfo *get_visual_for_format(GtkWidget *widget, enum SpiceSurfaceFmt format)
-{
-    GdkDrawable  *drawable = gtk_widget_get_window(widget);
-    GdkDisplay   *display = gdk_drawable_get_display(drawable);
-    GdkScreen    *screen = gdk_drawable_get_screen(drawable);
-    XVisualInfo  template;
-    int          found, i;
-    XVisualInfo *vi;
-
-    for (i = 0; i < SPICE_N_ELEMENTS(format_table); i++) {
-        if (format == format_table[i].spice)
-            break;
-    }
-    if (i == SPICE_N_ELEMENTS(format_table)) {
-        g_warn_if_reached();
-        return NULL;
-    }
-
-    template = format_table[i].xvisual;
-    template.screen = gdk_x11_screen_get_screen_number(screen);
-    vi = XGetVisualInfo(gdk_x11_display_get_xdisplay(display),
-                        VisualScreenMask | VisualDepthMask |
-                        VisualRedMaskMask | VisualGreenMaskMask | VisualBlueMaskMask,
-                        &template, &found);
-    return vi;
-}
-
-static XVisualInfo *get_visual_default(GtkWidget *widget)
-{
-    GdkDrawable  *drawable = gtk_widget_get_window(widget);
-    GdkDisplay   *display = gdk_drawable_get_display(drawable);
-    GdkScreen    *screen = gdk_drawable_get_screen(drawable);
-    XVisualInfo  template;
-    int          found;
-
-    template.screen = gdk_x11_screen_get_screen_number(screen);
-    return XGetVisualInfo(gdk_x11_display_get_xdisplay(display),
-                          VisualScreenMask,
-                          &template, &found);
-}
-
-static int catch_no_mitshm(Display * dpy, XErrorEvent * event)
-{
-    no_mitshm = true;
-    return 0;
-}
-
-G_GNUC_INTERNAL
-int spicex_image_create(SpiceDisplay *display)
-{
-    SpiceDisplayPrivate   *d = display->priv;
-
-    if (d->ximage != NULL)
-        return 0;
-
-    GdkDrawable     *window = gtk_widget_get_window(GTK_WIDGET(display));
-    GdkDisplay      *gtkdpy = gdk_drawable_get_display(window);
-    void            *old_handler = NULL;
-    XGCValues       gcval = {
-        .foreground = 0,
-        .background = 0,
-    };
-
-    d->dpy = gdk_x11_display_get_xdisplay(gtkdpy);
-    d->convert = false;
-    d->vi = get_visual_for_format(GTK_WIDGET(display), d->format);
-    if (d->vi == NULL) {
-        d->convert = true;
-        d->vi = get_visual_default(GTK_WIDGET(display));
-        d->vi = get_visual_for_format(GTK_WIDGET(display), SPICE_SURFACE_FMT_32_xRGB);
-        g_return_val_if_fail(d->vi != NULL, 1);
-    }
-    if (d->convert) {
-        d->data = g_malloc0(d->height * d->stride); /* pixels are 32 bits */
-    }
-
-    d->gc = XCreateGC(d->dpy, gdk_x11_drawable_get_xid(window),
-                      GCForeground | GCBackground, &gcval);
-
-    if (d->convert) /* do not use shm when doing color format conversion */
-        goto xcreate;
-
-    if (d->have_mitshm && d->shmid != -1) {
-        if (!XShmQueryExtension(d->dpy)) {
-            goto shm_fail;
-        }
-        no_mitshm = false;
-        old_handler = XSetErrorHandler(catch_no_mitshm);
-        d->shminfo = g_new0(XShmSegmentInfo, 1);
-        d->ximage = XShmCreateImage(d->dpy, d->vi->visual, d->vi->depth,
-                                    ZPixmap, d->data, d->shminfo, d->width, d->height);
-        if (d->ximage == NULL)
-            goto shm_fail;
-        d->shminfo->shmaddr = d->data;
-        d->shminfo->shmid = d->shmid;
-        d->shminfo->readOnly = false;
-        XShmAttach(d->dpy, d->shminfo);
-        XSync(d->dpy, False);
-        shmctl(d->shmid, IPC_RMID, 0);
-        if (no_mitshm)
-            goto shm_fail;
-        XSetErrorHandler(old_handler);
-        return 0;
-    }
-
- shm_fail:
-    d->have_mitshm = false;
-    g_free(d->shminfo);
-    d->shminfo = NULL;
-    if (old_handler)
-        XSetErrorHandler(old_handler);
- xcreate:
-    d->ximage = XCreateImage(d->dpy, d->vi->visual, d->vi->depth, ZPixmap, 0,
-                             d->data, d->width, d->height, 32, d->stride);
-    return 0;
-}
-
-G_GNUC_INTERNAL
-void spicex_image_destroy(SpiceDisplay *display)
-{
-    SpiceDisplayPrivate *d = display->priv;
-
-    if (d->ximage) {
-        /* avoid XDestroy to free shared memory, owned and freed by
-           channel-display itself */
-        if (d->ximage->data == d->data_origin)
-            d->ximage->data = NULL;
-        XDestroyImage(d->ximage);
-        d->ximage = NULL;
-        if (d->convert)
-            d->data = 0;
-    }
-    if (d->shminfo) {
-        XShmDetach(d->dpy, d->shminfo);
-        free(d->shminfo);
-        d->shminfo = NULL;
-    }
-    if (d->gc) {
-        XFreeGC(d->dpy, d->gc);
-        d->gc = NULL;
-    }
-    if (d->convert && d->data) {
-        g_free(d->data);
-        d->data = NULL;
-    }
-}
-
-G_GNUC_INTERNAL
-void spicex_expose_event(SpiceDisplay *display, GdkEventExpose *expose)
-{
-    GdkDrawable *window = gtk_widget_get_window(GTK_WIDGET(display));
-    SpiceDisplayPrivate *d = display->priv;
-    int x, y, w, h;
-
-    spice_display_get_scaling(display, NULL, &x, &y, &w, &h);
-
-    if (expose->area.x >= x &&
-        expose->area.y >= y &&
-        expose->area.x + expose->area.width  <= x + w &&
-        expose->area.y + expose->area.height <= y + h) {
-        /* area is completely inside the guest screen -- blit it */
-        if (d->have_mitshm && d->shminfo) {
-            XShmPutImage(d->dpy, gdk_x11_drawable_get_xid(window),
-                         d->gc, d->ximage,
-                         d->area.x + expose->area.x - x, d->area.y + expose->area.y - y,
-                         expose->area.x, expose->area.y,
-                         expose->area.width, expose->area.height,
-                         true);
-        } else {
-            XPutImage(d->dpy, gdk_x11_drawable_get_xid(window),
-                      d->gc, d->ximage,
-                      d->area.x + expose->area.x - x, d->area.y + expose->area.y - y,
-                      expose->area.x, expose->area.y,
-                      expose->area.width, expose->area.height);
-        }
-    } else {
-        /* complete window update */
-        if (d->ww > d->area.width || d->wh > d->area.height) {
-            int x1 = x;
-            int x2 = x + w;
-            int y1 = y;
-            int y2 = y + h;
-            XFillRectangle(d->dpy, gdk_x11_drawable_get_xid(window),
-                           d->gc, 0, 0, x1, d->wh);
-            XFillRectangle(d->dpy, gdk_x11_drawable_get_xid(window),
-                           d->gc, x2, 0, d->ww - x2, d->wh);
-            XFillRectangle(d->dpy, gdk_x11_drawable_get_xid(window),
-                           d->gc, 0, 0, d->ww, y1);
-            XFillRectangle(d->dpy, gdk_x11_drawable_get_xid(window),
-                           d->gc, 0, y2, d->ww, d->wh - y2);
-        }
-        if (d->have_mitshm && d->shminfo) {
-            XShmPutImage(d->dpy, gdk_x11_drawable_get_xid(window),
-                         d->gc, d->ximage,
-                         d->area.x, d->area.y, x, y, w, h,
-                         true);
-        } else {
-            XPutImage(d->dpy, gdk_x11_drawable_get_xid(window),
-                      d->gc, d->ximage,
-                      d->area.x, d->area.y, x, y, w, h);
-        }
-    }
-}
-
-G_GNUC_INTERNAL
-gboolean spicex_is_scaled(SpiceDisplay *display)
-{
-    return FALSE; /* backend doesn't support scaling yet */
-}
diff --git a/gtk/spice-widget.c b/gtk/spice-widget.c
deleted file mode 100644
index b9c4972..0000000
--- a/gtk/spice-widget.c
+++ /dev/null
@@ -1,2642 +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 <math.h>
-#include <glib.h>
-
-#if HAVE_X11_XKBLIB_H
-#include <X11/XKBlib.h>
-#include <gdk/gdkx.h>
-#endif
-#ifdef GDK_WINDOWING_X11
-#include <X11/Xlib.h>
-#include <gdk/gdkx.h>
-#endif
-#ifdef G_OS_WIN32
-#include <windows.h>
-#include <gdk/gdkwin32.h>
-#ifndef MAPVK_VK_TO_VSC /* may be undefined in older mingw-headers */
-#define MAPVK_VK_TO_VSC 0
-#endif
-#endif
-
-#include "spice-widget.h"
-#include "spice-widget-priv.h"
-#include "spice-gtk-session-priv.h"
-#include "vncdisplaykeymap.h"
-
-#include "glib-compat.h"
-#include "gtk-compat.h"
-
-/* Some compatibility defines to let us build on both Gtk2 and Gtk3 */
-
-/**
- * SECTION:spice-widget
- * @short_description: a GTK display widget
- * @title: Spice Display
- * @section_id:
- * @stability: Stable
- * @include: spice-widget.h
- *
- * A GTK widget that displays a SPICE server. It sends keyboard/mouse
- * events and can also share clipboard...
- *
- * Arbitrary key events can be sent thanks to spice_display_send_keys().
- *
- * The widget will optionally grab the keyboard and the mouse when
- * focused if the properties #SpiceDisplay:grab-keyboard and
- * #SpiceDisplay:grab-mouse are #TRUE respectively.  It can be
- * ungrabbed with spice_display_mouse_ungrab(), and by setting a key
- * combination with spice_display_set_grab_keys().
- *
- * Finally, spice_display_get_pixbuf() will take a screenshot of the
- * current display and return an #GdkPixbuf (that you can then easily
- * save to disk).
- */
-
-G_DEFINE_TYPE(SpiceDisplay, spice_display, GTK_TYPE_DRAWING_AREA)
-
-/* Properties */
-enum {
-    PROP_0,
-    PROP_SESSION,
-    PROP_CHANNEL_ID,
-    PROP_KEYBOARD_GRAB,
-    PROP_MOUSE_GRAB,
-    PROP_RESIZE_GUEST,
-    PROP_AUTO_CLIPBOARD,
-    PROP_SCALING,
-    PROP_ONLY_DOWNSCALE,
-    PROP_DISABLE_INPUTS,
-    PROP_ZOOM_LEVEL,
-    PROP_MONITOR_ID,
-    PROP_KEYPRESS_DELAY,
-    PROP_READY
-};
-
-/* Signals */
-enum {
-    SPICE_DISPLAY_MOUSE_GRAB,
-    SPICE_DISPLAY_KEYBOARD_GRAB,
-    SPICE_DISPLAY_GRAB_KEY_PRESSED,
-    SPICE_DISPLAY_LAST_SIGNAL,
-};
-
-static guint signals[SPICE_DISPLAY_LAST_SIGNAL];
-
-#ifdef G_OS_WIN32
-static HWND win32_window = NULL;
-#endif
-
-static void update_keyboard_grab(SpiceDisplay *display);
-static void try_keyboard_grab(SpiceDisplay *display);
-static void try_keyboard_ungrab(SpiceDisplay *display);
-static void update_mouse_grab(SpiceDisplay *display);
-static void try_mouse_grab(SpiceDisplay *display);
-static void try_mouse_ungrab(SpiceDisplay *display);
-static void recalc_geometry(GtkWidget *widget);
-static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data);
-static void channel_destroy(SpiceSession *s, SpiceChannel *channel, gpointer data);
-static void cursor_invalidate(SpiceDisplay *display);
-static void update_area(SpiceDisplay *display, gint x, gint y, gint width, gint height);
-static void release_keys(SpiceDisplay *display);
-
-/* ---------------------------------------------------------------- */
-
-static void spice_display_get_property(GObject    *object,
-                                       guint       prop_id,
-                                       GValue     *value,
-                                       GParamSpec *pspec)
-{
-    SpiceDisplay *display = SPICE_DISPLAY(object);
-    SpiceDisplayPrivate *d = display->priv;
-    gboolean boolean;
-
-    switch (prop_id) {
-    case PROP_SESSION:
-        g_value_set_object(value, d->session);
-        break;
-    case PROP_CHANNEL_ID:
-        g_value_set_int(value, d->channel_id);
-        break;
-    case PROP_MONITOR_ID:
-        g_value_set_int(value, d->monitor_id);
-        break;
-    case PROP_KEYBOARD_GRAB:
-        g_value_set_boolean(value, d->keyboard_grab_enable);
-        break;
-    case PROP_MOUSE_GRAB:
-        g_value_set_boolean(value, d->mouse_grab_enable);
-        break;
-    case PROP_RESIZE_GUEST:
-        g_value_set_boolean(value, d->resize_guest_enable);
-        break;
-    case PROP_AUTO_CLIPBOARD:
-        g_object_get(d->gtk_session, "auto-clipboard", &boolean, NULL);
-        g_value_set_boolean(value, boolean);
-        break;
-    case PROP_SCALING:
-        g_value_set_boolean(value, d->allow_scaling);
-        break;
-    case PROP_ONLY_DOWNSCALE:
-        g_value_set_boolean(value, d->only_downscale);
-        break;
-    case PROP_DISABLE_INPUTS:
-        g_value_set_boolean(value, d->disable_inputs);
-        break;
-    case PROP_ZOOM_LEVEL:
-        g_value_set_int(value, d->zoom_level);
-        break;
-    case PROP_READY:
-        g_value_set_boolean(value, d->ready);
-        break;
-    case PROP_KEYPRESS_DELAY:
-        g_value_set_uint(value, d->keypress_delay);
-        break;
-    default:
-        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
-        break;
-    }
-}
-
-static void scaling_updated(SpiceDisplay *display)
-{
-    SpiceDisplayPrivate *d = display->priv;
-    GdkWindow *window = gtk_widget_get_window(GTK_WIDGET(display));
-
-    recalc_geometry(GTK_WIDGET(display));
-    if (d->ximage && window) { /* if not yet shown */
-        gtk_widget_queue_draw(GTK_WIDGET(display));
-    }
-}
-
-static void update_size_request(SpiceDisplay *display)
-{
-    SpiceDisplayPrivate *d = display->priv;
-    gint reqwidth, reqheight;
-
-    if (d->resize_guest_enable) {
-        reqwidth = 640;
-        reqheight = 480;
-    } else {
-        reqwidth = d->area.width;
-        reqheight = d->area.height;
-    }
-
-    gtk_widget_set_size_request(GTK_WIDGET(display), reqwidth, reqheight);
-    recalc_geometry(GTK_WIDGET(display));
-}
-
-static void update_keyboard_focus(SpiceDisplay *display, gboolean state)
-{
-    SpiceDisplayPrivate *d = display->priv;
-
-    d->keyboard_have_focus = state;
-
-    /* keyboard grab gets inhibited by usb-device-manager when it is
-       in the process of redirecting a usb-device (as this may show a
-       policykit dialog). Making autoredir/automount setting changes while
-       this is happening is not a good idea! */
-    if (d->keyboard_grab_inhibit)
-        return;
-
-    spice_gtk_session_request_auto_usbredir(d->gtk_session, state);
-}
-
-static void update_ready(SpiceDisplay *display)
-{
-    SpiceDisplayPrivate *d = display->priv;
-    gboolean ready;
-
-    ready = d->mark != 0 && d->monitor_ready;
-
-    if (d->ready == ready)
-        return;
-
-    if (ready && gtk_widget_get_window(GTK_WIDGET(display)))
-        gtk_widget_queue_draw(GTK_WIDGET(display));
-
-    d->ready = ready;
-    g_object_notify(G_OBJECT(display), "ready");
-}
-
-static void set_monitor_ready(SpiceDisplay *self, gboolean ready)
-{
-    SpiceDisplayPrivate *d = self->priv;
-
-    d->monitor_ready = ready;
-    update_ready(self);
-}
-
-static gint get_display_id(SpiceDisplay *display)
-{
-    SpiceDisplayPrivate *d = display->priv;
-
-    /* supported monitor_id only with display channel #0 */
-    if (d->channel_id == 0 && d->monitor_id >= 0)
-        return d->monitor_id;
-
-    g_return_val_if_fail(d->monitor_id <= 0, -1);
-
-    return d->channel_id;
-}
-
-static void update_monitor_area(SpiceDisplay *display)
-{
-    SpiceDisplayPrivate *d = display->priv;
-    SpiceDisplayMonitorConfig *cfg, *c = NULL;
-    GArray *monitors = NULL;
-    int i;
-
-    SPICE_DEBUG("update monitor area %d:%d", d->channel_id, d->monitor_id);
-    if (d->monitor_id < 0)
-        goto whole;
-
-    g_object_get(d->display, "monitors", &monitors, NULL);
-    for (i = 0; monitors != NULL && i < monitors->len; i++) {
-        cfg = &g_array_index(monitors, SpiceDisplayMonitorConfig, i);
-        if (cfg->id == d->monitor_id) {
-           c = cfg;
-           break;
-        }
-    }
-    if (c == NULL) {
-        SPICE_DEBUG("update monitor: no monitor %d", d->monitor_id);
-        set_monitor_ready(display, false);
-        if (spice_channel_test_capability(d->display, SPICE_DISPLAY_CAP_MONITORS_CONFIG)) {
-            SPICE_DEBUG("waiting until MonitorsConfig is received");
-            g_clear_pointer(&monitors, g_array_unref);
-            return;
-        }
-        goto whole;
-    }
-
-    if (c->surface_id != 0) {
-        g_warning("FIXME: only support monitor config with primary surface 0, "
-                  "but given config surface %d", c->surface_id);
-        goto whole;
-    }
-
-    if (!d->resize_guest_enable)
-        spice_main_update_display(d->main, get_display_id(display),
-                                  c->x, c->y, c->width, c->height, FALSE);
-
-    update_area(display, c->x, c->y, c->width, c->height);
-    g_clear_pointer(&monitors, g_array_unref);
-    return;
-
-whole:
-    g_clear_pointer(&monitors, g_array_unref);
-    /* by display whole surface */
-    update_area(display, 0, 0, d->width, d->height);
-    set_monitor_ready(display, true);
-}
-
-static void spice_display_set_property(GObject      *object,
-                                       guint         prop_id,
-                                       const GValue *value,
-                                       GParamSpec   *pspec)
-{
-    SpiceDisplay *display = SPICE_DISPLAY(object);
-    SpiceDisplayPrivate *d = display->priv;
-
-    switch (prop_id) {
-    case PROP_SESSION:
-        g_warn_if_fail(d->session == NULL);
-        d->session = g_value_dup_object(value);
-        d->gtk_session = spice_gtk_session_get(d->session);
-        spice_g_signal_connect_object(d->gtk_session, "notify::pointer-grabbed",
-                                      G_CALLBACK(cursor_invalidate), object,
-                                      G_CONNECT_SWAPPED);
-        break;
-    case PROP_CHANNEL_ID:
-        d->channel_id = g_value_get_int(value);
-        break;
-    case PROP_MONITOR_ID:
-        d->monitor_id = g_value_get_int(value);
-        if (d->display) /* if constructed */
-            update_monitor_area(display);
-        break;
-    case PROP_KEYBOARD_GRAB:
-        d->keyboard_grab_enable = g_value_get_boolean(value);
-        update_keyboard_grab(display);
-        break;
-    case PROP_MOUSE_GRAB:
-        d->mouse_grab_enable = g_value_get_boolean(value);
-        update_mouse_grab(display);
-        break;
-    case PROP_RESIZE_GUEST:
-        d->resize_guest_enable = g_value_get_boolean(value);
-        update_size_request(display);
-        break;
-    case PROP_SCALING:
-        d->allow_scaling = g_value_get_boolean(value);
-        scaling_updated(display);
-        break;
-    case PROP_ONLY_DOWNSCALE:
-        d->only_downscale = g_value_get_boolean(value);
-        scaling_updated(display);
-        break;
-    case PROP_AUTO_CLIPBOARD:
-        g_object_set(d->gtk_session, "auto-clipboard",
-                     g_value_get_boolean(value), NULL);
-        break;
-    case PROP_DISABLE_INPUTS:
-        d->disable_inputs = g_value_get_boolean(value);
-        gtk_widget_set_can_focus(GTK_WIDGET(display), !d->disable_inputs);
-        update_keyboard_grab(display);
-        update_mouse_grab(display);
-        break;
-    case PROP_ZOOM_LEVEL:
-        d->zoom_level = g_value_get_int(value);
-        scaling_updated(display);
-        break;
-    case PROP_KEYPRESS_DELAY:
-        d->keypress_delay = g_value_get_uint(value);
-        break;
-    default:
-        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
-        break;
-    }
-}
-
-static void gtk_session_property_changed(GObject    *gobject,
-                                         GParamSpec *pspec,
-                                         gpointer    user_data)
-{
-    SpiceDisplay *display = user_data;
-
-    g_object_notify(G_OBJECT(display), g_param_spec_get_name(pspec));
-}
-
-static void session_inhibit_keyboard_grab_changed(GObject    *gobject,
-                                                  GParamSpec *pspec,
-                                                  gpointer    user_data)
-{
-    SpiceDisplay *display = user_data;
-    SpiceDisplayPrivate *d = display->priv;
-
-    g_object_get(d->session, "inhibit-keyboard-grab",
-                 &d->keyboard_grab_inhibit, NULL);
-    update_keyboard_grab(display);
-    update_mouse_grab(display);
-}
-
-static void spice_display_dispose(GObject *obj)
-{
-    SpiceDisplay *display = SPICE_DISPLAY(obj);
-    SpiceDisplayPrivate *d = display->priv;
-
-    SPICE_DEBUG("spice display dispose");
-
-    spicex_image_destroy(display);
-    g_clear_object(&d->session);
-    d->gtk_session = NULL;
-
-    if (d->key_delayed_id) {
-        g_source_remove(d->key_delayed_id);
-        d->key_delayed_id = 0;
-    }
-
-    G_OBJECT_CLASS(spice_display_parent_class)->dispose(obj);
-}
-
-static void spice_display_finalize(GObject *obj)
-{
-    SpiceDisplay *display = SPICE_DISPLAY(obj);
-    SpiceDisplayPrivate *d = display->priv;
-
-    SPICE_DEBUG("Finalize spice display");
-
-    if (d->grabseq) {
-        spice_grab_sequence_free(d->grabseq);
-        d->grabseq = NULL;
-    }
-    g_free(d->activeseq);
-    d->activeseq = NULL;
-
-    if (d->show_cursor) {
-        gdk_cursor_unref(d->show_cursor);
-        d->show_cursor = NULL;
-    }
-
-    if (d->mouse_cursor) {
-        gdk_cursor_unref(d->mouse_cursor);
-        d->mouse_cursor = NULL;
-    }
-
-    if (d->mouse_pixbuf) {
-        g_object_unref(d->mouse_pixbuf);
-        d->mouse_pixbuf = NULL;
-    }
-
-    G_OBJECT_CLASS(spice_display_parent_class)->finalize(obj);
-}
-
-static GdkCursor* get_blank_cursor(void)
-{
-    if (g_getenv("SPICE_DEBUG_CURSOR"))
-        return gdk_cursor_new(GDK_DOT);
-
-    return gdk_cursor_new(GDK_BLANK_CURSOR);
-}
-
-static gboolean grab_broken(SpiceDisplay *self, GdkEventGrabBroken *event,
-                            gpointer user_data G_GNUC_UNUSED)
-{
-    SPICE_DEBUG("%s (implicit: %d, keyboard: %d)", __FUNCTION__,
-                event->implicit, event->keyboard);
-
-    if (event->keyboard) {
-        try_keyboard_ungrab(self);
-        release_keys(self);
-    }
-
-    /* always release mouse when grab broken, this could be more
-       generally placed in keyboard_ungrab(), but one might worry of
-       breaking someone else code. */
-    try_mouse_ungrab(self);
-
-    return false;
-}
-
-static void drag_data_received_callback(SpiceDisplay *self,
-                                        GdkDragContext *drag_context,
-                                        gint x,
-                                        gint y,
-                                        GtkSelectionData *data,
-                                        guint info,
-                                        guint time,
-                                        gpointer *user_data)
-{
-    const guchar *buf;
-    gchar **file_urls;
-    int n_files;
-    SpiceDisplayPrivate *d = self->priv;
-    int i = 0;
-    GFile **files;
-
-    /* We get a buf like:
-     * file:///root/a.txt\r\nfile:///root/b.txt\r\n
-     */
-    SPICE_DEBUG("%s: drag a file", __FUNCTION__);
-    buf = gtk_selection_data_get_data(data);
-    g_return_if_fail(buf != NULL);
-
-    file_urls = g_uri_list_extract_uris((const gchar*)buf);
-    n_files = g_strv_length(file_urls);
-    files = g_new0(GFile*, n_files + 1);
-    for (i = 0; i < n_files; i++) {
-        files[i] = g_file_new_for_uri(file_urls[i]);
-    }
-    g_strfreev(file_urls);
-
-    spice_main_file_copy_async(d->main, files, 0, NULL, NULL,
-                               NULL, NULL, NULL);
-    for (i = 0; i < n_files; i++) {
-        g_object_unref(files[i]);
-    }
-    g_free(files);
-
-    gtk_drag_finish(drag_context, TRUE, FALSE, time);
-}
-
-static void grab_notify(SpiceDisplay *display, gboolean was_grabbed)
-{
-    SPICE_DEBUG("grab notify %d", was_grabbed);
-
-    if (was_grabbed == FALSE)
-        release_keys(display);
-}
-
-static void spice_display_init(SpiceDisplay *display)
-{
-    GtkWidget *widget = GTK_WIDGET(display);
-    SpiceDisplayPrivate *d;
-    GtkTargetEntry targets = { "text/uri-list", 0, 0 };
-
-    d = display->priv = SPICE_DISPLAY_GET_PRIVATE(display);
-
-    g_signal_connect(display, "grab-broken-event", G_CALLBACK(grab_broken), NULL);
-    g_signal_connect(display, "grab-notify", G_CALLBACK(grab_notify), NULL);
-
-    gtk_drag_dest_set(widget, GTK_DEST_DEFAULT_ALL, &targets, 1, GDK_ACTION_COPY);
-    g_signal_connect(display, "drag-data-received",
-                     G_CALLBACK(drag_data_received_callback), NULL);
-
-    gtk_widget_add_events(widget,
-                          GDK_STRUCTURE_MASK |
-                          GDK_POINTER_MOTION_MASK |
-                          GDK_BUTTON_PRESS_MASK |
-                          GDK_BUTTON_RELEASE_MASK |
-                          GDK_BUTTON_MOTION_MASK |
-                          GDK_ENTER_NOTIFY_MASK |
-                          GDK_LEAVE_NOTIFY_MASK |
-                          GDK_KEY_PRESS_MASK |
-                          GDK_SCROLL_MASK);
-#ifdef WITH_X11
-    gtk_widget_set_double_buffered(widget, false);
-#else
-    gtk_widget_set_double_buffered(widget, true);
-#endif
-    gtk_widget_set_can_focus(widget, true);
-    gtk_widget_set_has_window(widget, true);
-    d->grabseq = spice_grab_sequence_new_from_string("Control_L+Alt_L");
-    d->activeseq = g_new0(gboolean, d->grabseq->nkeysyms);
-
-    d->mouse_cursor = get_blank_cursor();
-    d->have_mitshm = true;
-}
-
-static GObject *
-spice_display_constructor(GType                  gtype,
-                          guint                  n_properties,
-                          GObjectConstructParam *properties)
-{
-    GObject *obj;
-    SpiceDisplay *display;
-    SpiceDisplayPrivate *d;
-    GList *list;
-    GList *it;
-
-    {
-        /* Always chain up to the parent constructor */
-        GObjectClass *parent_class;
-        parent_class = G_OBJECT_CLASS(spice_display_parent_class);
-        obj = parent_class->constructor(gtype, n_properties, properties);
-    }
-
-    display = SPICE_DISPLAY(obj);
-    d = display->priv;
-
-    if (!d->session)
-        g_error("SpiceDisplay constructed without a session");
-
-    spice_g_signal_connect_object(d->session, "channel-new",
-                                  G_CALLBACK(channel_new), display, 0);
-    spice_g_signal_connect_object(d->session, "channel-destroy",
-                                  G_CALLBACK(channel_destroy), display, 0);
-    list = spice_session_get_channels(d->session);
-    for (it = g_list_first(list); it != NULL; it = g_list_next(it)) {
-        if (SPICE_IS_MAIN_CHANNEL(it->data)) {
-            channel_new(d->session, it->data, (gpointer*)display);
-            break;
-        }
-    }
-    for (it = g_list_first(list); it != NULL; it = g_list_next(it)) {
-        if (!SPICE_IS_MAIN_CHANNEL(it->data))
-            channel_new(d->session, it->data, (gpointer*)display);
-    }
-    g_list_free(list);
-
-    spice_g_signal_connect_object(d->gtk_session, "notify::auto-clipboard",
-                                  G_CALLBACK(gtk_session_property_changed), display, 0);
-
-    spice_g_signal_connect_object(d->session, "notify::inhibit-keyboard-grab",
-                                  G_CALLBACK(session_inhibit_keyboard_grab_changed),
-                                  display, 0);
-
-    return obj;
-}
-
-/**
- * spice_display_set_grab_keys:
- * @display: the display widget
- * @seq: (transfer none): key sequence
- *
- * Set the key combination to grab/ungrab the keyboard. The default is
- * "Control L + Alt L".
- **/
-void spice_display_set_grab_keys(SpiceDisplay *display, SpiceGrabSequence *seq)
-{
-    SpiceDisplayPrivate *d;
-
-    g_return_if_fail(SPICE_IS_DISPLAY(display));
-
-    d = display->priv;
-    g_return_if_fail(d != NULL);
-
-    if (d->grabseq) {
-        spice_grab_sequence_free(d->grabseq);
-    }
-    if (seq)
-        d->grabseq = spice_grab_sequence_copy(seq);
-    else
-        d->grabseq = spice_grab_sequence_new_from_string("Control_L+Alt_L");
-    g_free(d->activeseq);
-    d->activeseq = g_new0(gboolean, d->grabseq->nkeysyms);
-}
-
-#ifdef G_OS_WIN32
-static LRESULT CALLBACK keyboard_hook_cb(int code, WPARAM wparam, LPARAM lparam)
-{
-    if  (win32_window && code == HC_ACTION && wparam != WM_KEYUP) {
-        KBDLLHOOKSTRUCT *hooked = (KBDLLHOOKSTRUCT*)lparam;
-        DWORD dwmsg = (hooked->flags << 24) | (hooked->scanCode << 16) | 1;
-
-        if (hooked->vkCode == VK_NUMLOCK || hooked->vkCode == VK_RSHIFT) {
-            dwmsg &= ~(1 << 24);
-            SendMessage(win32_window, wparam, hooked->vkCode, dwmsg);
-        }
-        switch (hooked->vkCode) {
-        case VK_CAPITAL:
-        case VK_SCROLL:
-        case VK_NUMLOCK:
-        case VK_LSHIFT:
-        case VK_RSHIFT:
-        case VK_RCONTROL:
-        case VK_LMENU:
-        case VK_RMENU:
-            break;
-        case VK_LCONTROL:
-            /* When pressing AltGr, an extra VK_LCONTROL with a special
-             * scancode with bit 9 set is sent. Let's ignore the extra
-             * VK_LCONTROL, as that will make AltGr misbehave. */
-            if (hooked->scanCode & 0x200)
-                return 1;
-            break;
-        default:
-            SendMessage(win32_window, wparam, hooked->vkCode, dwmsg);
-            return 1;
-        }
-    }
-    return CallNextHookEx(NULL, code, wparam, lparam);
-}
-#endif
-
-/**
- * spice_display_get_grab_keys:
- * @display: the display widget
- *
- * Returns: (transfer none): the current grab key combination.
- **/
-SpiceGrabSequence *spice_display_get_grab_keys(SpiceDisplay *display)
-{
-    SpiceDisplayPrivate *d;
-
-    g_return_val_if_fail(SPICE_IS_DISPLAY(display), NULL);
-
-    d = display->priv;
-    g_return_val_if_fail(d != NULL, NULL);
-
-    return d->grabseq;
-}
-
-static void try_keyboard_grab(SpiceDisplay *display)
-{
-    GtkWidget *widget = GTK_WIDGET(display);
-    SpiceDisplayPrivate *d = display->priv;
-    GdkGrabStatus status;
-
-    if (g_getenv("SPICE_NOGRAB"))
-        return;
-    if (d->disable_inputs)
-        return;
-
-    if (d->keyboard_grab_inhibit)
-        return;
-    if (!d->keyboard_grab_enable)
-        return;
-    if (d->keyboard_grab_active)
-        return;
-    if (!d->keyboard_have_focus)
-        return;
-    if (!d->mouse_have_pointer)
-        return;
-    if (d->keyboard_grab_released)
-        return;
-
-    g_return_if_fail(gtk_widget_is_focus(widget));
-
-    SPICE_DEBUG("grab keyboard");
-    gtk_widget_grab_focus(widget);
-
-#ifdef G_OS_WIN32
-    if (d->keyboard_hook == NULL)
-        d->keyboard_hook = SetWindowsHookEx(WH_KEYBOARD_LL, keyboard_hook_cb,
-                                            GetModuleHandle(NULL), 0);
-    g_warn_if_fail(d->keyboard_hook != NULL);
-#endif
-    status = gdk_keyboard_grab(gtk_widget_get_window(widget), FALSE,
-                               GDK_CURRENT_TIME);
-    if (status != GDK_GRAB_SUCCESS) {
-        g_warning("keyboard grab failed %d", status);
-        d->keyboard_grab_active = false;
-    } else {
-        d->keyboard_grab_active = true;
-        g_signal_emit(widget, signals[SPICE_DISPLAY_KEYBOARD_GRAB], 0, true);
-    }
-}
-
-static void try_keyboard_ungrab(SpiceDisplay *display)
-{
-    SpiceDisplayPrivate *d = display->priv;
-    GtkWidget *widget = GTK_WIDGET(display);
-
-    if (!d->keyboard_grab_active)
-        return;
-
-    SPICE_DEBUG("ungrab keyboard");
-    gdk_keyboard_ungrab(GDK_CURRENT_TIME);
-#ifdef G_OS_WIN32
-    if (d->keyboard_hook != NULL) {
-        UnhookWindowsHookEx(d->keyboard_hook);
-        d->keyboard_hook = NULL;
-    }
-#endif
-    d->keyboard_grab_active = false;
-    g_signal_emit(widget, signals[SPICE_DISPLAY_KEYBOARD_GRAB], 0, false);
-}
-
-static void update_keyboard_grab(SpiceDisplay *display)
-{
-    SpiceDisplayPrivate *d = display->priv;
-
-    if (d->keyboard_grab_enable &&
-        !d->keyboard_grab_inhibit &&
-        !d->disable_inputs)
-        try_keyboard_grab(display);
-    else
-        try_keyboard_ungrab(display);
-}
-
-static void set_mouse_accel(SpiceDisplay *display, gboolean enabled)
-{
-    SpiceDisplayPrivate *d = display->priv;
-
-#if defined GDK_WINDOWING_X11
-    GdkWindow *w = GDK_WINDOW(gtk_widget_get_window(GTK_WIDGET(display)));
-
-    if (!GDK_IS_X11_DISPLAY(gdk_window_get_display(w))) {
-        SPICE_DEBUG("FIXME: gtk backend is not X11");
-        return;
-    }
-
-    Display *x_display = GDK_WINDOW_XDISPLAY(w);
-    if (enabled) {
-        /* restore mouse acceleration */
-        XChangePointerControl(x_display, True, True,
-                              d->x11_accel_numerator, d->x11_accel_denominator, d->x11_threshold);
-    } else {
-        XGetPointerControl(x_display,
-                           &d->x11_accel_numerator, &d->x11_accel_denominator, &d->x11_threshold);
-        /* set mouse acceleration to default */
-        XChangePointerControl(x_display, True, True, -1, -1, -1);
-        SPICE_DEBUG("disabled X11 mouse motion %d %d %d",
-                    d->x11_accel_numerator, d->x11_accel_denominator, d->x11_threshold);
-    }
-#elif defined GDK_WINDOWING_WIN32
-    if (enabled) {
-        g_return_if_fail(SystemParametersInfo(SPI_SETMOUSE, 0, &d->win_mouse, 0));
-        g_return_if_fail(SystemParametersInfo(SPI_SETMOUSESPEED, 0, (PVOID)(INT_PTR)d->win_mouse_speed, 0));
-    } else {
-        int accel[3] = { 0, 0, 0 }; // disabled
-        g_return_if_fail(SystemParametersInfo(SPI_GETMOUSE, 0, &d->win_mouse, 0));
-        g_return_if_fail(SystemParametersInfo(SPI_GETMOUSESPEED, 0, &d->win_mouse_speed, 0));
-        g_return_if_fail(SystemParametersInfo(SPI_SETMOUSE, 0, &accel, SPIF_SENDCHANGE));
-        g_return_if_fail(SystemParametersInfo(SPI_SETMOUSESPEED, 0, (PVOID)10, SPIF_SENDCHANGE)); // default
-    }
-#else
-    g_warning("Mouse acceleration code missing for your platform");
-#endif
-}
-
-#ifdef G_OS_WIN32
-static gboolean win32_clip_cursor(void)
-{
-    RECT window, workarea, rect;
-    HMONITOR monitor;
-    MONITORINFO mi = { 0, };
-
-    g_return_val_if_fail(win32_window != NULL, FALSE);
-
-    if (!GetWindowRect(win32_window, &window))
-        goto error;
-
-    monitor = MonitorFromRect(&window, MONITOR_DEFAULTTONEAREST);
-    g_return_val_if_fail(monitor != NULL, false);
-
-    mi.cbSize = sizeof(mi);
-    if (!GetMonitorInfo(monitor, &mi))
-        goto error;
-    workarea = mi.rcWork;
-
-    if (!IntersectRect(&rect, &window, &workarea)) {
-        g_critical("error clipping cursor");
-        return false;
-    }
-
-    SPICE_DEBUG("clip rect %ld %ld %ld %ld\n",
-                rect.left, rect.right, rect.top, rect.bottom);
-
-    if (!ClipCursor(&rect))
-        goto error;
-
-    return true;
-
-error:
-    {
-        DWORD errval  = GetLastError();
-        gchar *errstr = g_win32_error_message(errval);
-        g_warning("failed to clip cursor (%ld) %s", errval, errstr);
-    }
-
-    return false;
-}
-#endif
-
-static GdkGrabStatus do_pointer_grab(SpiceDisplay *display)
-{
-    SpiceDisplayPrivate *d = display->priv;
-    GdkWindow *window = GDK_WINDOW(gtk_widget_get_window(GTK_WIDGET(display)));
-    GdkGrabStatus status = GDK_GRAB_BROKEN;
-    GdkCursor *blank = get_blank_cursor();
-
-    if (!gtk_widget_get_realized(GTK_WIDGET(display)))
-        goto end;
-
-#ifdef G_OS_WIN32
-    if (!win32_clip_cursor())
-        goto end;
-#endif
-
-    try_keyboard_grab(display);
-    /*
-     * from gtk-vnc:
-     * For relative mouse to work correctly when grabbed we need to
-     * allow the pointer to move anywhere on the local desktop, so
-     * use NULL for the 'confine_to' argument. Furthermore we need
-     * the coords to be reported to our VNC window, regardless of
-     * what window the pointer is actally over, so use 'FALSE' for
-     * 'owner_events' parameter
-     */
-    status = gdk_pointer_grab(window, FALSE,
-                     GDK_POINTER_MOTION_MASK |
-                     GDK_BUTTON_PRESS_MASK |
-                     GDK_BUTTON_RELEASE_MASK |
-                     GDK_BUTTON_MOTION_MASK |
-                     GDK_SCROLL_MASK,
-                     NULL,
-                     blank,
-                     GDK_CURRENT_TIME);
-    if (status != GDK_GRAB_SUCCESS) {
-        d->mouse_grab_active = false;
-        g_warning("pointer grab failed %d", status);
-    } else {
-        d->mouse_grab_active = true;
-        g_signal_emit(display, signals[SPICE_DISPLAY_MOUSE_GRAB], 0, true);
-        spice_gtk_session_set_pointer_grabbed(d->gtk_session, true);
-        set_mouse_accel(display, FALSE);
-    }
-
-end:
-    gdk_cursor_unref(blank);
-    return status;
-}
-
-static void update_mouse_pointer(SpiceDisplay *display)
-{
-    SpiceDisplayPrivate *d = display->priv;
-    GdkWindow *window = GDK_WINDOW(gtk_widget_get_window(GTK_WIDGET(display)));
-
-    if (!window)
-        return;
-
-    switch (d->mouse_mode) {
-    case SPICE_MOUSE_MODE_CLIENT:
-        if (gdk_window_get_cursor(window) != d->mouse_cursor)
-            gdk_window_set_cursor(window, d->mouse_cursor);
-        break;
-    case SPICE_MOUSE_MODE_SERVER:
-        if (gdk_window_get_cursor(window) != NULL)
-            gdk_window_set_cursor(window, NULL);
-        break;
-    default:
-        g_warn_if_reached();
-        break;
-    }
-}
-
-static void try_mouse_grab(SpiceDisplay *display)
-{
-    SpiceDisplayPrivate *d = display->priv;
-
-    if (g_getenv("SPICE_NOGRAB"))
-        return;
-    if (d->disable_inputs)
-        return;
-
-    if (!d->mouse_have_pointer)
-        return;
-    if (!d->keyboard_have_focus)
-        return;
-
-    if (!d->mouse_grab_enable)
-        return;
-    if (d->mouse_mode != SPICE_MOUSE_MODE_SERVER)
-        return;
-    if (d->mouse_grab_active)
-        return;
-
-    if (do_pointer_grab(display) != GDK_GRAB_SUCCESS)
-        return;
-
-    d->mouse_last_x = -1;
-    d->mouse_last_y = -1;
-}
-
-static void mouse_wrap(SpiceDisplay *display, GdkEventMotion *motion)
-{
-    SpiceDisplayPrivate *d = display->priv;
-    gint xr, yr;
-
-#ifdef G_OS_WIN32
-    RECT clip;
-    g_return_if_fail(GetClipCursor(&clip));
-    xr = clip.left + (clip.right - clip.left) / 2;
-    yr = clip.top + (clip.bottom - clip.top) / 2;
-    /* the clip rectangle has no offset, so we can't use gdk_wrap_pointer */
-    SetCursorPos(xr, yr);
-    d->mouse_last_x = -1;
-    d->mouse_last_y = -1;
-#else
-    GdkScreen *screen = gtk_widget_get_screen(GTK_WIDGET(display));
-    xr = gdk_screen_get_width(screen) / 2;
-    yr = gdk_screen_get_height(screen) / 2;
-
-    if (xr != (gint)motion->x_root || yr != (gint)motion->y_root) {
-        /* FIXME: we try our best to ignore that next pointer move event.. */
-        gdk_display_sync(gdk_screen_get_display(screen));
-
-        gdk_display_warp_pointer(gtk_widget_get_display(GTK_WIDGET(display)),
-                                 screen, xr, yr);
-        d->mouse_last_x = -1;
-        d->mouse_last_y = -1;
-    }
-#endif
-
-}
-
-static void try_mouse_ungrab(SpiceDisplay *display)
-{
-    SpiceDisplayPrivate *d = display->priv;
-    double s;
-    int x, y;
-
-    if (!d->mouse_grab_active)
-        return;
-
-    gdk_pointer_ungrab(GDK_CURRENT_TIME);
-    gtk_grab_remove(GTK_WIDGET(display));
-#ifdef G_OS_WIN32
-    ClipCursor(NULL);
-#endif
-    set_mouse_accel(display, TRUE);
-
-    d->mouse_grab_active = false;
-
-    spice_display_get_scaling(display, &s, &x, &y, NULL, NULL);
-
-    gdk_window_get_root_coords(gtk_widget_get_window(GTK_WIDGET(display)),
-                               x + d->mouse_guest_x * s,
-                               y + d->mouse_guest_y * s,
-                               &x, &y);
-
-    gdk_display_warp_pointer(gtk_widget_get_display(GTK_WIDGET(display)),
-                             gtk_widget_get_screen(GTK_WIDGET(display)),
-                             x, y);
-
-    g_signal_emit(display, signals[SPICE_DISPLAY_MOUSE_GRAB], 0, false);
-    spice_gtk_session_set_pointer_grabbed(d->gtk_session, false);
-}
-
-static void update_mouse_grab(SpiceDisplay *display)
-{
-    SpiceDisplayPrivate *d = display->priv;
-
-    if (d->mouse_grab_enable &&
-        !d->keyboard_grab_inhibit &&
-        !d->disable_inputs)
-        try_mouse_grab(display);
-    else
-        try_mouse_ungrab(display);
-}
-
-static void recalc_geometry(GtkWidget *widget)
-{
-    SpiceDisplay *display = SPICE_DISPLAY(widget);
-    SpiceDisplayPrivate *d = display->priv;
-    gdouble zoom = 1.0;
-
-    if (spicex_is_scaled(display))
-        zoom = (gdouble)d->zoom_level / 100;
-
-    SPICE_DEBUG("recalc geom monitor: %d:%d, guest +%d+%d:%dx%d, window %dx%d, zoom %g",
-                d->channel_id, d->monitor_id, d->area.x, d->area.y, d->area.width, d->area.height,
-                d->ww, d->wh, zoom);
-
-    if (d->resize_guest_enable)
-        spice_main_set_display(d->main, get_display_id(display),
-                               d->area.x, d->area.y, d->ww / zoom, d->wh / zoom);
-}
-
-/* ---------------------------------------------------------------- */
-
-#define CONVERT_0565_TO_0888(s)                                         \
-    (((((s) << 3) & 0xf8) | (((s) >> 2) & 0x7)) |                       \
-     ((((s) << 5) & 0xfc00) | (((s) >> 1) & 0x300)) |                   \
-     ((((s) << 8) & 0xf80000) | (((s) << 3) & 0x70000)))
-
-#define CONVERT_0565_TO_8888(s) (CONVERT_0565_TO_0888(s) | 0xff000000)
-
-#define CONVERT_0555_TO_0888(s)                                         \
-    (((((s) & 0x001f) << 3) | (((s) & 0x001c) >> 2)) |                  \
-     ((((s) & 0x03e0) << 6) | (((s) & 0x0380) << 1)) |                  \
-     ((((s) & 0x7c00) << 9) | ((((s) & 0x7000)) << 4)))
-
-#define CONVERT_0555_TO_8888(s) (CONVERT_0555_TO_0888(s) | 0xff000000)
-
-static gboolean do_color_convert(SpiceDisplay *display, GdkRectangle *r)
-{
-    SpiceDisplayPrivate *d = display->priv;
-    guint32 *dest = d->data;
-    guint16 *src = d->data_origin;
-    gint x, y;
-
-    g_return_val_if_fail(r != NULL, false);
-    g_return_val_if_fail(d->format == SPICE_SURFACE_FMT_16_555 ||
-                         d->format == SPICE_SURFACE_FMT_16_565, false);
-
-    src += (d->stride / 2) * r->y + r->x;
-    dest += d->area.width * (r->y - d->area.y) + (r->x - d->area.x);
-
-    if (d->format == SPICE_SURFACE_FMT_16_555) {
-        for (y = 0; y < r->height; y++) {
-            for (x = 0; x < r->width; x++) {
-                dest[x] = CONVERT_0555_TO_0888(src[x]);
-            }
-
-            dest += d->area.width;
-            src += d->stride / 2;
-        }
-    } else if (d->format == SPICE_SURFACE_FMT_16_565) {
-        for (y = 0; y < r->height; y++) {
-            for (x = 0; x < r->width; x++) {
-                dest[x] = CONVERT_0565_TO_0888(src[x]);
-            }
-
-            dest += d->area.width;
-            src += d->stride / 2;
-        }
-    }
-
-    return true;
-}
-
-
-#if GTK_CHECK_VERSION (2, 91, 0)
-static gboolean draw_event(GtkWidget *widget, cairo_t *cr)
-{
-    SpiceDisplay *display = SPICE_DISPLAY(widget);
-    SpiceDisplayPrivate *d = display->priv;
-    g_return_val_if_fail(d != NULL, false);
-
-    if (d->mark == 0 || d->data == NULL ||
-        d->area.width == 0 || d->area.height == 0)
-        return false;
-    g_return_val_if_fail(d->ximage != NULL, false);
-
-    spicex_draw_event(display, cr);
-    update_mouse_pointer(display);
-
-    return true;
-}
-#else
-static gboolean expose_event(GtkWidget *widget, GdkEventExpose *expose)
-{
-    SpiceDisplay *display = SPICE_DISPLAY(widget);
-    SpiceDisplayPrivate *d = display->priv;
-    g_return_val_if_fail(d != NULL, false);
-
-    if (d->mark == 0 || d->data == NULL ||
-        d->area.width == 0 || d->area.height == 0)
-        return false;
-    g_return_val_if_fail(d->ximage != NULL, false);
-
-    spicex_expose_event(display, expose);
-    update_mouse_pointer(display);
-
-    return true;
-}
-#endif
-
-/* ---------------------------------------------------------------- */
-typedef enum {
-    SEND_KEY_PRESS,
-    SEND_KEY_RELEASE,
-} SendKeyType;
-
-static void key_press_and_release(SpiceDisplay *display)
-{
-    SpiceDisplayPrivate *d = display->priv;
-
-    if (d->key_delayed_scancode == 0)
-        return;
-
-    spice_inputs_key_press_and_release(d->inputs, d->key_delayed_scancode);
-    d->key_delayed_scancode = 0;
-
-    if (d->key_delayed_id) {
-        g_source_remove(d->key_delayed_id);
-        d->key_delayed_id = 0;
-    }
-}
-
-static gboolean key_press_delayed(gpointer data)
-{
-    SpiceDisplay *display = data;
-    SpiceDisplayPrivate *d = display->priv;
-
-    if (d->key_delayed_scancode == 0)
-        return FALSE;
-
-    spice_inputs_key_press(d->inputs, d->key_delayed_scancode);
-    d->key_delayed_scancode = 0;
-
-    if (d->key_delayed_id) {
-        g_source_remove(d->key_delayed_id);
-        d->key_delayed_id = 0;
-    }
-
-    return FALSE;
-}
-
-static void send_key(SpiceDisplay *display, int scancode, SendKeyType type, gboolean press_delayed)
-{
-    SpiceDisplayPrivate *d = display->priv;
-    uint32_t i, b, m;
-
-    g_return_if_fail(scancode != 0);
-
-    if (!d->inputs)
-        return;
-
-    if (d->disable_inputs)
-        return;
-
-    i = scancode / 32;
-    b = scancode % 32;
-    m = (1 << b);
-    g_return_if_fail(i < SPICE_N_ELEMENTS(d->key_state));
-
-    switch (type) {
-    case SEND_KEY_PRESS:
-        /* ensure delayed key is pressed before any new input event */
-        key_press_delayed(display);
-
-        if (press_delayed &&
-            d->keypress_delay != 0 &&
-            !(d->key_state[i] & m)) {
-            g_warn_if_fail(d->key_delayed_id == 0);
-            d->key_delayed_id = g_timeout_add(d->keypress_delay, key_press_delayed, display);
-            d->key_delayed_scancode = scancode;
-        } else
-            spice_inputs_key_press(d->inputs, scancode);
-
-        d->key_state[i] |= m;
-        break;
-
-    case SEND_KEY_RELEASE:
-        if (!(d->key_state[i] & m))
-            break;
-
-        if (d->key_delayed_scancode == scancode)
-            key_press_and_release(display);
-        else {
-            /* ensure delayed key is pressed before other key are released */
-            key_press_delayed(display);
-            spice_inputs_key_release(d->inputs, scancode);
-        }
-
-        d->key_state[i] &= ~m;
-        break;
-
-    default:
-        g_warn_if_reached();
-    }
-}
-
-static void release_keys(SpiceDisplay *display)
-{
-    SpiceDisplayPrivate *d = display->priv;
-    uint32_t i, b;
-
-    SPICE_DEBUG("%s", __FUNCTION__);
-    for (i = 0; i < SPICE_N_ELEMENTS(d->key_state); i++) {
-        if (!d->key_state[i]) {
-            continue;
-        }
-        for (b = 0; b < 32; b++) {
-            unsigned int scancode = i * 32 + b;
-            if (scancode != 0) {
-                send_key(display, scancode, SEND_KEY_RELEASE, FALSE);
-            }
-        }
-    }
-}
-
-static gboolean check_for_grab_key(SpiceDisplay *display, int type, int keyval,
-                                   int check_type, int reset_type)
-{
-    SpiceDisplayPrivate *d = display->priv;
-    int i;
-
-    if (!d->grabseq->nkeysyms)
-        return FALSE;
-
-    if (type == check_type) {
-        /* Record the new key */
-        for (i = 0 ; i < d->grabseq->nkeysyms ; i++)
-            if (d->grabseq->keysyms[i] == keyval)
-                d->activeseq[i] = TRUE;
-
-        /* Return if any key is missing */
-        for (i = 0 ; i < d->grabseq->nkeysyms ; i++)
-            if (d->activeseq[i] == FALSE)
-                return FALSE;
-
-        /* resets the whole grab sequence on success */
-        memset(d->activeseq, 0, sizeof(gboolean) * d->grabseq->nkeysyms);
-        return TRUE;
-    } else if (type == reset_type) {
-        /* reset key event type resets the whole grab sequence */
-        memset(d->activeseq, 0, sizeof(gboolean) * d->grabseq->nkeysyms);
-        d->seq_pressed = FALSE;
-        return FALSE;
-    } else
-        g_warn_if_reached();
-
-    return FALSE;
-}
-
-static gboolean check_for_grab_key_pressed(SpiceDisplay *display, int type, int keyval)
-{
-    return check_for_grab_key(display, type, keyval, GDK_KEY_PRESS, GDK_KEY_RELEASE);
-}
-
-static gboolean check_for_grab_key_released(SpiceDisplay *display, int type, int keyval)
-{
-    return check_for_grab_key(display, type, keyval, GDK_KEY_RELEASE, GDK_KEY_PRESS);
-}
-
-static void update_display(SpiceDisplay *display)
-{
-#ifdef G_OS_WIN32
-    win32_window = display ? GDK_WINDOW_HWND(gtk_widget_get_window(GTK_WIDGET(display))) : NULL;
-#endif
-}
-
-static gboolean key_event(GtkWidget *widget, GdkEventKey *key)
-{
-    SpiceDisplay *display = SPICE_DISPLAY(widget);
-    SpiceDisplayPrivate *d = display->priv;
-    int scancode;
-
-#ifdef G_OS_WIN32
-    /* on windows, we ought to ignore the reserved key event? */
-    if (key->hardware_keycode == 0xff)
-        return false;
-
-    if (!d->keyboard_grab_active) {
-        if (key->hardware_keycode == VK_LWIN ||
-            key->hardware_keycode == VK_RWIN ||
-            key->hardware_keycode == VK_APPS)
-            return false;
-    }
-
-#endif
-    SPICE_DEBUG("%s %s: keycode: %d  state: %d  group %d modifier %d",
-            __FUNCTION__, key->type == GDK_KEY_PRESS ? "press" : "release",
-            key->hardware_keycode, key->state, key->group, key->is_modifier);
-
-    if (!d->seq_pressed && check_for_grab_key_pressed(display, key->type, key->keyval)) {
-        g_signal_emit(widget, signals[SPICE_DISPLAY_GRAB_KEY_PRESSED], 0);
-
-        if (d->mouse_mode == SPICE_MOUSE_MODE_SERVER) {
-            if (d->mouse_grab_active)
-                try_mouse_ungrab(display);
-            else
-                try_mouse_grab(display);
-        }
-        d->seq_pressed = TRUE;
-    } else if (d->seq_pressed && check_for_grab_key_released(display, key->type, key->keyval)) {
-        release_keys(display);
-        if (!d->keyboard_grab_released) {
-            d->keyboard_grab_released = TRUE;
-            try_keyboard_ungrab(display);
-        } else {
-            d->keyboard_grab_released = FALSE;
-            try_keyboard_grab(display);
-        }
-        d->seq_pressed = FALSE;
-    }
-
-    if (!d->inputs)
-        return true;
-
-    scancode = vnc_display_keymap_gdk2xtkbd(d->keycode_map, d->keycode_maplen,
-                                            key->hardware_keycode);
-#ifdef G_OS_WIN32
-    /* MapVirtualKey doesn't return scancode with needed higher byte */
-    scancode = MapVirtualKey(key->hardware_keycode, MAPVK_VK_TO_VSC) |
-        (scancode & 0xff00);
-#endif
-
-    switch (key->type) {
-    case GDK_KEY_PRESS:
-        send_key(display, scancode, SEND_KEY_PRESS, !key->is_modifier);
-        break;
-    case GDK_KEY_RELEASE:
-        send_key(display, scancode, SEND_KEY_RELEASE, !key->is_modifier);
-        break;
-    default:
-        g_warn_if_reached();
-        break;
-    }
-
-    return true;
-}
-
-static guint get_scancode_from_keyval(SpiceDisplay *display, guint keyval)
-{
-    SpiceDisplayPrivate *d = display->priv;
-    guint keycode = 0;
-    GdkKeymapKey *keys = NULL;
-    gint n_keys = 0;
-
-    if (gdk_keymap_get_entries_for_keyval(gdk_keymap_get_default(),
-                                          keyval, &keys, &n_keys)) {
-        /* FIXME what about levels? */
-        keycode = keys[0].keycode;
-        g_free(keys);
-    } else {
-        g_warning("could not lookup keyval %u, please report a bug", keyval);
-        return 0;
-    }
-
-    return vnc_display_keymap_gdk2xtkbd(d->keycode_map, d->keycode_maplen, keycode);
-}
-
-
-/**
- * spice_display_send_keys:
- * @display: The #SpiceDisplay
- * @keyvals: (array length=nkeyvals): Keyval array
- * @nkeyvals: Length of keyvals
- * @kind: #SpiceDisplayKeyEvent action
- *
- * Send keyval press/release events to the display.
- *
- **/
-void spice_display_send_keys(SpiceDisplay *display, const guint *keyvals,
-                             int nkeyvals, SpiceDisplayKeyEvent kind)
-{
-    int i;
-
-    g_return_if_fail(SPICE_IS_DISPLAY(display));
-    g_return_if_fail(keyvals != NULL);
-
-    SPICE_DEBUG("%s", __FUNCTION__);
-
-    if (kind & SPICE_DISPLAY_KEY_EVENT_PRESS) {
-        for (i = 0 ; i < nkeyvals ; i++)
-            send_key(display, get_scancode_from_keyval(display, keyvals[i]), SEND_KEY_PRESS, FALSE);
-    }
-
-    if (kind & SPICE_DISPLAY_KEY_EVENT_RELEASE) {
-        for (i = (nkeyvals-1) ; i >= 0 ; i--)
-            send_key(display, get_scancode_from_keyval(display, keyvals[i]), SEND_KEY_RELEASE, FALSE);
-    }
-}
-
-static gboolean enter_event(GtkWidget *widget, GdkEventCrossing *crossing G_GNUC_UNUSED)
-{
-    SpiceDisplay *display = SPICE_DISPLAY(widget);
-    SpiceDisplayPrivate *d = display->priv;
-
-    SPICE_DEBUG("%s", __FUNCTION__);
-
-    d->mouse_have_pointer = true;
-    try_keyboard_grab(display);
-    update_display(display);
-
-    return true;
-}
-
-static gboolean leave_event(GtkWidget *widget, GdkEventCrossing *crossing G_GNUC_UNUSED)
-{
-    SpiceDisplay *display = SPICE_DISPLAY(widget);
-    SpiceDisplayPrivate *d = display->priv;
-
-    SPICE_DEBUG("%s", __FUNCTION__);
-
-    if (d->mouse_grab_active)
-        return true;
-
-    d->mouse_have_pointer = false;
-    try_keyboard_ungrab(display);
-
-    return true;
-}
-
-static gboolean focus_in_event(GtkWidget *widget, GdkEventFocus *focus G_GNUC_UNUSED)
-{
-    SpiceDisplay *display = SPICE_DISPLAY(widget);
-    SpiceDisplayPrivate *d = display->priv;
-
-    SPICE_DEBUG("%s", __FUNCTION__);
-
-    /*
-     * Ignore focus in when we already have the focus
-     * (this happens when doing an ungrab from the leave_event callback).
-     */
-    if (d->keyboard_have_focus)
-        return true;
-
-    release_keys(display);
-    if (!d->disable_inputs)
-        spice_gtk_session_sync_keyboard_modifiers(d->gtk_session);
-    if (d->keyboard_grab_released)
-        memset(d->activeseq, 0, sizeof(gboolean) * d->grabseq->nkeysyms);
-    update_keyboard_focus(display, true);
-    try_keyboard_grab(display);
-
-    if (gtk_widget_get_realized(widget))
-        update_display(display);
-
-    return true;
-}
-
-static gboolean focus_out_event(GtkWidget *widget, GdkEventFocus *focus G_GNUC_UNUSED)
-{
-    SpiceDisplay *display = SPICE_DISPLAY(widget);
-    SpiceDisplayPrivate *d = display->priv;
-
-    SPICE_DEBUG("%s", __FUNCTION__);
-    update_display(NULL);
-
-    /*
-     * Ignore focus out after a keyboard grab
-     * (this happens when doing the grab from the enter_event callback).
-     */
-    if (d->keyboard_grab_active)
-        return true;
-
-    release_keys(display);
-    update_keyboard_focus(display, false);
-
-    return true;
-}
-
-static int button_gdk_to_spice(int gdk)
-{
-    static const int map[] = {
-        [ 1 ] = SPICE_MOUSE_BUTTON_LEFT,
-        [ 2 ] = SPICE_MOUSE_BUTTON_MIDDLE,
-        [ 3 ] = SPICE_MOUSE_BUTTON_RIGHT,
-        [ 4 ] = SPICE_MOUSE_BUTTON_UP,
-        [ 5 ] = SPICE_MOUSE_BUTTON_DOWN,
-    };
-
-    if (gdk < SPICE_N_ELEMENTS(map)) {
-        return map [ gdk ];
-    }
-    return 0;
-}
-
-static int button_mask_gdk_to_spice(int gdk)
-{
-    int spice = 0;
-
-    if (gdk & GDK_BUTTON1_MASK)
-        spice |= SPICE_MOUSE_BUTTON_MASK_LEFT;
-    if (gdk & GDK_BUTTON2_MASK)
-        spice |= SPICE_MOUSE_BUTTON_MASK_MIDDLE;
-    if (gdk & GDK_BUTTON3_MASK)
-        spice |= SPICE_MOUSE_BUTTON_MASK_RIGHT;
-    return spice;
-}
-
-G_GNUC_INTERNAL
-void spicex_transform_input (SpiceDisplay *display,
-                             double window_x, double window_y,
-                             int *input_x, int *input_y)
-{
-    SpiceDisplayPrivate *d = display->priv;
-    int display_x, display_y, display_w, display_h;
-    double is;
-
-    spice_display_get_scaling(display, NULL,
-                              &display_x, &display_y,
-                              &display_w, &display_h);
-
-    /* For input we need a different scaling factor in order to
-       be able to reach the full width of a display. For instance, consider
-       a display of 100 pixels showing in a window 10 pixels wide. The normal
-       scaling factor here would be 100/10==10, but if you then take the largest
-       possible window coordinate, i.e. 9 and multiply by 10 you get 90, not 99,
-       which is the max display coord.
-
-       If you want to be able to reach the last pixel in the window you need
-       max_window_x * input_scale == max_display_x, which is
-       (window_width - 1) * input_scale == (display_width - 1)
-
-       Note, this is the inverse of s (i.e. s ~= 1/is) as we're converting the
-       coordinates in the inverse direction (window -> display) as the fb size
-       (display -> window).
-    */
-    is = (double)(d->area.width-1) / (double)(display_w-1);
-
-    window_x -= display_x;
-    window_y -= display_y;
-
-    *input_x = floor (window_x * is);
-    *input_y = floor (window_y * is);
-}
-
-static gboolean motion_event(GtkWidget *widget, GdkEventMotion *motion)
-{
-    SpiceDisplay *display = SPICE_DISPLAY(widget);
-    SpiceDisplayPrivate *d = display->priv;
-    int x, y;
-
-    if (!d->inputs)
-        return true;
-    if (d->disable_inputs)
-        return true;
-
-    d->seq_pressed = FALSE;
-
-    if (d->keyboard_grab_released && d->keyboard_have_focus) {
-        d->keyboard_grab_released = FALSE;
-        release_keys(display);
-        try_keyboard_grab(display);
-    }
-
-    spicex_transform_input (display, motion->x, motion->y, &x, &y);
-
-    switch (d->mouse_mode) {
-    case SPICE_MOUSE_MODE_CLIENT:
-        if (x >= 0 && x < d->area.width &&
-            y >= 0 && y < d->area.height) {
-            spice_inputs_position(d->inputs, x, y, get_display_id(display),
-                                  button_mask_gdk_to_spice(motion->state));
-        }
-        break;
-    case SPICE_MOUSE_MODE_SERVER:
-        if (d->mouse_grab_active) {
-            gint dx = d->mouse_last_x != -1 ? x - d->mouse_last_x : 0;
-            gint dy = d->mouse_last_y != -1 ? y - d->mouse_last_y : 0;
-
-            spice_inputs_motion(d->inputs, dx, dy,
-                                button_mask_gdk_to_spice(motion->state));
-
-            d->mouse_last_x = x;
-            d->mouse_last_y = y;
-            if (dx != 0 || dy != 0)
-                mouse_wrap(display, motion);
-        }
-        break;
-    default:
-        g_warn_if_reached();
-        break;
-    }
-    return true;
-}
-
-static gboolean scroll_event(GtkWidget *widget, GdkEventScroll *scroll)
-{
-    int button;
-    SpiceDisplay *display = SPICE_DISPLAY(widget);
-    SpiceDisplayPrivate *d = display->priv;
-
-    SPICE_DEBUG("%s", __FUNCTION__);
-
-    if (!d->inputs)
-        return true;
-    if (d->disable_inputs)
-        return true;
-
-    if (scroll->direction == GDK_SCROLL_UP)
-        button = SPICE_MOUSE_BUTTON_UP;
-    else if (scroll->direction == GDK_SCROLL_DOWN)
-        button = SPICE_MOUSE_BUTTON_DOWN;
-    else {
-        SPICE_DEBUG("unsupported scroll direction");
-        return true;
-    }
-
-    spice_inputs_button_press(d->inputs, button,
-                              button_mask_gdk_to_spice(scroll->state));
-    spice_inputs_button_release(d->inputs, button,
-                                button_mask_gdk_to_spice(scroll->state));
-    return true;
-}
-
-static gboolean button_event(GtkWidget *widget, GdkEventButton *button)
-{
-    SpiceDisplay *display = SPICE_DISPLAY(widget);
-    SpiceDisplayPrivate *d = display->priv;
-    int x, y;
-
-    SPICE_DEBUG("%s %s: button %d, state 0x%x", __FUNCTION__,
-            button->type == GDK_BUTTON_PRESS ? "press" : "release",
-            button->button, button->state);
-
-    if (d->disable_inputs)
-        return true;
-
-    spicex_transform_input (display, button->x, button->y, &x, &y);
-    if ((x < 0 || x >= d->area.width ||
-         y < 0 || y >= d->area.height) &&
-        d->mouse_mode == SPICE_MOUSE_MODE_CLIENT) {
-        /* rule out clicks in outside region */
-        return true;
-    }
-
-    gtk_widget_grab_focus(widget);
-    if (d->mouse_mode == SPICE_MOUSE_MODE_SERVER) {
-        if (!d->mouse_grab_active) {
-            try_mouse_grab(display);
-            return true;
-        }
-    } else
-        /* allow to drag and drop between windows/displays:
-
-           By default, X (and other window system) do a pointer grab
-           when you press a button, so that the release event is
-           received by the same window regardless of where the pointer
-           is. Here, we change that behaviour, so that you can press
-           and release in two differents displays. This is only
-           supported in client mouse mode.
-
-           FIXME: should be multiple widget grab, but how?
-           or should know the position of the other widgets?
-        */
-        gdk_pointer_ungrab(GDK_CURRENT_TIME);
-
-    if (!d->inputs)
-        return true;
-
-    switch (button->type) {
-    case GDK_BUTTON_PRESS:
-        spice_inputs_button_press(d->inputs,
-                                  button_gdk_to_spice(button->button),
-                                  button_mask_gdk_to_spice(button->state));
-        break;
-    case GDK_BUTTON_RELEASE:
-        spice_inputs_button_release(d->inputs,
-                                    button_gdk_to_spice(button->button),
-                                    button_mask_gdk_to_spice(button->state));
-        break;
-    default:
-        break;
-    }
-    return true;
-}
-
-static gboolean configure_event(GtkWidget *widget, GdkEventConfigure *conf)
-{
-    SpiceDisplay *display = SPICE_DISPLAY(widget);
-    SpiceDisplayPrivate *d = display->priv;
-
-    if (conf->width == d->ww && conf->height == d->wh &&
-            conf->x == d->mx && conf->y == d->my) {
-        return true;
-    }
-
-    if (conf->width != d->ww  || conf->height != d->wh) {
-        d->ww = conf->width;
-        d->wh = conf->height;
-        recalc_geometry(widget);
-    }
-
-    d->mx = conf->x;
-    d->my = conf->y;
-
-#ifdef G_OS_WIN32
-    if (d->mouse_grab_active) {
-        try_mouse_ungrab(display);
-        try_mouse_grab(display);
-    }
-#endif
-
-    return true;
-}
-
-static void update_image(SpiceDisplay *display)
-{
-    SpiceDisplayPrivate *d = display->priv;
-
-    spicex_image_create(display);
-    if (d->convert)
-        do_color_convert(display, &d->area);
-}
-
-static void realize(GtkWidget *widget)
-{
-    SpiceDisplay *display = SPICE_DISPLAY(widget);
-    SpiceDisplayPrivate *d = display->priv;
-
-    GTK_WIDGET_CLASS(spice_display_parent_class)->realize(widget);
-
-    d->keycode_map =
-        vnc_display_keymap_gdk2xtkbd_table(gtk_widget_get_window(widget),
-                                           &d->keycode_maplen);
-    update_image(display);
-}
-
-static void unrealize(GtkWidget *widget)
-{
-    spicex_image_destroy(SPICE_DISPLAY(widget));
-
-    GTK_WIDGET_CLASS(spice_display_parent_class)->unrealize(widget);
-}
-
-
-/* ---------------------------------------------------------------- */
-
-static void spice_display_class_init(SpiceDisplayClass *klass)
-{
-    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
-    GtkWidgetClass *gtkwidget_class = GTK_WIDGET_CLASS(klass);
-
-#if GTK_CHECK_VERSION (2, 91, 0)
-    gtkwidget_class->draw = draw_event;
-#else
-    gtkwidget_class->expose_event = expose_event;
-#endif
-    gtkwidget_class->key_press_event = key_event;
-    gtkwidget_class->key_release_event = key_event;
-    gtkwidget_class->enter_notify_event = enter_event;
-    gtkwidget_class->leave_notify_event = leave_event;
-    gtkwidget_class->focus_in_event = focus_in_event;
-    gtkwidget_class->focus_out_event = focus_out_event;
-    gtkwidget_class->motion_notify_event = motion_event;
-    gtkwidget_class->button_press_event = button_event;
-    gtkwidget_class->button_release_event = button_event;
-    gtkwidget_class->configure_event = configure_event;
-    gtkwidget_class->scroll_event = scroll_event;
-    gtkwidget_class->realize = realize;
-    gtkwidget_class->unrealize = unrealize;
-
-    gobject_class->constructor = spice_display_constructor;
-    gobject_class->dispose = spice_display_dispose;
-    gobject_class->finalize = spice_display_finalize;
-    gobject_class->get_property = spice_display_get_property;
-    gobject_class->set_property = spice_display_set_property;
-
-    /**
-     * SpiceDisplay:session:
-     *
-     * #SpiceSession for this #SpiceDisplay
-     *
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_SESSION,
-         g_param_spec_object("session",
-                             "Session",
-                             "SpiceSession",
-                             SPICE_TYPE_SESSION,
-                             G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
-                             G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceDisplay:channel-id:
-     *
-     * channel-id for this #SpiceDisplay
-     *
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_CHANNEL_ID,
-         g_param_spec_int("channel-id",
-                          "Channel ID",
-                          "Channel ID for this display",
-                          0, 255, 0,
-                          G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
-                          G_PARAM_STATIC_STRINGS));
-
-    g_object_class_install_property
-        (gobject_class, PROP_KEYBOARD_GRAB,
-         g_param_spec_boolean("grab-keyboard",
-                              "Grab Keyboard",
-                              "Whether we should grab the keyboard.",
-                              TRUE,
-                              G_PARAM_READWRITE |
-                              G_PARAM_CONSTRUCT |
-                              G_PARAM_STATIC_STRINGS));
-
-    g_object_class_install_property
-        (gobject_class, PROP_MOUSE_GRAB,
-         g_param_spec_boolean("grab-mouse",
-                              "Grab Mouse",
-                              "Whether we should grab the mouse.",
-                              TRUE,
-                              G_PARAM_READWRITE |
-                              G_PARAM_CONSTRUCT |
-                              G_PARAM_STATIC_STRINGS));
-
-    g_object_class_install_property
-        (gobject_class, PROP_RESIZE_GUEST,
-         g_param_spec_boolean("resize-guest",
-                              "Resize guest",
-                              "Try to adapt guest display on window resize. "
-                              "Requires guest cooperation.",
-                              FALSE,
-                              G_PARAM_READWRITE |
-                              G_PARAM_CONSTRUCT |
-                              G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceDisplay:ready:
-     *
-     * Indicate whether the display is ready to be shown. It takes
-     * into account several conditions, such as the channel display
-     * "mark" state, whether the monitor area is visible..
-     *
-     * Since: 0.13
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_READY,
-         g_param_spec_boolean("ready",
-                              "Ready",
-                              "Ready to display",
-                              FALSE,
-                              G_PARAM_READABLE |
-                              G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceDisplay:auto-clipboard:
-     *
-     * When this is true the clipboard gets automatically shared between host
-     * and guest.
-     *
-     * Deprecated: 0.8: Use SpiceGtkSession:auto-clipboard property instead
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_AUTO_CLIPBOARD,
-         g_param_spec_boolean("auto-clipboard",
-                              "Auto clipboard",
-                              "Automatically relay clipboard changes between "
-                              "host and guest.",
-                              TRUE,
-                              G_PARAM_READWRITE |
-                              G_PARAM_STATIC_STRINGS |
-                              G_PARAM_DEPRECATED));
-
-    g_object_class_install_property
-        (gobject_class, PROP_SCALING,
-         g_param_spec_boolean("scaling", "Scaling",
-                              "Whether we should use scaling",
-                              TRUE,
-                              G_PARAM_READWRITE |
-                              G_PARAM_CONSTRUCT |
-                              G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceDisplay:only-downscale:
-     *
-     * If scaling, only scale down, never up.
-     *
-     * Since: 0.14
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_ONLY_DOWNSCALE,
-         g_param_spec_boolean("only-downscale", "Only Downscale",
-                              "If scaling, only scale down, never up",
-                              FALSE,
-                              G_PARAM_READWRITE |
-                              G_PARAM_CONSTRUCT |
-                              G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceDisplay:keypress-delay:
-     *
-     * Delay in ms of non-modifiers key press events. If the key is
-     * released before this delay, a single press & release event is
-     * sent to the server. If the key is pressed longer than the
-     * keypress-delay, the server will receive the delayed press
-     * event, and a following release event when the key is released.
-     *
-     * Since: 0.13
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_KEYPRESS_DELAY,
-         g_param_spec_uint("keypress-delay", "Keypress delay",
-                           "Keypress delay",
-                           0, G_MAXUINT, 100,
-                           G_PARAM_READWRITE |
-                           G_PARAM_CONSTRUCT |
-                           G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceDisplay:disable-inputs:
-     *
-     * Disable all keyboard & mouse inputs.
-     *
-     * Since: 0.8
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_DISABLE_INPUTS,
-         g_param_spec_boolean("disable-inputs", "Disable inputs",
-                              "Whether inputs should be disabled",
-                              FALSE,
-                              G_PARAM_READWRITE |
-                              G_PARAM_CONSTRUCT |
-                              G_PARAM_STATIC_STRINGS));
-
-
-    /**
-     * SpiceDisplay:zoom-level:
-     *
-     * Zoom level in percentage, from 10 to 400. Default to 100.
-     * (this option is only supported with cairo backend when scaling
-     * is enabled)
-     *
-     * Since: 0.10
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_ZOOM_LEVEL,
-         g_param_spec_int("zoom-level", "Zoom Level",
-                          "Zoom Level",
-                          10, 400, 100,
-                          G_PARAM_READWRITE |
-                          G_PARAM_CONSTRUCT |
-                          G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceDisplay:monitor-id:
-     *
-     * Select monitor from #SpiceDisplay to show.
-     * The value -1 means the whole display is shown.
-     * By default, the monitor 0 is selected.
-     *
-     * Since: 0.13
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_MONITOR_ID,
-         g_param_spec_int("monitor-id",
-                          "Monitor ID",
-                          "Select monitor ID",
-                          -1, G_MAXINT, 0,
-                          G_PARAM_READWRITE |
-                          G_PARAM_CONSTRUCT |
-                          G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceDisplay::mouse-grab:
-     * @display: the #SpiceDisplay that emitted the signal
-     * @status: 1 if grabbed, 0 otherwise.
-     *
-     * Notify when the mouse grab is active or not.
-     **/
-    signals[SPICE_DISPLAY_MOUSE_GRAB] =
-        g_signal_new("mouse-grab",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_FIRST,
-                     G_STRUCT_OFFSET(SpiceDisplayClass, mouse_grab),
-                     NULL, NULL,
-                     g_cclosure_marshal_VOID__INT,
-                     G_TYPE_NONE,
-                     1,
-                     G_TYPE_INT);
-
-    /**
-     * SpiceDisplay::keyboard-grab:
-     * @display: the #SpiceDisplay that emitted the signal
-     * @status: 1 if grabbed, 0 otherwise.
-     *
-     * Notify when the keyboard grab is active or not.
-     **/
-    signals[SPICE_DISPLAY_KEYBOARD_GRAB] =
-        g_signal_new("keyboard-grab",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_FIRST,
-                     G_STRUCT_OFFSET(SpiceDisplayClass, keyboard_grab),
-                     NULL, NULL,
-                     g_cclosure_marshal_VOID__INT,
-                     G_TYPE_NONE,
-                     1,
-                     G_TYPE_INT);
-
-    /**
-     * SpiceDisplay::grab-keys-pressed:
-     * @display: the #SpiceDisplay that emitted the signal
-     *
-     * Notify when the grab keys have been pressed
-     **/
-    signals[SPICE_DISPLAY_GRAB_KEY_PRESSED] =
-        g_signal_new("grab-keys-pressed",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_FIRST,
-                     G_STRUCT_OFFSET(SpiceDisplayClass, keyboard_grab),
-                     NULL, NULL,
-                     g_cclosure_marshal_VOID__VOID,
-                     G_TYPE_NONE,
-                     0);
-
-    g_type_class_add_private(klass, sizeof(SpiceDisplayPrivate));
-}
-
-/* ---------------------------------------------------------------- */
-
-#define SPICE_GDK_BUTTONS_MASK \
-    (GDK_BUTTON1_MASK|GDK_BUTTON2_MASK|GDK_BUTTON3_MASK|GDK_BUTTON4_MASK|GDK_BUTTON5_MASK)
-
-static void update_mouse_mode(SpiceChannel *channel, gpointer data)
-{
-    SpiceDisplay *display = data;
-    SpiceDisplayPrivate *d = display->priv;
-    GdkWindow *window = gtk_widget_get_window(GTK_WIDGET(display));
-
-    g_object_get(channel, "mouse-mode", &d->mouse_mode, NULL);
-    SPICE_DEBUG("mouse mode %d", d->mouse_mode);
-
-    switch (d->mouse_mode) {
-    case SPICE_MOUSE_MODE_CLIENT:
-        try_mouse_ungrab(display);
-        break;
-    case SPICE_MOUSE_MODE_SERVER:
-        d->mouse_guest_x = -1;
-        d->mouse_guest_y = -1;
-
-        if (window != NULL) {
-            GdkModifierType modifiers;
-            gdk_window_get_pointer(window, NULL, NULL, &modifiers);
-
-            if (modifiers & SPICE_GDK_BUTTONS_MASK)
-                try_mouse_grab(display);
-        }
-        break;
-    default:
-        g_warn_if_reached();
-    }
-
-    update_mouse_pointer(display);
-}
-
-static void update_area(SpiceDisplay *display,
-                        gint x, gint y, gint width, gint height)
-{
-    SpiceDisplayPrivate *d = display->priv;
-    GdkRectangle primary = {
-        .x = 0,
-        .y = 0,
-        .width = d->width,
-        .height = d->height
-    };
-    GdkRectangle area = {
-        .x = x,
-        .y = y,
-        .width = width,
-        .height = height
-    };
-
-    SPICE_DEBUG("update area, primary: %dx%d, area: +%d+%d %dx%d", d->width, d->height, area.x, area.y, area.width, area.height);
-
-    if (!gdk_rectangle_intersect(&primary, &area, &area)) {
-        SPICE_DEBUG("The monitor area is not intersecting primary surface");
-        memset(&d->area, '\0', sizeof(d->area));
-        set_monitor_ready(display, false);
-        return;
-    }
-
-    spicex_image_destroy(display);
-    d->area = area;
-    if (gtk_widget_get_realized(GTK_WIDGET(display)))
-        update_image(display);
-
-    update_size_request(display);
-
-    set_monitor_ready(display, true);
-}
-
-static void primary_create(SpiceChannel *channel, gint format,
-                           gint width, gint height, gint stride,
-                           gint shmid, gpointer imgdata, gpointer data)
-{
-    SpiceDisplay *display = data;
-    SpiceDisplayPrivate *d = display->priv;
-
-    d->format = format;
-    d->stride = stride;
-    d->shmid = shmid;
-    d->width = width;
-    d->height = height;
-    d->data_origin = d->data = imgdata;
-
-    update_monitor_area(display);
-}
-
-static void primary_destroy(SpiceChannel *channel, gpointer data)
-{
-    SpiceDisplay *display = SPICE_DISPLAY(data);
-    SpiceDisplayPrivate *d = display->priv;
-
-    spicex_image_destroy(display);
-    d->width  = 0;
-    d->height = 0;
-    d->stride = 0;
-    d->shmid  = 0;
-    d->data = NULL;
-    d->data_origin = NULL;
-    set_monitor_ready(display, false);
-}
-
-static void invalidate(SpiceChannel *channel,
-                       gint x, gint y, gint w, gint h, gpointer data)
-{
-    SpiceDisplay *display = data;
-    SpiceDisplayPrivate *d = display->priv;
-    int display_x, display_y;
-    int x1, y1, x2, y2;
-    double s;
-    GdkRectangle rect = {
-        .x = x,
-        .y = y,
-        .width = w,
-        .height = h
-    };
-
-    if (!gtk_widget_get_window(GTK_WIDGET(display)))
-        return;
-
-    if (!gdk_rectangle_intersect(&rect, &d->area, &rect))
-        return;
-
-    if (d->convert)
-        do_color_convert(display, &rect);
-
-    spice_display_get_scaling(display, &s,
-                              &display_x, &display_y,
-                              NULL, NULL);
-
-    x1 = floor ((rect.x - d->area.x) * s);
-    y1 = floor ((rect.y - d->area.y) * s);
-    x2 = ceil ((rect.x - d->area.x + rect.width) * s);
-    y2 = ceil ((rect.y - d->area.y + rect.height) * s);
-
-    gtk_widget_queue_draw_area(GTK_WIDGET(display),
-                               display_x + x1, display_y + y1,
-                               x2 - x1, y2-y1);
-}
-
-static void mark(SpiceDisplay *display, gint mark)
-{
-    SpiceDisplayPrivate *d = display->priv;
-    g_return_if_fail(d != NULL);
-
-    SPICE_DEBUG("widget mark: %d, %d:%d %p", mark, d->channel_id, d->monitor_id, display);
-    d->mark = mark;
-    update_ready(display);
-}
-
-static void cursor_set(SpiceCursorChannel *channel,
-                       gint width, gint height, gint hot_x, gint hot_y,
-                       gpointer rgba, gpointer data)
-{
-    SpiceDisplay *display = data;
-    SpiceDisplayPrivate *d = display->priv;
-    GdkCursor *cursor = NULL;
-
-    cursor_invalidate(display);
-
-    if (d->mouse_pixbuf) {
-        g_object_unref(d->mouse_pixbuf);
-        d->mouse_pixbuf = NULL;
-    }
-
-    if (rgba != NULL) {
-        d->mouse_pixbuf = gdk_pixbuf_new_from_data(g_memdup(rgba, width * height * 4),
-                                                   GDK_COLORSPACE_RGB,
-                                                   TRUE, 8,
-                                                   width,
-                                                   height,
-                                                   width * 4,
-                                                   (GdkPixbufDestroyNotify)g_free, NULL);
-        d->mouse_hotspot.x = hot_x;
-        d->mouse_hotspot.y = hot_y;
-        cursor = gdk_cursor_new_from_pixbuf(gtk_widget_get_display(GTK_WIDGET(display)),
-                                            d->mouse_pixbuf, hot_x, hot_y);
-    } else
-        g_warn_if_reached();
-
-    if (d->show_cursor) {
-        /* unhide */
-        gdk_cursor_unref(d->show_cursor);
-        d->show_cursor = NULL;
-        if (d->mouse_mode == SPICE_MOUSE_MODE_SERVER) {
-            /* keep a hidden cursor, will be shown in cursor_move() */
-            d->show_cursor = cursor;
-            return;
-        }
-    }
-
-    gdk_cursor_unref(d->mouse_cursor);
-    d->mouse_cursor = cursor;
-
-    update_mouse_pointer(display);
-    cursor_invalidate(display);
-}
-
-static void cursor_hide(SpiceCursorChannel *channel, gpointer data)
-{
-    SpiceDisplay *display = data;
-    SpiceDisplayPrivate *d = display->priv;
-
-    if (d->show_cursor != NULL) /* then we are already hidden */
-        return;
-
-    cursor_invalidate(display);
-    d->show_cursor = d->mouse_cursor;
-    d->mouse_cursor = get_blank_cursor();
-    update_mouse_pointer(display);
-}
-
-G_GNUC_INTERNAL
-void spice_display_get_scaling(SpiceDisplay *display,
-                               double *s_out,
-                               int *x_out, int *y_out,
-                               int *w_out, int *h_out)
-{
-    SpiceDisplayPrivate *d = display->priv;
-    int fbw = d->area.width, fbh = d->area.height;
-    int ww, wh;
-    int x, y, w, h;
-    double s;
-
-    if (gtk_widget_get_realized (GTK_WIDGET(display)))
-        gdk_drawable_get_size(gtk_widget_get_window(GTK_WIDGET(display)), &ww, &wh);
-    else {
-        ww = fbw;
-        wh = fbh;
-    }
-
-    if (!spicex_is_scaled(display)) {
-        s = 1.0;
-        x = 0;
-        y = 0;
-        if (ww > d->area.width)
-            x = (ww - d->area.width) / 2;
-        if (wh > d->area.height)
-            y = (wh - d->area.height) / 2;
-        w = fbw;
-        h = fbh;
-    } else {
-        s = MIN ((double)ww / (double)fbw, (double)wh / (double)fbh);
-
-        if (d->only_downscale && s >= 1.0)
-            s = 1.0;
-
-        /* Round to int size */
-        w = floor (fbw * s + 0.5);
-        h = floor (fbh * s + 0.5);
-
-        /* Center the display */
-        x = (ww - w) / 2;
-        y = (wh - h) / 2;
-    }
-
-    if (s_out)
-        *s_out = s;
-    if (w_out)
-        *w_out = w;
-    if (h_out)
-        *h_out = h;
-    if (x_out)
-        *x_out = x;
-    if (y_out)
-        *y_out = y;
-}
-
-static void cursor_invalidate(SpiceDisplay *display)
-{
-    SpiceDisplayPrivate *d = display->priv;
-    double s;
-    int x, y;
-
-    if (!gtk_widget_get_realized (GTK_WIDGET(display)))
-        return;
-
-    if (d->mouse_pixbuf == NULL)
-        return;
-
-    if (!d->ready || !d->monitor_ready)
-        return;
-
-    spice_display_get_scaling(display, &s, &x, &y, NULL, NULL);
-
-    gtk_widget_queue_draw_area(GTK_WIDGET(display),
-                               floor ((d->mouse_guest_x - d->mouse_hotspot.x - d->area.x) * s) + x,
-                               floor ((d->mouse_guest_y - d->mouse_hotspot.y - d->area.y) * s) + y,
-                               ceil (gdk_pixbuf_get_width(d->mouse_pixbuf) * s),
-                               ceil (gdk_pixbuf_get_height(d->mouse_pixbuf) * s));
-}
-
-static void cursor_move(SpiceCursorChannel *channel, gint x, gint y, gpointer data)
-{
-    SpiceDisplay *display = data;
-    SpiceDisplayPrivate *d = display->priv;
-
-    cursor_invalidate(display);
-
-    d->mouse_guest_x = x;
-    d->mouse_guest_y = y;
-
-    cursor_invalidate(display);
-
-    /* apparently we have to restore cursor when "cursor_move" */
-    if (d->show_cursor != NULL) {
-        gdk_cursor_unref(d->mouse_cursor);
-        d->mouse_cursor = d->show_cursor;
-        d->show_cursor = NULL;
-        update_mouse_pointer(display);
-    }
-}
-
-static void cursor_reset(SpiceCursorChannel *channel, gpointer data)
-{
-    SpiceDisplay *display = data;
-    GdkWindow *window = gtk_widget_get_window(GTK_WIDGET(display));
-
-    if (!window) {
-        SPICE_DEBUG("%s: no window, returning",  __FUNCTION__);
-        return;
-    }
-
-    SPICE_DEBUG("%s",  __FUNCTION__);
-    gdk_window_set_cursor(window, NULL);
-}
-
-static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data)
-{
-    SpiceDisplay *display = data;
-    SpiceDisplayPrivate *d = display->priv;
-    int id;
-
-    g_object_get(channel, "channel-id", &id, NULL);
-    if (SPICE_IS_MAIN_CHANNEL(channel)) {
-        d->main = SPICE_MAIN_CHANNEL(channel);
-        spice_g_signal_connect_object(channel, "main-mouse-update",
-                                      G_CALLBACK(update_mouse_mode), display, 0);
-        update_mouse_mode(channel, display);
-        return;
-    }
-
-    if (SPICE_IS_DISPLAY_CHANNEL(channel)) {
-        SpiceDisplayPrimary primary;
-        if (id != d->channel_id)
-            return;
-        d->display = channel;
-        spice_g_signal_connect_object(channel, "display-primary-create",
-                                      G_CALLBACK(primary_create), display, 0);
-        spice_g_signal_connect_object(channel, "display-primary-destroy",
-                                      G_CALLBACK(primary_destroy), display, 0);
-        spice_g_signal_connect_object(channel, "display-invalidate",
-                                      G_CALLBACK(invalidate), display, 0);
-        spice_g_signal_connect_object(channel, "display-mark",
-                                      G_CALLBACK(mark), display, G_CONNECT_AFTER | G_CONNECT_SWAPPED);
-        spice_g_signal_connect_object(channel, "notify::monitors",
-                                      G_CALLBACK(update_monitor_area), display, G_CONNECT_AFTER | G_CONNECT_SWAPPED);
-        if (spice_display_get_primary(channel, 0, &primary)) {
-            primary_create(channel, primary.format, primary.width, primary.height,
-                           primary.stride, primary.shmid, primary.data, display);
-            mark(display, primary.marked);
-        }
-        spice_channel_connect(channel);
-        spice_main_set_display_enabled(d->main, get_display_id(display), TRUE);
-        return;
-    }
-
-    if (SPICE_IS_CURSOR_CHANNEL(channel)) {
-        if (id != d->channel_id)
-            return;
-        d->cursor = SPICE_CURSOR_CHANNEL(channel);
-        spice_g_signal_connect_object(channel, "cursor-set",
-                                      G_CALLBACK(cursor_set), display, 0);
-        spice_g_signal_connect_object(channel, "cursor-move",
-                                      G_CALLBACK(cursor_move), display, 0);
-        spice_g_signal_connect_object(channel, "cursor-hide",
-                                      G_CALLBACK(cursor_hide), display, 0);
-        spice_g_signal_connect_object(channel, "cursor-reset",
-                                      G_CALLBACK(cursor_reset), display, 0);
-        spice_channel_connect(channel);
-        return;
-    }
-
-    if (SPICE_IS_INPUTS_CHANNEL(channel)) {
-        d->inputs = SPICE_INPUTS_CHANNEL(channel);
-        spice_channel_connect(channel);
-        return;
-    }
-
-#ifdef USE_SMARTCARD
-    if (SPICE_IS_SMARTCARD_CHANNEL(channel)) {
-        d->smartcard = SPICE_SMARTCARD_CHANNEL(channel);
-        spice_channel_connect(channel);
-        return;
-    }
-#endif
-
-    return;
-}
-
-static void channel_destroy(SpiceSession *s, SpiceChannel *channel, gpointer data)
-{
-    SpiceDisplay *display = data;
-    SpiceDisplayPrivate *d = display->priv;
-    int id;
-
-    g_object_get(channel, "channel-id", &id, NULL);
-    SPICE_DEBUG("channel_destroy %d", id);
-
-    if (SPICE_IS_MAIN_CHANNEL(channel)) {
-        d->main = NULL;
-        return;
-    }
-
-    if (SPICE_IS_DISPLAY_CHANNEL(channel)) {
-        if (id != d->channel_id)
-            return;
-        primary_destroy(d->display, display);
-        d->display = NULL;
-        return;
-    }
-
-    if (SPICE_IS_CURSOR_CHANNEL(channel)) {
-        if (id != d->channel_id)
-            return;
-        d->cursor = NULL;
-        return;
-    }
-
-    if (SPICE_IS_INPUTS_CHANNEL(channel)) {
-        d->inputs = NULL;
-        return;
-    }
-
-#ifdef USE_SMARTCARD
-    if (SPICE_IS_SMARTCARD_CHANNEL(channel)) {
-        d->smartcard = NULL;
-        return;
-    }
-#endif
-
-    return;
-}
-
-/**
- * spice_display_new:
- * @session: a #SpiceSession
- * @channel_id: the display channel ID to associate with #SpiceDisplay
- *
- * Returns: a new #SpiceDisplay widget.
- **/
-SpiceDisplay *spice_display_new(SpiceSession *session, int id)
-{
-    return g_object_new(SPICE_TYPE_DISPLAY, "session", session,
-                        "channel-id", id, NULL);
-}
-
-/**
- * spice_display_new_with_monitor:
- * @session: a #SpiceSession
- * @channel_id: the display channel ID to associate with #SpiceDisplay
- * @monitor_id: the monitor id within the display channel
- *
- * Since: 0.13
- * Returns: a new #SpiceDisplay widget.
- **/
-SpiceDisplay* spice_display_new_with_monitor(SpiceSession *session, gint channel_id, gint monitor_id)
-{
-    return g_object_new(SPICE_TYPE_DISPLAY,
-                        "session", session,
-                        "channel-id", channel_id,
-                        "monitor-id", monitor_id,
-                        NULL);
-}
-
-/**
- * spice_display_mouse_ungrab:
- * @display:
- *
- * Ungrab the mouse.
- **/
-void spice_display_mouse_ungrab(SpiceDisplay *display)
-{
-    g_return_if_fail(SPICE_IS_DISPLAY(display));
-
-    try_mouse_ungrab(display);
-}
-
-/**
- * spice_display_copy_to_guest:
- * @display:
- *
- * Copy client-side clipboard to guest clipboard.
- *
- * Deprecated: 0.8: Use spice_gtk_session_copy_to_guest() instead
- **/
-void spice_display_copy_to_guest(SpiceDisplay *display)
-{
-    SpiceDisplayPrivate *d;
-
-    g_return_if_fail(SPICE_IS_DISPLAY(display));
-
-    d = display->priv;
-
-    g_return_if_fail(d->gtk_session != NULL);
-
-    spice_gtk_session_copy_to_guest(d->gtk_session);
-}
-
-/**
- * spice_display_paste_from_guest:
- * @display:
- *
- * Copy guest clipboard to client-side clipboard.
- *
- * Deprecated: 0.8: Use spice_gtk_session_paste_from_guest() instead
- **/
-void spice_display_paste_from_guest(SpiceDisplay *display)
-{
-    SpiceDisplayPrivate *d;
-
-    g_return_if_fail(SPICE_IS_DISPLAY(display));
-
-    d = display->priv;
-
-    g_return_if_fail(d->gtk_session != NULL);
-
-    spice_gtk_session_paste_from_guest(d->gtk_session);
-}
-
-/**
- * spice_display_get_pixbuf:
- * @display:
- *
- * Take a screenshot of the display.
- *
- * Returns: (transfer full): a #GdkPixbuf with the screenshot image buffer
- **/
-GdkPixbuf *spice_display_get_pixbuf(SpiceDisplay *display)
-{
-    SpiceDisplayPrivate *d;
-    GdkPixbuf *pixbuf;
-    int x, y;
-    guchar *src, *data, *dest;
-
-    g_return_val_if_fail(SPICE_IS_DISPLAY(display), NULL);
-
-    d = display->priv;
-
-    g_return_val_if_fail(d != NULL, NULL);
-    /* TODO: ensure d->data has been exposed? */
-    g_return_val_if_fail(d->data != NULL, NULL);
-
-    data = g_malloc0(d->area.width * d->area.height * 3);
-    src = d->data;
-    dest = data;
-
-    src += d->area.y * d->stride + d->area.x * 4;
-    for (y = 0; y < d->area.height; ++y) {
-        for (x = 0; x < d->area.width; ++x) {
-          dest[0] = src[x * 4 + 2];
-          dest[1] = src[x * 4 + 1];
-          dest[2] = src[x * 4 + 0];
-          dest += 3;
-        }
-        src += d->stride;
-    }
-
-    pixbuf = gdk_pixbuf_new_from_data(data, GDK_COLORSPACE_RGB, false,
-                                      8, d->area.width, d->area.height, d->area.width * 3,
-                                      (GdkPixbufDestroyNotify)g_free, NULL);
-    return pixbuf;
-}
diff --git a/gtk/spice-widget.h b/gtk/spice-widget.h
deleted file mode 100644
index d239ed2..0000000
--- a/gtk/spice-widget.h
+++ /dev/null
@@ -1,92 +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_CLIENT_WIDGET_H__
-#define __SPICE_CLIENT_WIDGET_H__
-
-#include "spice-client.h"
-
-#include <gtk/gtk.h>
-#include "spice-grabsequence.h"
-#include "spice-widget-enums.h"
-#include "spice-util.h"
-#include "spice-gtk-session.h"
-
-G_BEGIN_DECLS
-
-#define SPICE_TYPE_DISPLAY            (spice_display_get_type())
-#define SPICE_DISPLAY(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), SPICE_TYPE_DISPLAY, SpiceDisplay))
-#define SPICE_DISPLAY_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), SPICE_TYPE_DISPLAY, SpiceDisplayClass))
-#define SPICE_IS_DISPLAY(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), SPICE_TYPE_DISPLAY))
-#define SPICE_IS_DISPLAY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SPICE_TYPE_DISPLAY))
-#define SPICE_DISPLAY_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), SPICE_TYPE_DISPLAY, SpiceDisplayClass))
-
-
-typedef struct _SpiceDisplay SpiceDisplay;
-typedef struct _SpiceDisplayClass SpiceDisplayClass;
-typedef struct _SpiceDisplayPrivate SpiceDisplayPrivate;
-
-struct _SpiceDisplay {
-    GtkDrawingArea parent;
-    SpiceDisplayPrivate *priv;
-    /* Do not add fields to this struct */
-};
-
-struct _SpiceDisplayClass {
-    GtkDrawingAreaClass parent_class;
-
-    /* signals */
-    void (*mouse_grab)(SpiceChannel *channel, gint grabbed);
-    void (*keyboard_grab)(SpiceChannel *channel, gint grabbed);
-
-    /*< private >*/
-    /*
-     * If adding fields to this struct, remove corresponding
-     * amount of padding to avoid changing overall struct size
-     */
-    gchar _spice_reserved[SPICE_RESERVED_PADDING];
-};
-
-typedef enum
-{
-	SPICE_DISPLAY_KEY_EVENT_PRESS = 1,
-	SPICE_DISPLAY_KEY_EVENT_RELEASE = 2,
-	SPICE_DISPLAY_KEY_EVENT_CLICK = 3,
-} SpiceDisplayKeyEvent;
-
-GType	        spice_display_get_type(void);
-
-SpiceDisplay* spice_display_new(SpiceSession *session, int channel_id);
-SpiceDisplay* spice_display_new_with_monitor(SpiceSession *session, gint channel_id, gint monitor_id);
-
-void spice_display_mouse_ungrab(SpiceDisplay *display);
-void spice_display_set_grab_keys(SpiceDisplay *display, SpiceGrabSequence *seq);
-SpiceGrabSequence *spice_display_get_grab_keys(SpiceDisplay *display);
-void spice_display_send_keys(SpiceDisplay *display, const guint *keyvals,
-                             int nkeyvals, SpiceDisplayKeyEvent kind);
-GdkPixbuf *spice_display_get_pixbuf(SpiceDisplay *display);
-
-#ifndef SPICE_DISABLE_DEPRECATED
-SPICE_DEPRECATED_FOR(spice_gtk_session_copy_to_guest)
-void spice_display_copy_to_guest(SpiceDisplay *display);
-SPICE_DEPRECATED_FOR(spice_gtk_session_paste_from_guest)
-void spice_display_paste_from_guest(SpiceDisplay *display);
-#endif
-
-G_END_DECLS
-
-#endif /* __SPICE_CLIENT_WIDGET_H__ */
diff --git a/gtk/spicy-screenshot.c b/gtk/spicy-screenshot.c
deleted file mode 100644
index e7835bf..0000000
--- a/gtk/spicy-screenshot.c
+++ /dev/null
@@ -1,196 +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.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) {
-        fputc(p[2], fp);
-        fputc(p[1], fp);
-        fputc(p[0], fp);
-        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 %d\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: %d", 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      = N_("Output file name (default spicy-screenshot.ppm)"),
-        .arg_description  = N_("<filename>"),
-    },
-    {
-        .long_name        = "version",
-        .arg              = G_OPTION_ARG_NONE,
-        .arg_data         = &version,
-        .description      = N_("Display version and quit"),
-    },
-    {
-        /* end of list */
-    }
-};
-
-int main(int argc, char *argv[])
-{
-    GError *error = NULL;
-    GOptionContext *context;
-
-    bindtextdomain(GETTEXT_PACKAGE, SPICE_GTK_LOCALEDIR);
-    bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
-    textdomain(GETTEXT_PACKAGE);
-
-    /* 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);
-    }
-
-#if !GLIB_CHECK_VERSION(2,36,0)
-    g_type_init();
-#endif
-    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/gtk/spicy-stats.c b/gtk/spicy-stats.c
deleted file mode 100644
index c98148d..0000000
--- a/gtk/spicy-stats.c
+++ /dev/null
@@ -1,144 +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.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: %d", 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      = N_("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);
-
-    bindtextdomain(GETTEXT_PACKAGE, SPICE_GTK_LOCALEDIR);
-    bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
-    textdomain(GETTEXT_PACKAGE);
-
-    /* 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);
-    }
-
-#if !GLIB_CHECK_VERSION(2,36,0)
-    g_type_init();
-#endif
-    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/gtk/spicy.c b/gtk/spicy.c
deleted file mode 100644
index 9cd6ee5..0000000
--- a/gtk/spicy.c
+++ /dev/null
@@ -1,1855 +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/gi18n.h>
-
-#include <sys/stat.h>
-#ifdef HAVE_TERMIOS_H
-#include <termios.h>
-#endif
-
-#ifdef USE_SMARTCARD
-#include <vreader.h>
-#include "smartcard-manager.h"
-#endif
-
-#include "glib-compat.h"
-#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"
-
-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;
-};
-
-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;
-};
-
-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,
-                                         GTK_STOCK_OK,
-                                         GTK_RESPONSE_ACCEPT,
-                                         GTK_STOCK_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 struct {
-    const char *text;
-    const char *prop;
-    GtkWidget *entry;
-} connect_entries[] = {
-    { .text = N_("Hostname"),   .prop = "host"      },
-    { .text = N_("Port"),       .prop = "port"      },
-    { .text = N_("TLS Port"),   .prop = "tls-port"  },
-};
-
-#ifndef G_OS_WIN32
-static void recent_selection_changed_dialog_cb(GtkRecentChooser *chooser, gpointer data)
-{
-    GtkRecentInfo *info;
-    gchar *txt = NULL;
-    const gchar *uri;
-    SpiceSession *session = data;
-
-    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);
-
-    g_object_get(session, "host", &txt, NULL);
-    gtk_entry_set_text(GTK_ENTRY(connect_entries[0].entry), txt ? txt : "");
-    g_free(txt);
-
-    g_object_get(session, "port", &txt, NULL);
-    gtk_entry_set_text(GTK_ENTRY(connect_entries[1].entry), txt ? txt : "");
-    g_free(txt);
-
-    g_object_get(session, "tls-port", &txt, NULL);
-    gtk_entry_set_text(GTK_ENTRY(connect_entries[2].entry), txt ? txt : "");
-    g_free(txt);
-
-    gtk_recent_info_unref(info);
-}
-
-static void recent_item_activated_dialog_cb(GtkRecentChooser *chooser, gpointer data)
-{
-   gtk_dialog_response (GTK_DIALOG (data), GTK_RESPONSE_ACCEPT);
-}
-#endif
-
-static int connect_dialog(SpiceSession *session)
-{
-    GtkWidget *dialog, *area, *label;
-    GtkTable *table;
-    int i, retval;
-
-    /* Create the widgets */
-    dialog = gtk_dialog_new_with_buttons(_("Connect to SPICE"),
-                                         NULL,
-                                         GTK_DIALOG_DESTROY_WITH_PARENT,
-                                         GTK_STOCK_CANCEL,
-                                         GTK_RESPONSE_REJECT,
-                                         GTK_STOCK_CONNECT,
-                                         GTK_RESPONSE_ACCEPT,
-                                         NULL);
-    gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
-    area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
-    table = GTK_TABLE(gtk_table_new(3, 2, 0));
-    gtk_box_pack_start(GTK_BOX(area), GTK_WIDGET(table), TRUE, TRUE, 0);
-    gtk_table_set_row_spacings(table, 5);
-    gtk_table_set_col_spacings(table, 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_table_attach_defaults(table, label, 0, 1, i, i+1);
-        connect_entries[i].entry = GTK_WIDGET(gtk_entry_new());
-        gtk_table_attach_defaults(table, connect_entries[i].entry, 1, 2, i, i+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);
-        }
-    }
-
-    label = gtk_label_new("Recent connections:");
-    gtk_box_pack_start(GTK_BOX(area), label, TRUE, TRUE, 0);
-    gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
-#ifndef G_OS_WIN32
-    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(GTK_BOX(area), 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(recent, "item-activated",
-                     G_CALLBACK(recent_item_activated_dialog_cb), dialog);
-#endif
-    /* show and wait for response */
-    gtk_widget_show_all(dialog);
-    if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
-        for (i = 0; i < SPICE_N_ELEMENTS(connect_entries); i++) {
-            const gchar *txt;
-            txt = gtk_entry_get_text(GTK_ENTRY(connect_entries[i].entry));
-            g_object_set(session, connect_entries[i].prop, txt, NULL);
-        }
-        retval = 0;
-    } else
-        retval = -1;
-    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
-
-#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,
-                    GTK_STOCK_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",  GTK_STOCK_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        = "HelpMenu",
-        .label       = "_Help",
-    },{
-
-        /* File menu */
-        .name        = "Connect",
-        .stock_id    = GTK_STOCK_CONNECT,
-        .label       = N_("_Connect ..."),
-        .callback    = G_CALLBACK(menu_cb_connect),
-    },{
-        .name        = "Close",
-        .stock_id    = GTK_STOCK_CLOSE,
-        .label       = N_("_Close"),
-        .callback    = G_CALLBACK(menu_cb_close),
-        .accelerator = "", /* none (disable default "<control>W") */
-    },{
-
-        /* Edit menu */
-        .name        = "CopyToGuest",
-        .stock_id    = GTK_STOCK_COPY,
-        .label       = N_("_Copy to guest"),
-        .callback    = G_CALLBACK(menu_cb_copy),
-        .accelerator = "", /* none (disable default "<control>C") */
-    },{
-        .name        = "PasteFromGuest",
-        .stock_id    = GTK_STOCK_PASTE,
-        .label       = N_("_Paste from guest"),
-        .callback    = G_CALLBACK(menu_cb_paste),
-        .accelerator = "", /* none (disable default "<control>V") */
-    },{
-
-        /* View menu */
-        .name        = "Fullscreen",
-        .stock_id    = GTK_STOCK_FULLSCREEN,
-        .label       = N_("_Fullscreen"),
-        .callback    = G_CALLBACK(menu_cb_fullscreen),
-        .accelerator = "<shift>F11",
-    },{
-#ifdef USE_SMARTCARD
-	.name        = "InsertSmartcard",
-	.label       = N_("_Insert Smartcard"),
-	.callback    = G_CALLBACK(menu_cb_insert_smartcard),
-        .accelerator = "<shift>F8",
-    },{
-	.name        = "RemoveSmartcard",
-	.label       = N_("_Remove Smartcard"),
-	.callback    = G_CALLBACK(menu_cb_remove_smartcard),
-        .accelerator = "<shift>F9",
-    },{
-#endif
-
-#ifdef USE_USBREDIR
-        .name        = "SelectUsbDevices",
-        .label       = N_("_Select USB Devices for redirection"),
-        .callback    = G_CALLBACK(menu_cb_select_usb_devices),
-        .accelerator = "<shift>F10",
-    },{
-#endif
-
-        /* Help menu */
-        .name        = "About",
-        .stock_id    = GTK_STOCK_ABOUT,
-        .label       = N_("_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",
-};
-
-static const GtkToggleActionEntry tentries[] = {
-    {
-        .name        = "grab-keyboard",
-        .label       = N_("Grab keyboard when active and focused"),
-        .callback    = G_CALLBACK(menu_cb_bool_prop),
-    },{
-        .name        = "grab-mouse",
-        .label       = N_("Grab mouse in server mode (no tabled/vdagent)"),
-        .callback    = G_CALLBACK(menu_cb_bool_prop),
-    },{
-        .name        = "resize-guest",
-        .label       = N_("Resize guest to match window size"),
-        .callback    = G_CALLBACK(menu_cb_bool_prop),
-    },{
-        .name        = "scaling",
-        .label       = N_("Scale display"),
-        .callback    = G_CALLBACK(menu_cb_bool_prop),
-    },{
-        .name        = "disable-inputs",
-        .label       = N_("Disable inputs"),
-        .callback    = G_CALLBACK(menu_cb_bool_prop),
-    },{
-        .name        = "auto-clipboard",
-        .label       = N_("Automagic clipboard sharing between host and guest"),
-        .callback    = G_CALLBACK(menu_cb_bool_prop),
-    },{
-        .name        = "auto-usbredir",
-        .label       = N_("Auto redirect newly plugged in USB devices"),
-        .callback    = G_CALLBACK(menu_cb_bool_prop),
-    },{
-        .name        = "Statusbar",
-        .label       = N_("Statusbar"),
-        .callback    = G_CALLBACK(menu_cb_statusbar),
-    },{
-        .name        = "Toolbar",
-        .label       = N_("Toolbar"),
-        .callback    = G_CALLBACK(menu_cb_toolbar),
-    }
-};
-
-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='resize-guest'/>\n"
-"      <menuitem action='scaling'/>\n"
-"      <menuitem action='disable-inputs'/>\n"
-"      <menuitem action='auto-clipboard'/>\n"
-"      <menuitem action='auto-usbredir'/>\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;
-}
-
-#ifndef G_OS_WIN32
-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);
-}
-#endif
-
-static gboolean configure_event_cb(GtkWidget         *widget,
-                                   GdkEventConfigure *event,
-                                   gpointer           data)
-{
-    gboolean resize_guest;
-    SpiceWindow *win = data;
-
-    g_return_val_if_fail(win != NULL, FALSE);
-    g_return_val_if_fail(win->conn != NULL, FALSE);
-
-    g_object_get(win->spice, "resize-guest", &resize_guest, NULL);
-    if (resize_guest && win->conn->agent_connected)
-        return FALSE;
-
-    return FALSE;
-}
-
-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_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");
-
-#ifndef G_OS_WIN32
-    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);
-#endif
-
-    /* spice display */
-    win->spice = GTK_WIDGET(spice_display_new_with_monitor(conn->session, id, monitor_id));
-    g_signal_connect(win->spice, "configure-event", G_CALLBACK(configure_event_cb), win);
-    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 */
-#if GTK_CHECK_VERSION(3,0,0)
-    win->statusbar = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 1);
-#else
-    win->statusbar = gtk_hbox_new(FALSE, 1);
-#endif
-
-    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 */
-#if GTK_CHECK_VERSION(3,0,0)
-    vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 1);
-#else
-    vbox = gtk_vbox_new(FALSE, 1);
-#endif
-    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);
-        }
-
-        rc = connect_dialog(conn->session);
-        if (rc == 0) {
-            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: %d", 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 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) {
-            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);
-    }
-}
-
-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);
-        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);
-    }
-
-    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);
-    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      = N_("Open in full screen mode"),
-    },{
-        .long_name        = "version",
-        .arg              = G_OPTION_ARG_NONE,
-        .arg_data         = &version,
-        .description      = N_("Display version and quit"),
-    },{
-        .long_name        = "title",
-        .arg              = G_OPTION_ARG_STRING,
-        .arg_data         = &spicy_title,
-        .description      = N_("Set the window title"),
-        .arg_description  = N_("<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
-    static struct termios saved_tios;
-    struct termios tios;
-
-    if (reset)
-        tios = saved_tios;
-    else {
-        tcgetattr(stdinfd, &tios);
-        saved_tios = tios;
-        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;
-
-#if !GLIB_CHECK_VERSION(2,31,18)
-    g_thread_init(NULL);
-#endif
-    bindtextdomain(GETTEXT_PACKAGE, SPICE_GTK_LOCALEDIR);
-    bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
-    textdomain(GETTEXT_PACKAGE);
-
-    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);
-    }
-
-#if !GLIB_CHECK_VERSION(2,36,0)
-    g_type_init();
-#endif
-    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) {
-        int ret = connect_dialog(conn->session);
-        if (ret != 0) {
-            exit(0);
-        }
-    }
-    g_free(host);
-    g_free(port);
-    g_free(tls_port);
-    g_free(unix_path);
-
-    watch_stdin();
-
-    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/gtk/usb-acl-helper.c b/gtk/usb-acl-helper.c
deleted file mode 100644
index 6a49627..0000000
--- a/gtk/usb-acl-helper.c
+++ /dev/null
@@ -1,299 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2011 Red Hat, Inc.
-
-   Red Hat Authors:
-   Hans de Goede <hdegoede at redhat.com>
-
-   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 <errno.h>
-#include <stdio.h>
-#include <string.h>
-
-#include "usb-acl-helper.h"
-#include "glib-compat.h"
-
-/* ------------------------------------------------------------------ */
-/* gobject glue                                                       */
-
-#define SPICE_USB_ACL_HELPER_GET_PRIVATE(obj)                                  \
-    (G_TYPE_INSTANCE_GET_PRIVATE ((obj), SPICE_TYPE_USB_ACL_HELPER, SpiceUsbAclHelperPrivate))
-
-struct _SpiceUsbAclHelperPrivate {
-    GSimpleAsyncResult *result;
-    GIOChannel *in_ch;
-    GIOChannel *out_ch;
-    GCancellable *cancellable;
-    gulong cancellable_id;
-};
-
-G_DEFINE_TYPE(SpiceUsbAclHelper, spice_usb_acl_helper, G_TYPE_OBJECT);
-
-static void spice_usb_acl_helper_init(SpiceUsbAclHelper *self)
-{
-    self->priv = SPICE_USB_ACL_HELPER_GET_PRIVATE(self);
-}
-
-static void spice_usb_acl_helper_cleanup(SpiceUsbAclHelper *self)
-{
-    SpiceUsbAclHelperPrivate *priv = self->priv;
-
-    g_cancellable_disconnect(priv->cancellable, priv->cancellable_id);
-    priv->cancellable = NULL;
-    priv->cancellable_id = 0;
-
-    g_clear_object(&priv->result);
-
-    if (priv->in_ch) {
-        g_io_channel_unref(priv->in_ch);
-        priv->in_ch = NULL;
-    }
-
-    if (priv->out_ch) {
-        g_io_channel_unref(priv->out_ch);
-        priv->out_ch = NULL;
-    }
-}
-
-static void spice_usb_acl_helper_finalize(GObject *gobject)
-{
-    spice_usb_acl_helper_cleanup(SPICE_USB_ACL_HELPER(gobject));
-
-    if (G_OBJECT_CLASS(spice_usb_acl_helper_parent_class)->finalize)
-        G_OBJECT_CLASS(spice_usb_acl_helper_parent_class)->finalize(gobject);
-}
-
-static void spice_usb_acl_helper_class_init(SpiceUsbAclHelperClass *klass)
-{
-    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
-
-    gobject_class->finalize     = spice_usb_acl_helper_finalize;
-
-    g_type_class_add_private(klass, sizeof(SpiceUsbAclHelperPrivate));
-}
-
-/* ------------------------------------------------------------------ */
-/* callbacks                                                          */
-
-static void async_result_set_cancelled(GSimpleAsyncResult *result)
-{
-    g_simple_async_result_set_error(result,
-                G_IO_ERROR, G_IO_ERROR_CANCELLED,
-                "Setting USB device node ACL cancelled");
-}
-
-static gboolean cb_out_watch(GIOChannel    *channel,
-                             GIOCondition   cond,
-                             gpointer      *user_data)
-{
-    SpiceUsbAclHelper *self = SPICE_USB_ACL_HELPER(user_data);
-    SpiceUsbAclHelperPrivate *priv = self->priv;
-    gboolean success = FALSE;
-    GError *err = NULL;
-    GIOStatus status;
-    gchar *string;
-    gsize size;
-
-    /* Check that we've not been cancelled */
-    if (priv->result == NULL)
-        goto done;
-
-    g_return_val_if_fail(channel == priv->out_ch, FALSE);
-
-    status = g_io_channel_read_line(priv->out_ch, &string, &size, NULL, &err);
-    switch (status) {
-        case G_IO_STATUS_NORMAL:
-            string[strlen(string) - 1] = 0;
-            if (!strcmp(string, "SUCCESS")) {
-                success = TRUE;
-            } else if (!strcmp(string, "CANCELED")) {
-                async_result_set_cancelled(priv->result);
-            } else {
-                g_simple_async_result_set_error(priv->result,
-                            SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
-                            "Error setting USB device node ACL: '%s'",
-                            string);
-            }
-            g_free(string);
-            break;
-        case G_IO_STATUS_ERROR:
-            g_simple_async_result_take_error(priv->result, err);
-            break;
-        case G_IO_STATUS_EOF:
-            g_simple_async_result_set_error(priv->result,
-                        SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
-                        "Unexpected EOF reading from acl helper stdout");
-            break;
-        case G_IO_STATUS_AGAIN:
-            return TRUE; /* Wait for more input */
-    }
-
-    g_cancellable_disconnect(priv->cancellable, priv->cancellable_id);
-    priv->cancellable = NULL;
-    priv->cancellable_id = 0;
-
-    g_simple_async_result_complete_in_idle(priv->result);
-    g_clear_object(&priv->result);
-
-    if (!success)
-        spice_usb_acl_helper_cleanup(self);
-
-done:
-    g_object_unref(self);
-    return FALSE;
-}
-
-static void cancelled_cb(GCancellable *cancellable, gpointer user_data)
-{
-    SpiceUsbAclHelper *self = SPICE_USB_ACL_HELPER(user_data);
-
-    spice_usb_acl_helper_close_acl(self);
-}
-
-static void helper_child_watch_cb(GPid pid, gint status, gpointer user_data)
-{
-    /* Nothing to do, but we need the child watch to avoid zombies */
-}
-
-/* ------------------------------------------------------------------ */
-/* private api                                                        */
-
-G_GNUC_INTERNAL
-SpiceUsbAclHelper *spice_usb_acl_helper_new(void)
-{
-    GObject *obj;
-
-    obj = g_object_new(SPICE_TYPE_USB_ACL_HELPER, NULL);
-
-    return SPICE_USB_ACL_HELPER(obj);
-}
-
-G_GNUC_INTERNAL
-void spice_usb_acl_helper_open_acl(SpiceUsbAclHelper *self,
-                                   gint busnum, gint devnum,
-                                   GCancellable *cancellable,
-                                   GAsyncReadyCallback callback,
-                                   gpointer user_data)
-{
-    g_return_if_fail(SPICE_IS_USB_ACL_HELPER(self));
-
-    SpiceUsbAclHelperPrivate *priv = self->priv;
-    GSimpleAsyncResult *result;
-    GError *err = NULL;
-    GIOStatus status;
-    GPid helper_pid;
-    gsize bytes_written;
-    gchar *argv[] = { (char*) ACL_HELPER_PATH"/spice-client-glib-usb-acl-helper", NULL };
-    gint in, out;
-    gchar buf[128];
-
-    result = g_simple_async_result_new(G_OBJECT(self), callback, user_data,
-                                       spice_usb_acl_helper_open_acl);
-
-    if (priv->out_ch) {
-        g_simple_async_result_set_error(result,
-                            SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
-                            "Error acl-helper already has an acl open");
-        goto done;
-    }
-
-    if (g_cancellable_set_error_if_cancelled(cancellable, &err)) {
-        g_simple_async_result_take_error(result, err);
-        goto done;
-    }
-
-    if (!g_spawn_async_with_pipes(NULL, argv, NULL,
-                           G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH,
-                           NULL, NULL, &helper_pid, &in, &out, NULL, &err)) {
-        g_simple_async_result_take_error(result, err);
-        goto done;
-    }
-    g_child_watch_add(helper_pid, helper_child_watch_cb, NULL);
-
-    priv->in_ch = g_io_channel_unix_new(in);
-    g_io_channel_set_close_on_unref(priv->in_ch, TRUE);
-
-    priv->out_ch = g_io_channel_unix_new(out);
-    g_io_channel_set_close_on_unref(priv->out_ch, TRUE);
-    status = g_io_channel_set_flags(priv->out_ch, G_IO_FLAG_NONBLOCK, &err);
-    if (status != G_IO_STATUS_NORMAL) {
-        g_simple_async_result_take_error(result, err);
-        goto done;
-    }
-
-    snprintf(buf, sizeof(buf), "%d %d\n", busnum, devnum);
-    status = g_io_channel_write_chars(priv->in_ch, buf, -1,
-                                      &bytes_written, &err);
-    if (status != G_IO_STATUS_NORMAL) {
-        g_simple_async_result_take_error(result, err);
-        goto done;
-    }
-    status = g_io_channel_flush(priv->in_ch, &err);
-    if (status != G_IO_STATUS_NORMAL) {
-        g_simple_async_result_take_error(result, err);
-        goto done;
-    }
-
-    priv->result = result;
-    if (cancellable) {
-        priv->cancellable = cancellable;
-        priv->cancellable_id = g_cancellable_connect(cancellable,
-                                                     G_CALLBACK(cancelled_cb),
-                                                     self, NULL);
-    }
-    g_io_add_watch(priv->out_ch, G_IO_IN|G_IO_HUP,
-                   (GIOFunc)cb_out_watch, g_object_ref(self));
-    return;
-
-done:
-    spice_usb_acl_helper_cleanup(self);
-    g_simple_async_result_complete_in_idle(result);
-    g_object_unref(result);
-}
-
-G_GNUC_INTERNAL
-gboolean spice_usb_acl_helper_open_acl_finish(
-    SpiceUsbAclHelper *self, GAsyncResult *res, GError **err)
-{
-    GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT(res);
-
-    g_return_val_if_fail(g_simple_async_result_is_valid(res, G_OBJECT(self),
-                                               spice_usb_acl_helper_open_acl),
-                         FALSE);
-
-    if (g_simple_async_result_propagate_error(result, err))
-        return FALSE;
-
-    return TRUE;
-}
-
-G_GNUC_INTERNAL
-void spice_usb_acl_helper_close_acl(SpiceUsbAclHelper *self)
-{
-    g_return_if_fail(SPICE_IS_USB_ACL_HELPER(self));
-
-    SpiceUsbAclHelperPrivate *priv = self->priv;
-
-    /* If the acl open has not completed yet report it as cancelled */
-    if (priv->result) {
-        async_result_set_cancelled(priv->result);
-        g_simple_async_result_complete_in_idle(priv->result);
-    }
-
-    spice_usb_acl_helper_cleanup(self);
-}
diff --git a/gtk/usb-acl-helper.h b/gtk/usb-acl-helper.h
deleted file mode 100644
index 2d41b68..0000000
--- a/gtk/usb-acl-helper.h
+++ /dev/null
@@ -1,72 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2011 Red Hat, Inc.
-
-   Red Hat Authors:
-   Hans de Goede <hdegoede at redhat.com>
-
-   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_USB_ACL_HELPER_H__
-#define __SPICE_USB_ACL_HELPER_H__
-
-#include "spice-client.h"
-#include <gio/gio.h>
-
-/* Note the entire usb-acl-helper class is private to spice-client-glib !! */
-
-G_BEGIN_DECLS
-
-#define SPICE_TYPE_USB_ACL_HELPER            (spice_usb_acl_helper_get_type ())
-#define SPICE_USB_ACL_HELPER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), SPICE_TYPE_USB_ACL_HELPER, SpiceUsbAclHelper))
-#define SPICE_USB_ACL_HELPER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SPICE_TYPE_USB_ACL_HELPER, SpiceUsbAclHelperClass))
-#define SPICE_IS_USB_ACL_HELPER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SPICE_TYPE_USB_ACL_HELPER))
-#define SPICE_IS_USB_ACL_HELPER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SPICE_TYPE_USB_ACL_HELPER))
-#define SPICE_USB_ACL_HELPER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SPICE_TYPE_USB_ACL_HELPER, SpiceUsbAclHelperClass))
-
-typedef struct _SpiceUsbAclHelper SpiceUsbAclHelper;
-typedef struct _SpiceUsbAclHelperClass SpiceUsbAclHelperClass;
-typedef struct _SpiceUsbAclHelperPrivate SpiceUsbAclHelperPrivate;
-
-struct _SpiceUsbAclHelper
-{
-    GObject parent;
-
-    /*< private >*/
-    SpiceUsbAclHelperPrivate *priv;
-    /* Do not add fields to this struct */
-};
-
-struct _SpiceUsbAclHelperClass
-{
-    GObjectClass parent_class;
-};
-
-GType spice_usb_acl_helper_get_type(void);
-
-SpiceUsbAclHelper *spice_usb_acl_helper_new(void);
-
-void spice_usb_acl_helper_open_acl(SpiceUsbAclHelper *self,
-                                   gint busnum, gint devnum,
-                                   GCancellable *cancellable,
-                                   GAsyncReadyCallback callback,
-                                   gpointer user_data);
-gboolean spice_usb_acl_helper_open_acl_finish(
-    SpiceUsbAclHelper *self, GAsyncResult *res, GError **err);
-
-void spice_usb_acl_helper_close_acl(SpiceUsbAclHelper *self);
-
-G_END_DECLS
-
-#endif /* __SPICE_USB_ACL_HELPER_H__ */
diff --git a/gtk/usb-device-manager-priv.h b/gtk/usb-device-manager-priv.h
deleted file mode 100644
index b6fa9c9..0000000
--- a/gtk/usb-device-manager-priv.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2011,2012 Red Hat, Inc.
-
-   Red Hat Authors:
-   Hans de Goede <hdegoede at redhat.com>
-
-   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_USB_DEVICE_MANAGER_PRIV_H__
-#define __SPICE_USB_DEVICE_MANAGER_PRIV_H__
-
-#include "usb-device-manager.h"
-
-G_BEGIN_DECLS
-
-gboolean spice_usb_device_manager_start_event_listening(
-    SpiceUsbDeviceManager *manager, GError **err);
-
-void spice_usb_device_manager_stop_event_listening(
-    SpiceUsbDeviceManager *manager);
-
-#ifdef USE_USBREDIR
-#include <libusb.h>
-void spice_usb_device_manager_device_error(
-    SpiceUsbDeviceManager *manager, SpiceUsbDevice *device, GError *err);
-
-guint8 spice_usb_device_get_busnum(const SpiceUsbDevice *device);
-guint8 spice_usb_device_get_devaddr(const SpiceUsbDevice *device);
-guint16 spice_usb_device_get_vid(const SpiceUsbDevice *device);
-guint16 spice_usb_device_get_pid(const SpiceUsbDevice *device);
-
-#endif
-
-G_END_DECLS
-
-#endif /* __SPICE_USB_DEVICE_MANAGER_PRIV_H__ */
diff --git a/gtk/usb-device-manager.c b/gtk/usb-device-manager.c
deleted file mode 100644
index 7aa60c4..0000000
--- a/gtk/usb-device-manager.c
+++ /dev/null
@@ -1,1932 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2011, 2012 Red Hat, Inc.
-
-   Red Hat Authors:
-   Hans de Goede <hdegoede at redhat.com>
-
-   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-object.h>
-
-#include "glib-compat.h"
-
-#ifdef USE_USBREDIR
-#include <errno.h>
-#include <libusb.h>
-
-#if defined(USE_GUDEV)
-#include <gudev/gudev.h>
-#elif defined(G_OS_WIN32)
-#include "win-usb-dev.h"
-#include "win-usb-driver-install.h"
-#define USE_GUDEV /* win-usb-dev.h provides a fake gudev interface */
-#elif !defined USE_LIBUSB_HOTPLUG
-#error "Expecting one of USE_GUDEV or USE_LIBUSB_HOTPLUG to be defined"
-#endif
-
-#include "channel-usbredir-priv.h"
-#include "usbredirhost.h"
-#include "usbutil.h"
-#endif
-
-#include "spice-session-priv.h"
-#include "spice-client.h"
-#include "spice-marshal.h"
-#include "usb-device-manager-priv.h"
-
-#include <glib/gi18n.h>
-
-#ifndef G_OS_WIN32 /* Linux -- device id is bus.addr */
-#define DEV_ID_FMT "at %d.%d"
-#else /* Windows -- device id is vid:pid */
-#define DEV_ID_FMT "0x%04x:0x%04x"
-#endif
-
-/**
- * SECTION:usb-device-manager
- * @short_description: USB device management
- * @title: Spice USB Manager
- * @section_id:
- * @see_also:
- * @stability: Stable
- * @include: usb-device-manager.h
- *
- * #SpiceUsbDeviceManager monitors USB redirection channels and USB
- * devices plugging/unplugging. If #SpiceUsbDeviceManager:auto-connect
- * is set to %TRUE, it will automatically connect newly plugged USB
- * devices to available channels.
- *
- * There should always be a 1:1 relation between #SpiceUsbDeviceManager objects
- * and #SpiceSession objects. Therefor there is no
- * spice_usb_device_manager_new, instead there is
- * spice_usb_device_manager_get() which ensures this 1:1 relation.
- */
-
-/* ------------------------------------------------------------------ */
-/* gobject glue                                                       */
-
-#define SPICE_USB_DEVICE_MANAGER_GET_PRIVATE(obj)                                  \
-    (G_TYPE_INSTANCE_GET_PRIVATE ((obj), SPICE_TYPE_USB_DEVICE_MANAGER, SpiceUsbDeviceManagerPrivate))
-
-enum {
-    PROP_0,
-    PROP_SESSION,
-    PROP_AUTO_CONNECT,
-    PROP_AUTO_CONNECT_FILTER,
-    PROP_REDIRECT_ON_CONNECT,
-};
-
-enum
-{
-    DEVICE_ADDED,
-    DEVICE_REMOVED,
-    AUTO_CONNECT_FAILED,
-    DEVICE_ERROR,
-    LAST_SIGNAL,
-};
-
-struct _SpiceUsbDeviceManagerPrivate {
-    SpiceSession *session;
-    gboolean auto_connect;
-    gchar *auto_connect_filter;
-    gchar *redirect_on_connect;
-#ifdef USE_USBREDIR
-    libusb_context *context;
-    int event_listeners;
-    GThread *event_thread;
-    gboolean event_thread_run;
-    struct usbredirfilter_rule *auto_conn_filter_rules;
-    struct usbredirfilter_rule *redirect_on_connect_rules;
-    int auto_conn_filter_rules_count;
-    int redirect_on_connect_rules_count;
-#ifdef USE_GUDEV
-    GUdevClient *udev;
-    libusb_device **coldplug_list; /* Avoid needless reprobing during init */
-#else
-    libusb_hotplug_callback_handle hp_handle;
-#endif
-#ifdef G_OS_WIN32
-    SpiceWinUsbDriver     *installer;
-#endif
-#endif
-    GPtrArray *devices;
-    GPtrArray *channels;
-};
-
-enum {
-    SPICE_USB_DEVICE_STATE_NONE = 0, /* this is also DISCONNECTED */
-    SPICE_USB_DEVICE_STATE_CONNECTING,
-    SPICE_USB_DEVICE_STATE_CONNECTED,
-    SPICE_USB_DEVICE_STATE_DISCONNECTING,
-    SPICE_USB_DEVICE_STATE_INSTALLING,
-    SPICE_USB_DEVICE_STATE_UNINSTALLING,
-    SPICE_USB_DEVICE_STATE_INSTALLED,
-    SPICE_USB_DEVICE_STATE_MAX
-};
-
-#ifdef USE_USBREDIR
-
-typedef struct _SpiceUsbDeviceInfo {
-    guint8  busnum;
-    guint8  devaddr;
-    guint16 vid;
-    guint16 pid;
-#ifdef G_OS_WIN32
-    guint8  state;
-#else
-    libusb_device *libdev;
-#endif
-    gint    ref;
-} SpiceUsbDeviceInfo;
-
-
-static void channel_new(SpiceSession *session, SpiceChannel *channel,
-                        gpointer user_data);
-static void channel_destroy(SpiceSession *session, SpiceChannel *channel,
-                            gpointer user_data);
-#ifdef USE_GUDEV
-static void spice_usb_device_manager_uevent_cb(GUdevClient     *client,
-                                               const gchar     *action,
-                                               GUdevDevice     *udevice,
-                                               gpointer         user_data);
-static void spice_usb_device_manager_add_udev(SpiceUsbDeviceManager  *self,
-                                              GUdevDevice            *udev);
-#else
-static int spice_usb_device_manager_hotplug_cb(libusb_context       *ctx,
-                                               libusb_device        *device,
-                                               libusb_hotplug_event  event,
-                                               void                 *data);
-#endif
-static void spice_usb_device_manager_check_redir_on_connect(
-    SpiceUsbDeviceManager *self, SpiceChannel *channel);
-
-static SpiceUsbDeviceInfo *spice_usb_device_new(libusb_device *libdev);
-static SpiceUsbDevice *spice_usb_device_ref(SpiceUsbDevice *device);
-static void spice_usb_device_unref(SpiceUsbDevice *device);
-
-#ifdef G_OS_WIN32
-static guint8 spice_usb_device_get_state(SpiceUsbDevice *device);
-static void  spice_usb_device_set_state(SpiceUsbDevice *device, guint8 s);
-#endif
-
-static gboolean spice_usb_device_equal_libdev(SpiceUsbDevice *device,
-                                              libusb_device *libdev);
-static libusb_device *
-spice_usb_device_manager_device_to_libdev(SpiceUsbDeviceManager *self,
-                                          SpiceUsbDevice *device);
-
-static void
-_spice_usb_device_manager_connect_device_async(SpiceUsbDeviceManager *self,
-                                               SpiceUsbDevice *device,
-                                               GCancellable *cancellable,
-                                               GAsyncReadyCallback callback,
-                                               gpointer user_data);
-
-G_DEFINE_BOXED_TYPE(SpiceUsbDevice, spice_usb_device,
-                    (GBoxedCopyFunc)spice_usb_device_ref,
-                    (GBoxedFreeFunc)spice_usb_device_unref)
-
-#else
-G_DEFINE_BOXED_TYPE(SpiceUsbDevice, spice_usb_device, g_object_ref, g_object_unref)
-#endif
-
-static void spice_usb_device_manager_initable_iface_init(GInitableIface *iface);
-
-#ifdef USE_USBREDIR
-#ifdef G_OS_WIN32
-static void spice_usb_device_manager_drv_install_cb(GObject *gobject,
-                                                    GAsyncResult *res,
-                                                    gpointer user_data);
-#endif
-#endif
-
-static guint signals[LAST_SIGNAL] = { 0, };
-
-G_DEFINE_TYPE_WITH_CODE(SpiceUsbDeviceManager, spice_usb_device_manager, G_TYPE_OBJECT,
-     G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, spice_usb_device_manager_initable_iface_init));
-
-static void spice_usb_device_manager_init(SpiceUsbDeviceManager *self)
-{
-    SpiceUsbDeviceManagerPrivate *priv;
-
-    priv = SPICE_USB_DEVICE_MANAGER_GET_PRIVATE(self);
-    self->priv = priv;
-
-    priv->channels = g_ptr_array_new();
-#ifdef USE_USBREDIR
-    priv->devices  = g_ptr_array_new_with_free_func((GDestroyNotify)
-                                                    spice_usb_device_unref);
-#endif
-}
-
-static gboolean spice_usb_device_manager_initable_init(GInitable  *initable,
-                                                    GCancellable  *cancellable,
-                                                    GError        **err)
-{
-    SpiceUsbDeviceManager *self;
-    SpiceUsbDeviceManagerPrivate *priv;
-#ifdef USE_USBREDIR
-    GList *list;
-    GList *it;
-    int rc;
-#ifdef USE_GUDEV
-    const gchar *const subsystems[] = {"usb", NULL};
-#endif
-#endif
-
-    g_return_val_if_fail(SPICE_IS_USB_DEVICE_MANAGER(initable), FALSE);
-    g_return_val_if_fail(err == NULL || *err == NULL, FALSE);
-
-    if (cancellable != NULL) {
-        g_set_error_literal(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
-                            "Cancellable initialization not supported");
-        return FALSE;
-    }
-
-    self = SPICE_USB_DEVICE_MANAGER(initable);
-    priv = self->priv;
-
-    if (!priv->session) {
-        g_set_error_literal(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
-                "SpiceUsbDeviceManager constructed without a session");
-        return FALSE;
-    }
-
-#ifdef USE_USBREDIR
-    /* Initialize libusb */
-    rc = libusb_init(&priv->context);
-    if (rc < 0) {
-        const char *desc = spice_usbutil_libusb_strerror(rc);
-        g_warning("Error initializing USB support: %s [%i]", desc, rc);
-        g_set_error(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
-                    "Error initializing USB support: %s [%i]", desc, rc);
-        return FALSE;
-    }
-
-    /* Start listening for usb devices plug / unplug */
-#ifdef USE_GUDEV
-    priv->udev = g_udev_client_new(subsystems);
-    g_signal_connect(G_OBJECT(priv->udev), "uevent",
-                     G_CALLBACK(spice_usb_device_manager_uevent_cb), self);
-    /* Do coldplug (detection of already connected devices) */
-    libusb_get_device_list(priv->context, &priv->coldplug_list);
-    list = g_udev_client_query_by_subsystem(priv->udev, "usb");
-    for (it = g_list_first(list); it; it = g_list_next(it)) {
-        spice_usb_device_manager_add_udev(self, it->data);
-        g_object_unref(it->data);
-    }
-    g_list_free(list);
-    libusb_free_device_list(priv->coldplug_list, 1);
-    priv->coldplug_list = NULL;
-#else
-    rc = libusb_hotplug_register_callback(priv->context,
-        LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT,
-        LIBUSB_HOTPLUG_ENUMERATE, LIBUSB_HOTPLUG_MATCH_ANY,
-        LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_MATCH_ANY,
-        spice_usb_device_manager_hotplug_cb, self, &priv->hp_handle);
-    if (rc < 0) {
-        const char *desc = spice_usbutil_libusb_strerror(rc);
-        g_warning("Error initializing USB hotplug support: %s [%i]", desc, rc);
-        g_set_error(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
-                  "Error initializing USB hotplug support: %s [%i]", desc, rc);
-        return FALSE;
-    }
-    spice_usb_device_manager_start_event_listening(self, NULL);
-#endif
-
-    /* Start listening for usb channels connect/disconnect */
-    spice_g_signal_connect_object(priv->session, "channel-new", G_CALLBACK(channel_new), self, G_CONNECT_AFTER);
-    g_signal_connect(priv->session, "channel-destroy",
-                     G_CALLBACK(channel_destroy), self);
-    list = spice_session_get_channels(priv->session);
-    for (it = g_list_first(list); it != NULL; it = g_list_next(it)) {
-        channel_new(priv->session, it->data, (gpointer*)self);
-    }
-    g_list_free(list);
-
-    return TRUE;
-#else
-    g_set_error_literal(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
-                        _("USB redirection support not compiled in"));
-    return FALSE;
-#endif
-}
-
-static void spice_usb_device_manager_dispose(GObject *gobject)
-{
-#ifdef USE_USBREDIR
-    SpiceUsbDeviceManager *self = SPICE_USB_DEVICE_MANAGER(gobject);
-    SpiceUsbDeviceManagerPrivate *priv = self->priv;
-
-#ifdef USE_LIBUSB_HOTPLUG
-    if (priv->hp_handle) {
-        spice_usb_device_manager_stop_event_listening(self);
-        /* This also wakes up the libusb_handle_events() in the event_thread */
-        libusb_hotplug_deregister_callback(priv->context, priv->hp_handle);
-        priv->hp_handle = 0;
-    }
-#endif
-    if (priv->event_thread && !priv->event_thread_run) {
-        g_thread_join(priv->event_thread);
-        priv->event_thread = NULL;
-    }
-#endif
-
-    /* Chain up to the parent class */
-    if (G_OBJECT_CLASS(spice_usb_device_manager_parent_class)->dispose)
-        G_OBJECT_CLASS(spice_usb_device_manager_parent_class)->dispose(gobject);
-}
-
-static void spice_usb_device_manager_finalize(GObject *gobject)
-{
-    SpiceUsbDeviceManager *self = SPICE_USB_DEVICE_MANAGER(gobject);
-    SpiceUsbDeviceManagerPrivate *priv = self->priv;
-
-    g_ptr_array_unref(priv->channels);
-    if (priv->devices)
-        g_ptr_array_unref(priv->devices);
-
-#ifdef USE_USBREDIR
-#ifdef USE_GUDEV
-    g_clear_object(&priv->udev);
-#endif
-    g_return_if_fail(priv->event_thread == NULL);
-    if (priv->context)
-        libusb_exit(priv->context);
-    free(priv->auto_conn_filter_rules);
-    free(priv->redirect_on_connect_rules);
-#ifdef G_OS_WIN32
-    if (priv->installer)
-        g_object_unref(priv->installer);
-#endif
-#endif
-
-    g_free(priv->auto_connect_filter);
-    g_free(priv->redirect_on_connect);
-
-    /* Chain up to the parent class */
-    if (G_OBJECT_CLASS(spice_usb_device_manager_parent_class)->finalize)
-        G_OBJECT_CLASS(spice_usb_device_manager_parent_class)->finalize(gobject);
-}
-
-static void spice_usb_device_manager_initable_iface_init(GInitableIface *iface)
-{
-    iface->init = spice_usb_device_manager_initable_init;
-}
-
-static void spice_usb_device_manager_get_property(GObject     *gobject,
-                                                  guint        prop_id,
-                                                  GValue      *value,
-                                                  GParamSpec  *pspec)
-{
-    SpiceUsbDeviceManager *self = SPICE_USB_DEVICE_MANAGER(gobject);
-    SpiceUsbDeviceManagerPrivate *priv = self->priv;
-
-    switch (prop_id) {
-    case PROP_SESSION:
-        g_value_set_object(value, priv->session);
-        break;
-    case PROP_AUTO_CONNECT:
-        g_value_set_boolean(value, priv->auto_connect);
-        break;
-    case PROP_AUTO_CONNECT_FILTER:
-        g_value_set_string(value, priv->auto_connect_filter);
-        break;
-    case PROP_REDIRECT_ON_CONNECT:
-        g_value_set_string(value, priv->redirect_on_connect);
-        break;
-    default:
-        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
-        break;
-    }
-}
-
-static void spice_usb_device_manager_set_property(GObject       *gobject,
-                                                  guint          prop_id,
-                                                  const GValue  *value,
-                                                  GParamSpec    *pspec)
-{
-    SpiceUsbDeviceManager *self = SPICE_USB_DEVICE_MANAGER(gobject);
-    SpiceUsbDeviceManagerPrivate *priv = self->priv;
-
-    switch (prop_id) {
-    case PROP_SESSION:
-        priv->session = g_value_get_object(value);
-        break;
-    case PROP_AUTO_CONNECT:
-        priv->auto_connect = g_value_get_boolean(value);
-        break;
-    case PROP_AUTO_CONNECT_FILTER: {
-        const gchar *filter = g_value_get_string(value);
-#ifdef USE_USBREDIR
-        struct usbredirfilter_rule *rules;
-        int r, count;
-
-        r = usbredirfilter_string_to_rules(filter, ",", "|", &rules, &count);
-        if (r) {
-            if (r == -ENOMEM)
-                g_error("Failed to allocate memory for auto-connect-filter");
-            g_warning("Error parsing auto-connect-filter string, keeping old filter");
-            break;
-        }
-
-        free(priv->auto_conn_filter_rules);
-        priv->auto_conn_filter_rules = rules;
-        priv->auto_conn_filter_rules_count = count;
-#endif
-        g_free(priv->auto_connect_filter);
-        priv->auto_connect_filter = g_strdup(filter);
-        break;
-    }
-    case PROP_REDIRECT_ON_CONNECT: {
-        const gchar *filter = g_value_get_string(value);
-#ifdef USE_USBREDIR
-        struct usbredirfilter_rule *rules = NULL;
-        int r = 0, count = 0;
-
-        if (filter)
-            r = usbredirfilter_string_to_rules(filter, ",", "|",
-                                               &rules, &count);
-        if (r) {
-            if (r == -ENOMEM)
-                g_error("Failed to allocate memory for redirect-on-connect");
-            g_warning("Error parsing redirect-on-connect string, keeping old filter");
-            break;
-        }
-
-        free(priv->redirect_on_connect_rules);
-        priv->redirect_on_connect_rules = rules;
-        priv->redirect_on_connect_rules_count = count;
-#endif
-        g_free(priv->redirect_on_connect);
-        priv->redirect_on_connect = g_strdup(filter);
-        break;
-    }
-    default:
-        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
-        break;
-    }
-}
-
-static void spice_usb_device_manager_class_init(SpiceUsbDeviceManagerClass *klass)
-{
-    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
-    GParamSpec *pspec;
-
-    gobject_class->dispose      = spice_usb_device_manager_dispose;
-    gobject_class->finalize     = spice_usb_device_manager_finalize;
-    gobject_class->get_property = spice_usb_device_manager_get_property;
-    gobject_class->set_property = spice_usb_device_manager_set_property;
-
-    /**
-     * SpiceUsbDeviceManager:session:
-     *
-     * #SpiceSession this #SpiceUsbDeviceManager is associated with
-     *
-     **/
-    g_object_class_install_property
-        (gobject_class, PROP_SESSION,
-         g_param_spec_object("session",
-                             "Session",
-                             "SpiceSession",
-                             SPICE_TYPE_SESSION,
-                             G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
-                             G_PARAM_STATIC_STRINGS));
-
-    /**
-     * SpiceUsbDeviceManager:auto-connect:
-     *
-     * Set this to TRUE to automatically redirect newly plugged in device.
-     *
-     * Note when #SpiceGtkSession's auto-usbredir property is TRUE, this
-     * property is controlled by #SpiceGtkSession.
-     */
-    pspec = g_param_spec_boolean("auto-connect", "Auto Connect",
-                                 "Auto connect plugged in USB devices",
-                                 FALSE,
-                                 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
-    g_object_class_install_property(gobject_class, PROP_AUTO_CONNECT, pspec);
-
-    /**
-     * SpiceUsbDeviceManager:auto-connect-filter:
-     *
-     * Set a string specifying a filter to use to determine which USB devices
-     * to autoconnect when plugged in, a filter consists of one or more rules.
-     * Where each rule has the form of:
-     *
-     * @class, at vendor, at product, at version, at allow
-     *
-     * Use -1 for @class/@vendor/@product/@version to accept any value.
-     *
-     * And the rules themselves are concatenated like this:
-     *
-     * @rule1|@rule2|@rule3
-     *
-     * The default setting filters out HID (class 0x03) USB devices from auto
-     * connect and auto connects anything else. Note the explicit allow rule at
-     * the end, this is necessary since by default all devices without a
-     * matching filter rule will not auto-connect.
-     *
-     * Filter strings in this format can be easily created with the RHEV-M
-     * USB filter editor tool.
-     */
-    pspec = g_param_spec_string("auto-connect-filter", "Auto Connect Filter ",
-               "Filter determining which USB devices to auto connect",
-               "0x03,-1,-1,-1,0|-1,-1,-1,-1,1",
-               G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
-    g_object_class_install_property(gobject_class, PROP_AUTO_CONNECT_FILTER,
-                                    pspec);
-
-    /**
-     * SpiceUsbDeviceManager:redirect-on-connect:
-     *
-     * Set a string specifying a filter selecting USB devices to automatically
-     * redirect after a Spice connection has been established.
-     *
-     * See #SpiceUsbDeviceManager:auto-connect-filter for the filter string
-     * format.
-     */
-    pspec = g_param_spec_string("redirect-on-connect", "Redirect on connect",
-               "Filter selecting USB devices to redirect on connect", NULL,
-               G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
-    g_object_class_install_property(gobject_class, PROP_REDIRECT_ON_CONNECT,
-                                    pspec);
-
-    /**
-     * SpiceUsbDeviceManager::device-added:
-     * @manager: the #SpiceUsbDeviceManager that emitted the signal
-     * @device: #SpiceUsbDevice boxed object corresponding to the added device
-     *
-     * The #SpiceUsbDeviceManager::device-added signal is emitted whenever
-     * a new USB device has been plugged in.
-     **/
-    signals[DEVICE_ADDED] =
-        g_signal_new("device-added",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_FIRST,
-                     G_STRUCT_OFFSET(SpiceUsbDeviceManagerClass, device_added),
-                     NULL, NULL,
-                     g_cclosure_marshal_VOID__BOXED,
-                     G_TYPE_NONE,
-                     1,
-                     SPICE_TYPE_USB_DEVICE);
-
-    /**
-     * SpiceUsbDeviceManager::device-removed:
-     * @manager: the #SpiceUsbDeviceManager that emitted the signal
-     * @device: #SpiceUsbDevice boxed object corresponding to the removed device
-     *
-     * The #SpiceUsbDeviceManager::device-removed signal is emitted whenever
-     * an USB device has been removed.
-     **/
-    signals[DEVICE_REMOVED] =
-        g_signal_new("device-removed",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_FIRST,
-                     G_STRUCT_OFFSET(SpiceUsbDeviceManagerClass, device_removed),
-                     NULL, NULL,
-                     g_cclosure_marshal_VOID__BOXED,
-                     G_TYPE_NONE,
-                     1,
-                     SPICE_TYPE_USB_DEVICE);
-
-    /**
-     * SpiceUsbDeviceManager::auto-connect-failed:
-     * @manager: the #SpiceUsbDeviceManager that emitted the signal
-     * @device: #SpiceUsbDevice boxed object corresponding to the device which failed to auto connect
-     * @error: #GError describing the reason why the autoconnect failed
-     *
-     * The #SpiceUsbDeviceManager::auto-connect-failed signal is emitted
-     * whenever the auto-connect property is true, and a newly plugged in
-     * device could not be auto-connected.
-     **/
-    signals[AUTO_CONNECT_FAILED] =
-        g_signal_new("auto-connect-failed",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_FIRST,
-                     G_STRUCT_OFFSET(SpiceUsbDeviceManagerClass, auto_connect_failed),
-                     NULL, NULL,
-                     g_cclosure_user_marshal_VOID__BOXED_BOXED,
-                     G_TYPE_NONE,
-                     2,
-                     SPICE_TYPE_USB_DEVICE,
-                     G_TYPE_ERROR);
-
-    /**
-     * SpiceUsbDeviceManager::device-error:
-     * @manager: #SpiceUsbDeviceManager that emitted the signal
-     * @device:  #SpiceUsbDevice boxed object corresponding to the device which has an error
-     * @error:   #GError describing the error
-     *
-     * The #SpiceUsbDeviceManager::device-error signal is emitted whenever an
-     * error happens which causes a device to no longer be available to the
-     * guest.
-     **/
-    signals[DEVICE_ERROR] =
-        g_signal_new("device-error",
-                     G_OBJECT_CLASS_TYPE(gobject_class),
-                     G_SIGNAL_RUN_FIRST,
-                     G_STRUCT_OFFSET(SpiceUsbDeviceManagerClass, device_error),
-                     NULL, NULL,
-                     g_cclosure_user_marshal_VOID__BOXED_BOXED,
-                     G_TYPE_NONE,
-                     2,
-                     SPICE_TYPE_USB_DEVICE,
-                     G_TYPE_ERROR);
-
-    g_type_class_add_private(klass, sizeof(SpiceUsbDeviceManagerPrivate));
-}
-
-#ifdef USE_USBREDIR
-
-/* ------------------------------------------------------------------ */
-/* gudev / libusb Helper functions                                    */
-
-#ifdef USE_GUDEV
-static gboolean spice_usb_device_manager_get_udev_bus_n_address(
-    GUdevDevice *udev, int *bus, int *address)
-{
-    const gchar *bus_str, *address_str;
-
-    *bus = *address = 0;
-
-#ifndef G_OS_WIN32
-    bus_str = g_udev_device_get_property(udev, "BUSNUM");
-    address_str = g_udev_device_get_property(udev, "DEVNUM");
-#else /* Windows -- request vid:pid instead */
-    bus_str = g_udev_device_get_property(udev, "VID");
-    address_str = g_udev_device_get_property(udev, "PID");
-#endif
-    if (bus_str)
-        *bus = atoi(bus_str);
-    if (address_str)
-        *address = atoi(address_str);
-
-    return *bus && *address;
-}
-#endif
-
-static gboolean spice_usb_device_manager_get_device_descriptor(
-    libusb_device *libdev,
-    struct libusb_device_descriptor *desc)
-{
-    int errcode;
-    const gchar *errstr;
-
-    g_return_val_if_fail(libdev != NULL, FALSE);
-    g_return_val_if_fail(desc   != NULL, FALSE);
-
-    errcode = libusb_get_device_descriptor(libdev, desc);
-    if (errcode < 0) {
-        int bus, addr;
-
-        bus = libusb_get_bus_number(libdev);
-        addr = libusb_get_device_address(libdev);
-        errstr = spice_usbutil_libusb_strerror(errcode);
-        g_warning("cannot get device descriptor for (%p) %d.%d -- %s(%d)",
-                  libdev, bus, addr, errstr, errcode);
-        return FALSE;
-    }
-    return TRUE;
-}
-
-
-/**
- * spice_usb_device_get_libusb_device:
- * @device: #SpiceUsbDevice to get the descriptor information of
- *
- * Returns: (transfer none): the %libusb_device associated to %SpiceUsbDevice.
- *
- * Since: 0.27
- **/
-gconstpointer
-spice_usb_device_get_libusb_device(const SpiceUsbDevice *device G_GNUC_UNUSED)
-{
-#ifdef USE_USBREDIR
-#ifndef G_OS_WIN32
-    const SpiceUsbDeviceInfo *info = (const SpiceUsbDeviceInfo *)device;
-
-    g_return_val_if_fail(info != NULL, FALSE);
-
-    return info->libdev;
-#endif
-#endif
-    return NULL;
-}
-
-static gboolean spice_usb_device_manager_get_libdev_vid_pid(
-    libusb_device *libdev, int *vid, int *pid)
-{
-    struct libusb_device_descriptor desc;
-
-    g_return_val_if_fail(libdev != NULL, FALSE);
-    g_return_val_if_fail(vid != NULL, FALSE);
-    g_return_val_if_fail(pid != NULL, FALSE);
-
-    *vid = *pid = 0;
-
-    if (!spice_usb_device_manager_get_device_descriptor(libdev, &desc)) {
-        return FALSE;
-    }
-    *vid = desc.idVendor;
-    *pid = desc.idProduct;
-
-    return TRUE;
-}
-
-/* ------------------------------------------------------------------ */
-/* callbacks                                                          */
-
-static void channel_new(SpiceSession *session, SpiceChannel *channel,
-                        gpointer user_data)
-{
-    SpiceUsbDeviceManager *self = user_data;
-
-    if (!SPICE_IS_USBREDIR_CHANNEL(channel))
-        return;
-
-    spice_usbredir_channel_set_context(SPICE_USBREDIR_CHANNEL(channel),
-                                       self->priv->context);
-    spice_channel_connect(channel);
-    g_ptr_array_add(self->priv->channels, channel);
-
-    spice_usb_device_manager_check_redir_on_connect(self, channel);
-
-    /*
-     * add a reference to ourself, to make sure the libusb context is
-     * alive as long as the channel is.
-     * TODO: moving to gusb could help here too.
-     */
-    g_object_ref(self);
-    g_object_weak_ref(G_OBJECT(channel), (GWeakNotify)g_object_unref, self);
-}
-
-static void channel_destroy(SpiceSession *session, SpiceChannel *channel,
-                            gpointer user_data)
-{
-    SpiceUsbDeviceManager *self = user_data;
-
-    if (!SPICE_IS_USBREDIR_CHANNEL(channel))
-        return;
-
-    g_ptr_array_remove(self->priv->channels, channel);
-}
-
-static void spice_usb_device_manager_auto_connect_cb(GObject      *gobject,
-                                                     GAsyncResult *res,
-                                                     gpointer      user_data)
-{
-    SpiceUsbDeviceManager *self = SPICE_USB_DEVICE_MANAGER(gobject);
-    SpiceUsbDevice *device = user_data;
-    GError *err = NULL;
-
-    spice_usb_device_manager_connect_device_finish(self, res, &err);
-    if (err) {
-        gchar *desc = spice_usb_device_get_description(device, NULL);
-        g_prefix_error(&err, "Could not auto-redirect %s: ", desc);
-        g_free(desc);
-
-        SPICE_DEBUG("%s", err->message);
-        g_signal_emit(self, signals[AUTO_CONNECT_FAILED], 0, device, err);
-        g_error_free(err);
-    }
-    spice_usb_device_unref(device);
-}
-
-#ifndef G_OS_WIN32 /* match functions for Linux -- match by bus.addr */
-static gboolean
-spice_usb_device_manager_device_match(SpiceUsbDevice *device,
-                                      const int bus, const int address)
-{
-    return (spice_usb_device_get_busnum(device) == bus &&
-            spice_usb_device_get_devaddr(device) == address);
-}
-
-#ifdef USE_GUDEV
-static gboolean
-spice_usb_device_manager_libdev_match(libusb_device *libdev,
-                                      const int bus, const int address)
-{
-    return (libusb_get_bus_number(libdev) == bus &&
-            libusb_get_device_address(libdev) == address);
-}
-#endif
-
-#else /* Win32 -- match functions for Windows -- match by vid:pid */
-static gboolean
-spice_usb_device_manager_device_match(SpiceUsbDevice *device,
-                                      const int vid, const int pid)
-{
-    return (spice_usb_device_get_vid(device) == vid &&
-            spice_usb_device_get_pid(device) == pid);
-}
-
-static gboolean
-spice_usb_device_manager_libdev_match(libusb_device *libdev,
-                                      const int vid, const int pid)
-{
-    int vid2, pid2;
-
-    if (!spice_usb_device_manager_get_libdev_vid_pid(libdev, &vid2, &pid2)) {
-        return FALSE;
-    }
-    return (vid == vid2 && pid == pid2);
-}
-#endif /* of Win32 -- match functions */
-
-static SpiceUsbDevice*
-spice_usb_device_manager_find_device(SpiceUsbDeviceManager *self,
-                                     const int bus, const int address)
-{
-    SpiceUsbDeviceManagerPrivate *priv = self->priv;
-    SpiceUsbDevice *curr, *device = NULL;
-    guint i;
-
-    for (i = 0; i < priv->devices->len; i++) {
-        curr = g_ptr_array_index(priv->devices, i);
-        if (spice_usb_device_manager_device_match(curr, bus, address)) {
-            device = curr;
-            break;
-        }
-    }
-    return device;
-}
-
-static void spice_usb_device_manager_add_dev(SpiceUsbDeviceManager  *self,
-                                             libusb_device          *libdev)
-{
-    SpiceUsbDeviceManagerPrivate *priv = self->priv;
-    struct libusb_device_descriptor desc;
-    SpiceUsbDevice *device;
-
-    if (!spice_usb_device_manager_get_device_descriptor(libdev, &desc))
-        return;
-
-    /* Skip hubs */
-    if (desc.bDeviceClass == LIBUSB_CLASS_HUB)
-        return;
-
-    device = (SpiceUsbDevice*)spice_usb_device_new(libdev);
-    if (!device)
-        return;
-
-    g_ptr_array_add(priv->devices, device);
-
-    if (priv->auto_connect) {
-        gboolean can_redirect, auto_ok;
-
-        can_redirect = spice_usb_device_manager_can_redirect_device(
-                                        self, device, NULL);
-
-        auto_ok = usbredirhost_check_device_filter(
-                            priv->auto_conn_filter_rules,
-                            priv->auto_conn_filter_rules_count,
-                            libdev, 0) == 0;
-
-        if (can_redirect && auto_ok)
-            spice_usb_device_manager_connect_device_async(self,
-                                   device, NULL,
-                                   spice_usb_device_manager_auto_connect_cb,
-                                   spice_usb_device_ref(device));
-    }
-
-    SPICE_DEBUG("device added %p", device);
-    g_signal_emit(self, signals[DEVICE_ADDED], 0, device);
-}
-
-static void spice_usb_device_manager_remove_dev(SpiceUsbDeviceManager *self,
-                                                int bus, int address)
-{
-    SpiceUsbDeviceManagerPrivate *priv = self->priv;
-    SpiceUsbDevice *device;
-
-    device = spice_usb_device_manager_find_device(self, bus, address);
-    if (!device) {
-        g_warning("Could not find USB device to remove " DEV_ID_FMT,
-                  bus, address);
-        return;
-    }
-
-#ifdef G_OS_WIN32
-    const guint8 state = spice_usb_device_get_state(device);
-    if ((state == SPICE_USB_DEVICE_STATE_INSTALLING) ||
-        (state == SPICE_USB_DEVICE_STATE_UNINSTALLING)) {
-        SPICE_DEBUG("skipping " DEV_ID_FMT ". It is un/installing its driver",
-                    bus, address);
-        return;
-    }
-#endif
-
-    spice_usb_device_manager_disconnect_device(self, device);
-
-    SPICE_DEBUG("device removed %p", device);
-    spice_usb_device_ref(device);
-    g_ptr_array_remove(priv->devices, device);
-    g_signal_emit(self, signals[DEVICE_REMOVED], 0, device);
-    spice_usb_device_unref(device);
-}
-
-#ifdef USE_GUDEV
-static void spice_usb_device_manager_add_udev(SpiceUsbDeviceManager  *self,
-                                              GUdevDevice            *udev)
-{
-    SpiceUsbDeviceManagerPrivate *priv = self->priv;
-    libusb_device *libdev = NULL, **dev_list = NULL;
-    SpiceUsbDevice *device;
-    const gchar *devtype;
-    int i, bus, address;
-
-    devtype = g_udev_device_get_property(udev, "DEVTYPE");
-    /* Check if this is a usb device (and not an interface) */
-    if (!devtype || strcmp(devtype, "usb_device"))
-        return;
-
-    if (!spice_usb_device_manager_get_udev_bus_n_address(udev, &bus, &address)) {
-        g_warning("USB device without bus number or device address");
-        return;
-    }
-
-    device = spice_usb_device_manager_find_device(self, bus, address);
-    if (device) {
-        SPICE_DEBUG("USB device 0x%04x:0x%04x at %d.%d already exists, ignored",
-                    spice_usb_device_get_vid(device),
-                    spice_usb_device_get_pid(device),
-                    spice_usb_device_get_busnum(device),
-                    spice_usb_device_get_devaddr(device));
-        return;
-    }
-
-    if (priv->coldplug_list)
-        dev_list = priv->coldplug_list;
-    else
-        libusb_get_device_list(priv->context, &dev_list);
-
-    for (i = 0; dev_list && dev_list[i]; i++) {
-        if (spice_usb_device_manager_libdev_match(dev_list[i], bus, address)) {
-            libdev = dev_list[i];
-            break;
-        }
-    }
-
-    if (libdev)
-        spice_usb_device_manager_add_dev(self, libdev);
-    else
-        g_warning("Could not find USB device to add " DEV_ID_FMT,
-                  bus, address);
-
-    if (!priv->coldplug_list)
-        libusb_free_device_list(dev_list, 1);
-}
-
-static void spice_usb_device_manager_remove_udev(SpiceUsbDeviceManager  *self,
-                                                 GUdevDevice            *udev)
-{
-    int bus, address;
-
-    if (!spice_usb_device_manager_get_udev_bus_n_address(udev, &bus, &address))
-        return;
-
-    spice_usb_device_manager_remove_dev(self, bus, address);
-}
-
-static void spice_usb_device_manager_uevent_cb(GUdevClient     *client,
-                                               const gchar     *action,
-                                               GUdevDevice     *udevice,
-                                               gpointer         user_data)
-{
-    SpiceUsbDeviceManager *self = SPICE_USB_DEVICE_MANAGER(user_data);
-
-    if (g_str_equal(action, "add"))
-        spice_usb_device_manager_add_udev(self, udevice);
-    else if (g_str_equal (action, "remove"))
-        spice_usb_device_manager_remove_udev(self, udevice);
-}
-#else
-struct hotplug_idle_cb_args {
-    SpiceUsbDeviceManager *self;
-    libusb_device *device;
-    libusb_hotplug_event event;
-};
-
-static gboolean spice_usb_device_manager_hotplug_idle_cb(gpointer user_data)
-{
-    struct hotplug_idle_cb_args *args = user_data;
-    SpiceUsbDeviceManager *self = SPICE_USB_DEVICE_MANAGER(args->self);
-
-    switch (args->event) {
-    case LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED:
-        spice_usb_device_manager_add_dev(self, args->device);
-        break;
-    case LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT:
-        spice_usb_device_manager_remove_dev(self,
-                                    libusb_get_bus_number(args->device),
-                                    libusb_get_device_address(args->device));
-        break;
-    }
-    libusb_unref_device(args->device);
-    g_object_unref(self);
-    g_free(args);
-    return FALSE;
-}
-
-/* Can be called from both the main-thread as well as the event_thread */
-static int spice_usb_device_manager_hotplug_cb(libusb_context       *ctx,
-                                               libusb_device        *device,
-                                               libusb_hotplug_event  event,
-                                               void                 *user_data)
-{
-    SpiceUsbDeviceManager *self = SPICE_USB_DEVICE_MANAGER(user_data);
-    struct hotplug_idle_cb_args *args = g_malloc0(sizeof(*args));
-
-    args->self = g_object_ref(self);
-    args->device = libusb_ref_device(device);
-    args->event = event;
-    g_idle_add(spice_usb_device_manager_hotplug_idle_cb, args);
-    return 0;
-}
-#endif
-
-static void spice_usb_device_manager_channel_connect_cb(
-    GObject *gobject, GAsyncResult *channel_res, gpointer user_data)
-{
-    SpiceUsbredirChannel *channel = SPICE_USBREDIR_CHANNEL(gobject);
-    GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT(user_data);
-    GError *err = NULL;
-
-    spice_usbredir_channel_connect_device_finish(channel, channel_res, &err);
-    if (err) {
-        g_simple_async_result_take_error(result, err);
-    }
-    g_simple_async_result_complete(result);
-    g_object_unref(result);
-}
-
-#ifdef G_OS_WIN32
-
-typedef struct _UsbInstallCbInfo {
-    SpiceUsbDeviceManager *manager;
-    SpiceUsbDevice        *device;
-    SpiceWinUsbDriver     *installer;
-    GCancellable          *cancellable;
-    GAsyncReadyCallback   callback;
-    gpointer              user_data;
-    gboolean              is_install;
-} UsbInstallCbInfo;
-
-/**
- * spice_usb_device_manager_drv_install_cb:
- * @gobject: #SpiceWinUsbDriver in charge of installing the driver
- * @res: #GAsyncResult of async win usb driver installation
- * @user_data: #SpiceUsbDeviceManager requested the installation
- *
- * Called when an Windows libusb driver installation completed.
- *
- * If the driver installation was successful, continue with USB
- * device redirection
- *
- * Always call _spice_usb_device_manager_connect_device_async.
- * When installation fails, libusb_open fails too, but cleanup would be better.
- */
-static void spice_usb_device_manager_drv_install_cb(GObject *gobject,
-                                                    GAsyncResult *res,
-                                                    gpointer user_data)
-{
-    SpiceUsbDeviceManager *self;
-    SpiceWinUsbDriver *installer;
-    gint status;
-    GError *err = NULL;
-    SpiceUsbDevice *device;
-    UsbInstallCbInfo *cbinfo;
-    GCancellable *cancellable;
-    GAsyncReadyCallback callback;
-    gboolean is_install;
-    const gchar *opstr;
-
-    g_return_if_fail(user_data != NULL);
-
-    cbinfo = user_data;
-    self        = cbinfo->manager;
-    device      = cbinfo->device;
-    installer   = cbinfo->installer;
-    cancellable = cbinfo->cancellable;
-    callback    = cbinfo->callback;
-    user_data   = cbinfo->user_data;
-    is_install  = cbinfo->is_install;
-
-    g_free(cbinfo);
-
-    g_return_if_fail(SPICE_IS_USB_DEVICE_MANAGER(self));
-    g_return_if_fail(SPICE_IS_WIN_USB_DRIVER(installer));
-    g_return_if_fail(device!= NULL);
-
-    opstr = is_install ? "install" : "uninstall";
-    SPICE_DEBUG("Win USB driver %s finished", opstr);
-
-    status = spice_win_usb_driver_install_finish(installer, res, &err);
-
-    spice_usb_device_unref(device);
-
-    if (is_install) {
-        spice_usb_device_set_state(device, SPICE_USB_DEVICE_STATE_INSTALLED);
-    } else {
-        spice_usb_device_set_state(device, SPICE_USB_DEVICE_STATE_NONE);
-    }
-
-    if (err) {
-        g_warning("win usb driver %s failed -- %s", opstr, err->message);
-        g_error_free(err);
-    }
-
-    if (!status) {
-        g_warning("failed to %s win usb driver (status=0)", opstr);
-    }
-
-    if (! is_install) {
-        return;
-    }
-
-    /* device is already ref'ed */
-    _spice_usb_device_manager_connect_device_async(self,
-                                                   device,
-                                                   cancellable,
-                                                   callback,
-                                                   user_data);
-
-}
-#endif
-
-/* ------------------------------------------------------------------ */
-/* private api                                                        */
-
-static gpointer spice_usb_device_manager_usb_ev_thread(gpointer user_data)
-{
-    SpiceUsbDeviceManager *self = SPICE_USB_DEVICE_MANAGER(user_data);
-    SpiceUsbDeviceManagerPrivate *priv = self->priv;
-    int rc;
-
-    while (priv->event_thread_run) {
-        rc = libusb_handle_events(priv->context);
-        if (rc && rc != LIBUSB_ERROR_INTERRUPTED) {
-            const char *desc = spice_usbutil_libusb_strerror(rc);
-            g_warning("Error handling USB events: %s [%i]", desc, rc);
-            break;
-        }
-    }
-
-    return NULL;
-}
-
-gboolean spice_usb_device_manager_start_event_listening(
-    SpiceUsbDeviceManager *self, GError **err)
-{
-    SpiceUsbDeviceManagerPrivate *priv = self->priv;
-
-    g_return_val_if_fail(err == NULL || *err == NULL, FALSE);
-
-    priv->event_listeners++;
-    if (priv->event_listeners > 1)
-        return TRUE;
-
-    /* We don't join the thread when we stop event listening, as the
-       libusb_handle_events call in the thread won't exit until the
-       libusb_close call for the device is made from usbredirhost_close. */
-    if (priv->event_thread) {
-         g_thread_join(priv->event_thread);
-         priv->event_thread = NULL;
-    }
-    priv->event_thread_run = TRUE;
-#if GLIB_CHECK_VERSION(2,31,19)
-    priv->event_thread = g_thread_new("usb_ev_thread",
-                                      spice_usb_device_manager_usb_ev_thread,
-                                      self);
-#else
-    priv->event_thread = g_thread_create(spice_usb_device_manager_usb_ev_thread,
-                                         self, TRUE, err);
-#endif
-    return priv->event_thread != NULL;
-}
-
-void spice_usb_device_manager_stop_event_listening(
-    SpiceUsbDeviceManager *self)
-{
-    SpiceUsbDeviceManagerPrivate *priv = self->priv;
-
-    g_return_if_fail(priv->event_listeners > 0);
-
-    priv->event_listeners--;
-    if (priv->event_listeners == 0)
-        priv->event_thread_run = FALSE;
-}
-
-static void spice_usb_device_manager_check_redir_on_connect(
-    SpiceUsbDeviceManager *self, SpiceChannel *channel)
-{
-    SpiceUsbDeviceManagerPrivate *priv = self->priv;
-    GSimpleAsyncResult *result;
-    SpiceUsbDevice *device;
-    libusb_device *libdev;
-    guint i;
-
-    if (priv->redirect_on_connect == NULL)
-        return;
-
-    for (i = 0; i < priv->devices->len; i++) {
-        device = g_ptr_array_index(priv->devices, i);
-
-        if (spice_usb_device_manager_is_device_connected(self, device))
-            continue;
-
-        libdev = spice_usb_device_manager_device_to_libdev(self, device);
-#ifdef G_OS_WIN32
-        if (libdev == NULL)
-            continue;
-#endif
-        if (usbredirhost_check_device_filter(
-                            priv->redirect_on_connect_rules,
-                            priv->redirect_on_connect_rules_count,
-                            libdev, 0) == 0) {
-            /* Note: re-uses spice_usb_device_manager_connect_device_async's
-               completion handling code! */
-            result = g_simple_async_result_new(G_OBJECT(self),
-                               spice_usb_device_manager_auto_connect_cb,
-                               spice_usb_device_ref(device),
-                               spice_usb_device_manager_connect_device_async);
-            spice_usbredir_channel_connect_device_async(
-                               SPICE_USBREDIR_CHANNEL(channel),
-                               libdev, device, NULL,
-                               spice_usb_device_manager_channel_connect_cb,
-                               result);
-            libusb_unref_device(libdev);
-            return; /* We've taken the channel! */
-        }
-
-        libusb_unref_device(libdev);
-    }
-}
-
-void spice_usb_device_manager_device_error(
-    SpiceUsbDeviceManager *self, SpiceUsbDevice *device, GError *err)
-{
-    g_return_if_fail(SPICE_IS_USB_DEVICE_MANAGER(self));
-    g_return_if_fail(device != NULL);
-
-    g_signal_emit(self, signals[DEVICE_ERROR], 0, device, err);
-}
-#endif
-
-static SpiceUsbredirChannel *spice_usb_device_manager_get_channel_for_dev(
-    SpiceUsbDeviceManager *manager, SpiceUsbDevice *device)
-{
-#ifdef USE_USBREDIR
-    SpiceUsbDeviceManagerPrivate *priv = manager->priv;
-    guint i;
-
-    for (i = 0; i < priv->channels->len; i++) {
-        SpiceUsbredirChannel *channel = g_ptr_array_index(priv->channels, i);
-        libusb_device *libdev = spice_usbredir_channel_get_device(channel);
-        if (spice_usb_device_equal_libdev(device, libdev))
-            return channel;
-    }
-#endif
-    return NULL;
-}
-
-/* ------------------------------------------------------------------ */
-/* public api                                                         */
-
-/**
- * spice_usb_device_manager_get_devices_with_filter:
- * @manager: the #SpiceUsbDeviceManager manager
- * @filter: (allow-none): filter string for selecting which devices to return,
- *      see #SpiceUsbDeviceManager:auto-connect-filter for the f ilter
- *      string format
- *
- * Returns: (element-type SpiceUsbDevice) (transfer full): a
- * %GPtrArray array of %SpiceUsbDevice
- *
- * Since: 0.20
- */
-GPtrArray* spice_usb_device_manager_get_devices_with_filter(
-    SpiceUsbDeviceManager *self, const gchar *filter)
-{
-    GPtrArray *devices_copy = NULL;
-
-    g_return_val_if_fail(SPICE_IS_USB_DEVICE_MANAGER(self), NULL);
-
-#ifdef USE_USBREDIR
-    SpiceUsbDeviceManagerPrivate *priv = self->priv;
-    struct usbredirfilter_rule *rules = NULL;;
-    int r, count = 0;
-    guint i;
-
-    if (filter) {
-        r = usbredirfilter_string_to_rules(filter, ",", "|", &rules, &count);
-        if (r) {
-            if (r == -ENOMEM)
-                g_error("Failed to allocate memory for filter");
-            g_warning("Error parsing filter, ignoring");
-            rules = NULL;
-            count = 0;
-        }
-    }
-
-    devices_copy = g_ptr_array_new_with_free_func((GDestroyNotify)
-                                                  spice_usb_device_unref);
-    for (i = 0; i < priv->devices->len; i++) {
-        SpiceUsbDevice *device = g_ptr_array_index(priv->devices, i);
-
-        if (rules) {
-            libusb_device *libdev =
-                spice_usb_device_manager_device_to_libdev(self, device);
-#ifdef G_OS_WIN32
-            if (libdev == NULL)
-                continue;
-#endif
-            if (usbredirhost_check_device_filter(rules, count, libdev, 0) != 0)
-                continue;
-        }
-        g_ptr_array_add(devices_copy, spice_usb_device_ref(device));
-    }
-
-    free(rules);
-#endif
-
-    return devices_copy;
-}
-
-/**
- * spice_usb_device_manager_get_devices:
- * @manager: the #SpiceUsbDeviceManager manager
- *
- * Returns: (element-type SpiceUsbDevice) (transfer full): a %GPtrArray array of %SpiceUsbDevice
- */
-GPtrArray* spice_usb_device_manager_get_devices(SpiceUsbDeviceManager *self)
-{
-    return spice_usb_device_manager_get_devices_with_filter(self, NULL);
-}
-
-/**
- * spice_usb_device_manager_is_device_connected:
- * @manager: the #SpiceUsbDeviceManager manager
- * @device: a #SpiceUsbDevice
- *
- * Returns: %TRUE if @device has an associated USB redirection channel
- */
-gboolean spice_usb_device_manager_is_device_connected(SpiceUsbDeviceManager *self,
-                                                      SpiceUsbDevice *device)
-{
-    g_return_val_if_fail(SPICE_IS_USB_DEVICE_MANAGER(self), FALSE);
-    g_return_val_if_fail(device != NULL, FALSE);
-
-    return !!spice_usb_device_manager_get_channel_for_dev(self, device);
-}
-
-/**
- * spice_usb_device_manager_connect_device_async:
- * @manager: the #SpiceUsbDeviceManager manager
- * @device: a #SpiceUsbDevice to redirect
- * @cancellable: a #GCancellable or NULL
- * @callback: a #GAsyncReadyCallback to call when the request is satisfied
- * @user_data: data to pass to callback
- */
-static void
-_spice_usb_device_manager_connect_device_async(SpiceUsbDeviceManager *self,
-                                               SpiceUsbDevice *device,
-                                               GCancellable *cancellable,
-                                               GAsyncReadyCallback callback,
-                                               gpointer user_data)
-{
-    GSimpleAsyncResult *result;
-
-    g_return_if_fail(SPICE_IS_USB_DEVICE_MANAGER(self));
-    g_return_if_fail(device != NULL);
-
-    SPICE_DEBUG("connecting device %p", device);
-
-    result = g_simple_async_result_new(G_OBJECT(self), callback, user_data,
-                               spice_usb_device_manager_connect_device_async);
-
-#ifdef USE_USBREDIR
-    SpiceUsbDeviceManagerPrivate *priv = self->priv;
-    libusb_device *libdev;
-    guint i;
-
-    if (spice_usb_device_manager_is_device_connected(self, device)) {
-        g_simple_async_result_set_error(result,
-                            SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
-                            "Cannot connect an already connected usb device");
-        goto done;
-    }
-
-    for (i = 0; i < priv->channels->len; i++) {
-        SpiceUsbredirChannel *channel = g_ptr_array_index(priv->channels, i);
-
-        if (spice_usbredir_channel_get_device(channel))
-            continue; /* Skip already used channels */
-
-        libdev = spice_usb_device_manager_device_to_libdev(self, device);
-#ifdef G_OS_WIN32
-        if (libdev == NULL) {
-            /* Most likely, the device was plugged out at driver installation
-             * time, and its remove-device event was ignored.
-             * So remove the device now
-             */
-            SPICE_DEBUG("libdev does not exist for %p -- removing", device);
-            spice_usb_device_ref(device);
-            g_ptr_array_remove(priv->devices, device);
-            g_signal_emit(self, signals[DEVICE_REMOVED], 0, device);
-            spice_usb_device_unref(device);
-            g_simple_async_result_set_error(result,
-                                            SPICE_CLIENT_ERROR,
-                                            SPICE_CLIENT_ERROR_FAILED,
-                                            _("Device was not found"));
-            goto done;
-        }
-#endif
-        spice_usbredir_channel_connect_device_async(channel,
-                                 libdev,
-                                 device,
-                                 cancellable,
-                                 spice_usb_device_manager_channel_connect_cb,
-                                 result);
-        libusb_unref_device(libdev);
-        return;
-    }
-#endif
-
-    g_simple_async_result_set_error(result,
-                            SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
-                            _("No free USB channel"));
-#ifdef USE_USBREDIR
-done:
-#endif
-    g_simple_async_result_complete_in_idle(result);
-    g_object_unref(result);
-}
-
-
-void spice_usb_device_manager_connect_device_async(SpiceUsbDeviceManager *self,
-                                             SpiceUsbDevice *device,
-                                             GCancellable *cancellable,
-                                             GAsyncReadyCallback callback,
-                                             gpointer user_data)
-{
-
-#if defined(USE_USBREDIR) && defined(G_OS_WIN32)
-    SpiceWinUsbDriver *installer;
-    UsbInstallCbInfo *cbinfo;
-
-    spice_usb_device_set_state(device, SPICE_USB_DEVICE_STATE_INSTALLING);
-    if (! self->priv->installer) {
-        self->priv->installer = spice_win_usb_driver_new();
-    }
-    installer = self->priv->installer;
-    cbinfo = g_new0(UsbInstallCbInfo, 1);
-    cbinfo->manager     = self;
-    cbinfo->device      = spice_usb_device_ref(device);
-    cbinfo->installer   = installer;
-    cbinfo->cancellable = cancellable;
-    cbinfo->callback    = callback;
-    cbinfo->user_data   = user_data;
-    cbinfo->is_install  = TRUE;
-
-    spice_win_usb_driver_install(installer, device, cancellable,
-                                 spice_usb_device_manager_drv_install_cb,
-                                 cbinfo);
-#else
-    _spice_usb_device_manager_connect_device_async(self,
-                                                   device,
-                                                   cancellable,
-                                                   callback,
-                                                   user_data);
-#endif
-}
-
-gboolean spice_usb_device_manager_connect_device_finish(
-    SpiceUsbDeviceManager *self, GAsyncResult *res, GError **err)
-{
-    GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT(res);
-
-    g_return_val_if_fail(g_simple_async_result_is_valid(res, G_OBJECT(self),
-                               spice_usb_device_manager_connect_device_async),
-                         FALSE);
-
-    if (g_simple_async_result_propagate_error(simple, err))
-        return FALSE;
-
-    return TRUE;
-}
-
-/**
- * spice_usb_device_manager_disconnect_device:
- * @manager: the #SpiceUsbDeviceManager manager
- * @device: a #SpiceUsbDevice to disconnect
- *
- * Returns: %TRUE if @device has an associated USB redirection channel
- */
-void spice_usb_device_manager_disconnect_device(SpiceUsbDeviceManager *self,
-                                                SpiceUsbDevice *device)
-{
-    g_return_if_fail(SPICE_IS_USB_DEVICE_MANAGER(self));
-    g_return_if_fail(device != NULL);
-
-    SPICE_DEBUG("disconnecting device %p", device);
-
-#ifdef USE_USBREDIR
-    SpiceUsbredirChannel *channel;
-
-    channel = spice_usb_device_manager_get_channel_for_dev(self, device);
-    if (channel)
-        spice_usbredir_channel_disconnect_device(channel);
-
-#ifdef G_OS_WIN32
-    SpiceWinUsbDriver *installer;
-    UsbInstallCbInfo *cbinfo;
-    guint8 state;
-
-    g_warn_if_fail(device != NULL);
-    g_warn_if_fail(self->priv->installer != NULL);
-
-    state = spice_usb_device_get_state(device);
-    if ((state != SPICE_USB_DEVICE_STATE_INSTALLED) &&
-        (state != SPICE_USB_DEVICE_STATE_CONNECTED)) {
-        return;
-    }
-
-    spice_usb_device_set_state(device, SPICE_USB_DEVICE_STATE_UNINSTALLING);
-    if (! self->priv->installer) {
-        self->priv->installer = spice_win_usb_driver_new();
-    }
-    installer = self->priv->installer;
-    cbinfo = g_new0(UsbInstallCbInfo, 1);
-    cbinfo->manager     = self;
-    cbinfo->device      = spice_usb_device_ref(device);
-    cbinfo->installer   = installer;
-    cbinfo->cancellable = NULL;
-    cbinfo->callback    = NULL;
-    cbinfo->user_data   = NULL;
-    cbinfo->is_install  = FALSE;
-
-    spice_win_usb_driver_uninstall(installer, device, NULL,
-                                   spice_usb_device_manager_drv_install_cb,
-                                   cbinfo);
-#endif
-
-#endif
-}
-
-gboolean
-spice_usb_device_manager_can_redirect_device(SpiceUsbDeviceManager  *self,
-                                             SpiceUsbDevice         *device,
-                                             GError                **err)
-{
-#ifdef USE_USBREDIR
-    const struct usbredirfilter_rule *guest_filter_rules = NULL;
-    SpiceUsbDeviceManagerPrivate *priv = self->priv;
-    int i, guest_filter_rules_count;
-
-    g_return_val_if_fail(SPICE_IS_USB_DEVICE_MANAGER(self), FALSE);
-    g_return_val_if_fail(device != NULL, FALSE);
-    g_return_val_if_fail(err == NULL || *err == NULL, FALSE);
-
-    if (!spice_session_get_usbredir_enabled(priv->session)) {
-        g_set_error_literal(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
-                            _("USB redirection is disabled"));
-        return FALSE;
-    }
-
-    if (!priv->channels->len) {
-        g_set_error_literal(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
-                            _("The connected VM is not configured for USB redirection"));
-        return FALSE;
-    }
-
-    /* Skip the other checks for already connected devices */
-    if (spice_usb_device_manager_is_device_connected(self, device))
-        return TRUE;
-
-    /* We assume all channels have the same filter, so we just take the
-       filter from the first channel */
-    spice_usbredir_channel_get_guest_filter(
-        g_ptr_array_index(priv->channels, 0),
-        &guest_filter_rules, &guest_filter_rules_count);
-
-    if (guest_filter_rules) {
-        gboolean filter_ok;
-        libusb_device *libdev;
-
-        libdev = spice_usb_device_manager_device_to_libdev(self, device);
-#ifdef G_OS_WIN32
-        if (libdev == NULL) {
-            g_set_error_literal(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
-                                _("Some USB devices were not found"));
-            return FALSE;
-        }
-#endif
-        filter_ok = (usbredirhost_check_device_filter(
-                            guest_filter_rules, guest_filter_rules_count,
-                            libdev, 0) == 0);
-        libusb_unref_device(libdev);
-        if (!filter_ok) {
-            g_set_error_literal(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
-                                _("Some USB devices are blocked by host policy"));
-            return FALSE;
-        }
-    }
-
-    /* Check if there are free channels */
-    for (i = 0; i < priv->channels->len; i++) {
-        SpiceUsbredirChannel *channel = g_ptr_array_index(priv->channels, i);
-
-        if (!spice_usbredir_channel_get_device(channel))
-            break;
-    }
-    if (i == priv->channels->len) {
-        g_set_error_literal(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
-                            _("There are no free USB channels"));
-        return FALSE;
-    }
-
-    return TRUE;
-#else
-    g_set_error_literal(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
-                        _("USB redirection support not compiled in"));
-    return FALSE;
-#endif
-}
-
-/**
- * spice_usb_device_get_description:
- * @device: #SpiceUsbDevice to get the description of
- * @format: (allow-none): an optional printf() format string with
- * positional parameters
- *
- * Get a string describing the device which is suitable as a description of
- * the device for the end user. The returned string should be freed with
- * g_free() when no longer needed.
- *
- * The @format positional parameters are the following:
- * - '%%1$s' manufacturer
- * - '%%2$s' product
- * - '%%3$s' descriptor (a [vendor_id:product_id] string)
- * - '%%4$d' bus
- * - '%%5$d' address
- *
- * (the default format string is "%%s %%s %%s at %%d-%%d")
- *
- * Returns: a newly-allocated string holding the description, or %NULL if failed
- */
-gchar *spice_usb_device_get_description(SpiceUsbDevice *device, const gchar *format)
-{
-#ifdef USE_USBREDIR
-    int bus, address, vid, pid;
-    gchar *description, *descriptor, *manufacturer = NULL, *product = NULL;
-
-    g_return_val_if_fail(device != NULL, NULL);
-
-    bus     = spice_usb_device_get_busnum(device);
-    address = spice_usb_device_get_devaddr(device);
-    vid     = spice_usb_device_get_vid(device);
-    pid     = spice_usb_device_get_pid(device);
-
-    if ((vid > 0) && (pid > 0)) {
-        descriptor = g_strdup_printf("[%04x:%04x]", vid, pid);
-    } else {
-        descriptor = g_strdup("");
-    }
-
-    spice_usb_util_get_device_strings(bus, address, vid, pid,
-                                      &manufacturer, &product);
-
-    if (!format)
-        format = _("%s %s %s at %d-%d");
-
-    description = g_strdup_printf(format, manufacturer, product, descriptor, bus, address);
-
-    g_free(manufacturer);
-    g_free(descriptor);
-    g_free(product);
-
-    return description;
-#else
-    return NULL;
-#endif
-}
-
-
-
-#ifdef USE_USBREDIR
-/*
- * SpiceUsbDeviceInfo
- */
-static SpiceUsbDeviceInfo *spice_usb_device_new(libusb_device *libdev)
-{
-    SpiceUsbDeviceInfo *info;
-    int vid, pid;
-    guint8 bus, addr;
-
-    g_return_val_if_fail(libdev != NULL, NULL);
-
-    bus = libusb_get_bus_number(libdev);
-    addr = libusb_get_device_address(libdev);
-
-    if (!spice_usb_device_manager_get_libdev_vid_pid(libdev, &vid, &pid)) {
-        return NULL;
-    }
-
-    info = g_new0(SpiceUsbDeviceInfo, 1);
-
-    info->busnum  = bus;
-    info->devaddr = addr;
-    info->vid = vid;
-    info->pid = pid;
-    info->ref = 1;
-#ifndef G_OS_WIN32
-    info->libdev = libusb_ref_device(libdev);
-#endif
-
-    return info;
-}
-
-guint8 spice_usb_device_get_busnum(const SpiceUsbDevice *device)
-{
-    const SpiceUsbDeviceInfo *info = (const SpiceUsbDeviceInfo *)device;
-
-    g_return_val_if_fail(info != NULL, 0);
-
-    return info->busnum;
-}
-
-guint8 spice_usb_device_get_devaddr(const SpiceUsbDevice *device)
-{
-    const SpiceUsbDeviceInfo *info = (const SpiceUsbDeviceInfo *)device;
-
-    g_return_val_if_fail(info != NULL, 0);
-
-    return info->devaddr;
-}
-
-guint16 spice_usb_device_get_vid(const SpiceUsbDevice *device)
-{
-    const SpiceUsbDeviceInfo *info = (const SpiceUsbDeviceInfo *)device;
-
-    g_return_val_if_fail(info != NULL, 0);
-
-    return info->vid;
-}
-
-guint16 spice_usb_device_get_pid(const SpiceUsbDevice *device)
-{
-    const SpiceUsbDeviceInfo *info = (const SpiceUsbDeviceInfo *)device;
-
-    g_return_val_if_fail(info != NULL, 0);
-
-    return info->pid;
-}
-
-#ifdef G_OS_WIN32
-void spice_usb_device_set_state(SpiceUsbDevice *device, guint8 state)
-{
-    SpiceUsbDeviceInfo *info = (SpiceUsbDeviceInfo *)device;
-
-    g_return_if_fail(info != NULL);
-
-    info->state = state;
-}
-
-guint8 spice_usb_device_get_state(SpiceUsbDevice *device)
-{
-    SpiceUsbDeviceInfo *info = (SpiceUsbDeviceInfo *)device;
-
-    g_return_val_if_fail(info != NULL, 0);
-
-    return info->state;
-}
-#endif
-
-static SpiceUsbDevice *spice_usb_device_ref(SpiceUsbDevice *device)
-{
-    SpiceUsbDeviceInfo *info = (SpiceUsbDeviceInfo *)device;
-
-    g_return_val_if_fail(info != NULL, NULL);
-    g_atomic_int_inc(&info->ref);
-    return device;
-}
-
-static void spice_usb_device_unref(SpiceUsbDevice *device)
-{
-    gboolean ref_count_is_0;
-
-    SpiceUsbDeviceInfo *info = (SpiceUsbDeviceInfo *)device;
-
-    g_return_if_fail(info != NULL);
-
-    ref_count_is_0 = g_atomic_int_dec_and_test(&info->ref);
-    if (ref_count_is_0) {
-#ifndef G_OS_WIN32
-        libusb_unref_device(info->libdev);
-#endif
-        g_free(info);
-    }
-}
-
-#ifndef G_OS_WIN32 /* Linux -- directly compare libdev */
-static gboolean
-spice_usb_device_equal_libdev(SpiceUsbDevice *device,
-                              libusb_device  *libdev)
-{
-    SpiceUsbDeviceInfo *info = (SpiceUsbDeviceInfo *)device;
-
-    if ((device == NULL) || (libdev == NULL))
-        return FALSE;
-
-    return info->libdev == libdev;
-}
-#else /* Windows -- compare vid:pid of device and libdev */
-static gboolean
-spice_usb_device_equal_libdev(SpiceUsbDevice *device,
-                              libusb_device  *libdev)
-{
-    int vid1, vid2, pid1, pid2;
-
-    if ((device == NULL) || (libdev == NULL))
-        return FALSE;
-
-    vid1 = spice_usb_device_get_vid(device);
-    pid1 = spice_usb_device_get_pid(device);
-
-    if (!spice_usb_device_manager_get_libdev_vid_pid(libdev, &vid2, &pid2)) {
-        return FALSE;
-    }
-
-    return ((vid1 == vid2) && (pid1 == pid2));
-}
-#endif
-
-/*
- * Caller must libusb_unref_device the libusb_device returned by this function.
- * Returns a libusb_device, or NULL upon failure
- */
-static libusb_device *
-spice_usb_device_manager_device_to_libdev(SpiceUsbDeviceManager *self,
-                                          SpiceUsbDevice *device)
-{
-#ifdef G_OS_WIN32
-    /*
-     * On win32 we need to do this the hard and slow way, by asking libusb to
-     * re-enumerate all devices and then finding a matching device.
-     * We cannot cache the libusb_device like we do under Linux since the
-     * driver swap we do under windows invalidates the cached libdev.
-     */
-
-    libusb_device *d, **devlist;
-    int bus, addr;
-    int i;
-
-    g_return_val_if_fail(SPICE_IS_USB_DEVICE_MANAGER(self), NULL);
-    g_return_val_if_fail(device != NULL, NULL);
-    g_return_val_if_fail(self->priv != NULL, NULL);
-    g_return_val_if_fail(self->priv->context != NULL, NULL);
-
-    /* On windows we match by vid / pid, since the address may change */
-    bus  = spice_usb_device_get_vid(device);
-    addr = spice_usb_device_get_pid(device);
-
-    libusb_get_device_list(self->priv->context, &devlist);
-    if (!devlist)
-        return NULL;
-
-    for (i = 0; (d = devlist[i]) != NULL; i++) {
-        if (spice_usb_device_manager_libdev_match(d, bus, addr)) {
-            libusb_ref_device(d);
-            break;
-        }
-    }
-
-    libusb_free_device_list(devlist, 1);
-
-    return d;
-
-#else
-    /* Simply return a ref to the cached libdev */
-    SpiceUsbDeviceInfo *info = (SpiceUsbDeviceInfo *)device;
-
-    return libusb_ref_device(info->libdev);
-#endif
-}
-#endif /* USE_USBREDIR */
diff --git a/gtk/usb-device-manager.h b/gtk/usb-device-manager.h
deleted file mode 100644
index 5b4cfbe..0000000
--- a/gtk/usb-device-manager.h
+++ /dev/null
@@ -1,122 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2011, 2012 Red Hat, Inc.
-
-   Red Hat Authors:
-   Hans de Goede <hdegoede at redhat.com>
-
-   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_USB_DEVICE_MANAGER_H__
-#define __SPICE_USB_DEVICE_MANAGER_H__
-
-#include "spice-client.h"
-#include <gio/gio.h>
-
-G_BEGIN_DECLS
-
-#define SPICE_TYPE_USB_DEVICE_MANAGER            (spice_usb_device_manager_get_type ())
-#define SPICE_USB_DEVICE_MANAGER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), SPICE_TYPE_USB_DEVICE_MANAGER, SpiceUsbDeviceManager))
-#define SPICE_USB_DEVICE_MANAGER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SPICE_TYPE_USB_DEVICE_MANAGER, SpiceUsbDeviceManagerClass))
-#define SPICE_IS_USB_DEVICE_MANAGER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SPICE_TYPE_USB_DEVICE_MANAGER))
-#define SPICE_IS_USB_DEVICE_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SPICE_TYPE_USB_DEVICE_MANAGER))
-#define SPICE_USB_DEVICE_MANAGER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SPICE_TYPE_USB_DEVICE_MANAGER, SpiceUsbDeviceManagerClass))
-
-#define SPICE_TYPE_USB_DEVICE                    (spice_usb_device_get_type())
-
-typedef struct _SpiceUsbDeviceManager SpiceUsbDeviceManager;
-typedef struct _SpiceUsbDeviceManagerClass SpiceUsbDeviceManagerClass;
-typedef struct _SpiceUsbDeviceManagerPrivate SpiceUsbDeviceManagerPrivate;
-
-typedef struct _SpiceUsbDevice SpiceUsbDevice;
-
-/**
- * SpiceUsbDeviceManager:
- *
- * The #SpiceUsbDeviceManager struct is opaque and should not be accessed directly.
- */
-struct _SpiceUsbDeviceManager
-{
-    GObject parent;
-
-    /*< private >*/
-    SpiceUsbDeviceManagerPrivate *priv;
-    /* Do not add fields to this struct */
-};
-
-/**
- * SpiceUsbDeviceManagerClass:
- * @parent_class: Parent class.
- * @device_added: Signal class handler for the #SpiceUsbDeviceManager::device-added signal.
- * @device_removed: Signal class handler for the #SpiceUsbDeviceManager::device-removed signal.
- * @auto_connect_failed: Signal class handler for the #SpiceUsbDeviceManager::auto-connect-failed signal.
- *
- * Class structure for #SpiceUsbDeviceManager.
- */
-struct _SpiceUsbDeviceManagerClass
-{
-    GObjectClass parent_class;
-
-    /* signals */
-    void (*device_added) (SpiceUsbDeviceManager *manager,
-                          SpiceUsbDevice *device);
-    void (*device_removed) (SpiceUsbDeviceManager *manager,
-                            SpiceUsbDevice *device);
-    void (*auto_connect_failed) (SpiceUsbDeviceManager *manager,
-                                 SpiceUsbDevice *device, GError *error);
-    void (*device_error) (SpiceUsbDeviceManager *manager,
-                          SpiceUsbDevice *device, GError *error);
-    /*< private >*/
-    /*
-     * If adding fields to this struct, remove corresponding
-     * amount of padding to avoid changing overall struct size
-     */
-    gchar _spice_reserved[SPICE_RESERVED_PADDING];
-};
-
-GType spice_usb_device_get_type(void);
-GType spice_usb_device_manager_get_type(void);
-
-gchar *spice_usb_device_get_description(SpiceUsbDevice *device, const gchar *format);
-gconstpointer spice_usb_device_get_libusb_device(const SpiceUsbDevice *device);
-
-SpiceUsbDeviceManager *spice_usb_device_manager_get(SpiceSession *session,
-                                                    GError **err);
-
-GPtrArray *spice_usb_device_manager_get_devices(SpiceUsbDeviceManager *manager);
-GPtrArray* spice_usb_device_manager_get_devices_with_filter(
-    SpiceUsbDeviceManager *manager, const gchar *filter);
-
-gboolean spice_usb_device_manager_is_device_connected(SpiceUsbDeviceManager *manager,
-                                                      SpiceUsbDevice *device);
-void spice_usb_device_manager_connect_device_async(
-                                             SpiceUsbDeviceManager *manager,
-                                             SpiceUsbDevice *device,
-                                             GCancellable *cancellable,
-                                             GAsyncReadyCallback callback,
-                                             gpointer user_data);
-gboolean spice_usb_device_manager_connect_device_finish(
-    SpiceUsbDeviceManager *self, GAsyncResult *res, GError **err);
-
-void spice_usb_device_manager_disconnect_device(SpiceUsbDeviceManager *manager,
-                                                SpiceUsbDevice *device);
-
-gboolean
-spice_usb_device_manager_can_redirect_device(SpiceUsbDeviceManager  *self,
-                                             SpiceUsbDevice         *device,
-                                             GError                **err);
-
-G_END_DECLS
-
-#endif /* __SPICE_USB_DEVICE_MANAGER_H__ */
diff --git a/gtk/usb-device-widget.c b/gtk/usb-device-widget.c
deleted file mode 100644
index 1ec30e3..0000000
--- a/gtk/usb-device-widget.c
+++ /dev/null
@@ -1,554 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2012 Red Hat, Inc.
-
-   Red Hat Authors:
-   Hans de Goede <hdegoede at redhat.com>
-
-   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.h>
-#include "glib-compat.h"
-#include "spice-client.h"
-#include "spice-marshal.h"
-#include "usb-device-widget.h"
-
-/**
- * SECTION:usb-device-widget
- * @short_description: USB device selection widget
- * @title: Spice USB device selection widget
- * @section_id:
- * @see_also:
- * @stability: Stable
- * @include: usb-device-widget.h
- *
- * #SpiceUsbDeviceWidget is a gtk widget which apps can use to easily
- * add an UI to select USB devices to redirect (or unredirect).
- */
-
-/* ------------------------------------------------------------------ */
-/* Prototypes for callbacks  */
-static void device_added_cb(SpiceUsbDeviceManager *manager,
-    SpiceUsbDevice *device, gpointer user_data);
-static void device_removed_cb(SpiceUsbDeviceManager *manager,
-    SpiceUsbDevice *device, gpointer user_data);
-static void device_error_cb(SpiceUsbDeviceManager *manager,
-    SpiceUsbDevice *device, GError *err, gpointer user_data);
-static gboolean spice_usb_device_widget_update_status(gpointer user_data);
-
-/* ------------------------------------------------------------------ */
-/* gobject glue                                                       */
-
-#define SPICE_USB_DEVICE_WIDGET_GET_PRIVATE(obj) \
-    (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_USB_DEVICE_WIDGET, \
-                                 SpiceUsbDeviceWidgetPrivate))
-
-enum {
-    PROP_0,
-    PROP_SESSION,
-    PROP_DEVICE_FORMAT_STRING,
-};
-
-enum {
-    CONNECT_FAILED,
-    LAST_SIGNAL,
-};
-
-struct _SpiceUsbDeviceWidgetPrivate {
-    SpiceSession *session;
-    gchar *device_format_string;
-    SpiceUsbDeviceManager *manager;
-    GtkWidget *info_bar;
-    gchar *err_msg;
-    gsize device_count;
-};
-
-static guint signals[LAST_SIGNAL] = { 0, };
-
-#if GTK_CHECK_VERSION(3,0,0)
-G_DEFINE_TYPE(SpiceUsbDeviceWidget, spice_usb_device_widget, GTK_TYPE_BOX);
-#else
-G_DEFINE_TYPE(SpiceUsbDeviceWidget, spice_usb_device_widget, GTK_TYPE_VBOX);
-#endif
-
-
-static void spice_usb_device_widget_get_property(GObject     *gobject,
-                                                 guint        prop_id,
-                                                 GValue      *value,
-                                                 GParamSpec  *pspec)
-{
-    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(gobject);
-    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
-
-    switch (prop_id) {
-    case PROP_SESSION:
-        g_value_set_object(value, priv->session);
-        break;
-    case PROP_DEVICE_FORMAT_STRING:
-        g_value_set_string(value, priv->device_format_string);
-        break;
-    default:
-        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
-        break;
-    }
-}
-
-static void spice_usb_device_widget_set_property(GObject       *gobject,
-                                                 guint          prop_id,
-                                                 const GValue  *value,
-                                                 GParamSpec    *pspec)
-{
-    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(gobject);
-    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
-
-    switch (prop_id) {
-    case PROP_SESSION:
-        priv->session = g_value_dup_object(value);
-        break;
-    case PROP_DEVICE_FORMAT_STRING:
-        priv->device_format_string = g_value_dup_string(value);
-        break;
-    default:
-        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
-        break;
-    }
-}
-
-static void spice_usb_device_widget_hide_info_bar(SpiceUsbDeviceWidget *self)
-{
-    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
-
-    if (priv->info_bar) {
-        gtk_widget_destroy(priv->info_bar);
-        priv->info_bar = NULL;
-    }
-}
-
-static void
-spice_usb_device_widget_show_info_bar(SpiceUsbDeviceWidget *self,
-                                      const gchar          *message,
-                                      GtkMessageType        message_type,
-                                      const gchar          *stock_icon_id)
-{
-    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
-    GtkWidget *info_bar, *content_area, *hbox, *widget;
-
-    spice_usb_device_widget_hide_info_bar(self);
-
-    info_bar = gtk_info_bar_new();
-    gtk_info_bar_set_message_type(GTK_INFO_BAR(info_bar), message_type);
-
-    content_area = gtk_info_bar_get_content_area(GTK_INFO_BAR(info_bar));
-#if GTK_CHECK_VERSION(3,0,0)
-    hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 12);
-#else
-    hbox = gtk_hbox_new(FALSE, 12);
-#endif
-    gtk_container_add(GTK_CONTAINER(content_area), hbox);
-
-    widget = gtk_image_new_from_stock(stock_icon_id,
-                                      GTK_ICON_SIZE_SMALL_TOOLBAR);
-    gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, FALSE, 0);
-
-    widget = gtk_label_new(message);
-    gtk_box_pack_start(GTK_BOX(hbox), widget, TRUE, TRUE, 0);
-
-    priv->info_bar = gtk_alignment_new(0.0, 0.0, 1.0, 0.0);
-    gtk_alignment_set_padding(GTK_ALIGNMENT(priv->info_bar), 0, 0, 12, 0);
-    gtk_container_add(GTK_CONTAINER(priv->info_bar), info_bar);
-    gtk_box_pack_start(GTK_BOX(self), priv->info_bar, FALSE, FALSE, 0);
-    gtk_widget_show_all(priv->info_bar);
-}
-
-static GObject *spice_usb_device_widget_constructor(
-    GType gtype, guint n_properties, GObjectConstructParam *properties)
-{
-    GObject *obj;
-    SpiceUsbDeviceWidget *self;
-    SpiceUsbDeviceWidgetPrivate *priv;
-    GPtrArray *devices = NULL;
-    GError *err = NULL;
-    GtkWidget *label;
-    gchar *str;
-    int i;
-
-    {
-        /* Always chain up to the parent constructor */
-        GObjectClass *parent_class;
-        parent_class = G_OBJECT_CLASS(spice_usb_device_widget_parent_class);
-        obj = parent_class->constructor(gtype, n_properties, properties);
-    }
-
-    self = SPICE_USB_DEVICE_WIDGET(obj);
-    priv = self->priv;
-    if (!priv->session)
-        g_error("SpiceUsbDeviceWidget constructed without a session");
-
-    label = gtk_label_new(NULL);
-    str = g_strdup_printf("<b>%s</b>", _("Select USB devices to redirect"));
-    gtk_label_set_markup(GTK_LABEL (label), str);
-    g_free(str);
-    gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
-    gtk_box_pack_start(GTK_BOX(self), label, FALSE, FALSE, 0);
-
-    priv->manager = spice_usb_device_manager_get(priv->session, &err);
-    if (err) {
-        spice_usb_device_widget_show_info_bar(self, err->message,
-                                              GTK_MESSAGE_WARNING,
-                                              GTK_STOCK_DIALOG_WARNING);
-        g_clear_error(&err);
-        return obj;
-    }
-
-    g_signal_connect(priv->manager, "device-added",
-                     G_CALLBACK(device_added_cb), self);
-    g_signal_connect(priv->manager, "device-removed",
-                     G_CALLBACK(device_removed_cb), self);
-    g_signal_connect(priv->manager, "device-error",
-                     G_CALLBACK(device_error_cb), self);
-
-    devices = spice_usb_device_manager_get_devices(priv->manager);
-    if (!devices)
-        goto end;
-
-    for (i = 0; i < devices->len; i++)
-        device_added_cb(NULL, g_ptr_array_index(devices, i), self);
-
-    g_ptr_array_unref(devices);
-
-end:
-    spice_usb_device_widget_update_status(self);
-
-    return obj;
-}
-
-static void spice_usb_device_widget_finalize(GObject *object)
-{
-    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(object);
-    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
-
-    if (priv->manager) {
-        g_signal_handlers_disconnect_by_func(priv->manager,
-                                             device_added_cb, self);
-        g_signal_handlers_disconnect_by_func(priv->manager,
-                                             device_removed_cb, self);
-        g_signal_handlers_disconnect_by_func(priv->manager,
-                                             device_error_cb, self);
-    }
-    g_object_unref(priv->session);
-    g_free(priv->device_format_string);
-
-    if (G_OBJECT_CLASS(spice_usb_device_widget_parent_class)->finalize)
-        G_OBJECT_CLASS(spice_usb_device_widget_parent_class)->finalize(object);
-}
-
-static void spice_usb_device_widget_class_init(
-    SpiceUsbDeviceWidgetClass *klass)
-{
-    GObjectClass *gobject_class = (GObjectClass *)klass;
-    GParamSpec *pspec;
-
-    g_type_class_add_private (klass, sizeof (SpiceUsbDeviceWidgetPrivate));
-
-    gobject_class->constructor  = spice_usb_device_widget_constructor;
-    gobject_class->finalize     = spice_usb_device_widget_finalize;
-    gobject_class->get_property = spice_usb_device_widget_get_property;
-    gobject_class->set_property = spice_usb_device_widget_set_property;
-
-    /**
-     * SpiceUsbDeviceWidget:session:
-     *
-     * #SpiceSession this #SpiceUsbDeviceWidget is associated with
-     *
-     **/
-    pspec = g_param_spec_object("session",
-                                "Session",
-                                "SpiceSession",
-                                SPICE_TYPE_SESSION,
-                                G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
-                                G_PARAM_STATIC_STRINGS);
-    g_object_class_install_property(gobject_class, PROP_SESSION, pspec);
-
-    /**
-     * SpiceUsbDeviceWidget:device-format-string:
-     *
-     * Format string to pass to spice_usb_device_get_description() for getting
-     * the device USB descriptions.
-     */
-    pspec = g_param_spec_string("device-format-string",
-                                "Device format string",
-                                "Format string for device description",
-                                NULL,
-                                G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
-                                G_PARAM_STATIC_STRINGS);
-    g_object_class_install_property(gobject_class, PROP_DEVICE_FORMAT_STRING,
-                                    pspec);
-
-    /**
-     * SpiceUsbDeviceWidget::connect-failed:
-     * @widget: The #SpiceUsbDeviceWidget that emitted the signal
-     * @device: #SpiceUsbDevice boxed object corresponding to the added device
-     * @error:  #GError describing the reason why the connect failed
-     *
-     * The #SpiceUsbDeviceWidget::connect-failed signal is emitted whenever
-     * the user has requested for a device to be redirected and this has
-     * failed.
-     **/
-    signals[CONNECT_FAILED] =
-        g_signal_new("connect-failed",
-                    G_OBJECT_CLASS_TYPE(gobject_class),
-                    G_SIGNAL_RUN_FIRST,
-                    G_STRUCT_OFFSET(SpiceUsbDeviceWidgetClass, connect_failed),
-                    NULL, NULL,
-                    g_cclosure_user_marshal_VOID__BOXED_BOXED,
-                    G_TYPE_NONE,
-                    2,
-                    SPICE_TYPE_USB_DEVICE,
-                    G_TYPE_ERROR);
-}
-
-static void spice_usb_device_widget_init(SpiceUsbDeviceWidget *self)
-{
-    self->priv = SPICE_USB_DEVICE_WIDGET_GET_PRIVATE(self);
-}
-
-/* ------------------------------------------------------------------ */
-/* public api                                                         */
-
-/**
- * spice_usb_device_widget_new:
- * @session: #SpiceSession for which to widget will control USB redirection
- * @device_format_string: (allow-none): String passed to
- * spice_usb_device_get_description()
- *
- * Returns: a new #SpiceUsbDeviceWidget instance
- */
-GtkWidget *spice_usb_device_widget_new(SpiceSession    *session,
-                                       const gchar     *device_format_string)
-{
-    return g_object_new(SPICE_TYPE_USB_DEVICE_WIDGET,
-                        "orientation", GTK_ORIENTATION_VERTICAL,
-                        "session", session,
-                        "device-format-string", device_format_string,
-                        "spacing", 6,
-                        NULL);
-}
-
-/* ------------------------------------------------------------------ */
-/* callbacks                                                          */
-
-static SpiceUsbDevice *get_usb_device(GtkWidget *widget)
-{
-    if (!GTK_IS_ALIGNMENT(widget))
-        return NULL;
-
-    widget = gtk_bin_get_child(GTK_BIN(widget));
-    return g_object_get_data(G_OBJECT(widget), "usb-device");
-}
-
-static void check_can_redirect(GtkWidget *widget, gpointer user_data)
-{
-    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
-    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
-    SpiceUsbDevice *device;
-    gboolean can_redirect;
-    GError *err = NULL;
-
-    device = get_usb_device(widget);
-    if (!device)
-        return; /* Non device widget, ie the info_bar */
-
-    priv->device_count++;
-    can_redirect = spice_usb_device_manager_can_redirect_device(priv->manager,
-                                                                device, &err);
-    gtk_widget_set_sensitive(widget, can_redirect);
-
-    /* If we cannot redirect this device, append the error message to
-       err_msg, but only if it is *not* already there! */
-    if (!can_redirect) {
-        if (priv->err_msg) {
-            if (!strstr(priv->err_msg, err->message)) {
-                gchar *old_err_msg = priv->err_msg;
-
-                priv->err_msg = g_strdup_printf("%s\n%s", priv->err_msg,
-                                                err->message);
-                g_free(old_err_msg);
-            }
-        } else {
-            priv->err_msg = g_strdup(err->message);
-        }
-    }
-
-    g_clear_error(&err);
-}
-
-static gboolean spice_usb_device_widget_update_status(gpointer user_data)
-{
-    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
-    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
-
-    priv->device_count = 0;
-    gtk_container_foreach(GTK_CONTAINER(self), check_can_redirect, self);
-
-    if (priv->err_msg) {
-        spice_usb_device_widget_show_info_bar(self, priv->err_msg,
-                                              GTK_MESSAGE_INFO,
-                                              GTK_STOCK_DIALOG_WARNING);
-        g_free(priv->err_msg);
-        priv->err_msg = NULL;
-    } else {
-        spice_usb_device_widget_hide_info_bar(self);
-    }
-
-    if (priv->device_count == 0)
-        spice_usb_device_widget_show_info_bar(self, _("No USB devices detected"),
-                                              GTK_MESSAGE_INFO,
-                                              GTK_STOCK_DIALOG_INFO);
-    return FALSE;
-}
-
-typedef struct _connect_cb_data {
-    GtkWidget *check;
-    SpiceUsbDeviceWidget *self;
-} connect_cb_data;
-
-static void connect_cb(GObject *gobject, GAsyncResult *res, gpointer user_data)
-{
-    SpiceUsbDeviceManager *manager = SPICE_USB_DEVICE_MANAGER(gobject);
-    connect_cb_data *data = user_data;
-    SpiceUsbDeviceWidget *self = data->self;
-    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
-    SpiceUsbDevice *device;
-    GError *err = NULL;
-    gchar *desc;
-
-    spice_usb_device_manager_connect_device_finish(manager, res, &err);
-    if (err) {
-        device = g_object_get_data(G_OBJECT(data->check), "usb-device");
-        desc = spice_usb_device_get_description(device,
-                                                priv->device_format_string);
-        g_prefix_error(&err, "Could not redirect %s: ", desc);
-        g_free(desc);
-
-        SPICE_DEBUG("%s", err->message);
-        g_signal_emit(self, signals[CONNECT_FAILED], 0, device, err);
-        g_error_free(err);
-
-        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(data->check), FALSE);
-        spice_usb_device_widget_update_status(self);
-    }
-
-    g_object_unref(data->check);
-    g_object_unref(data->self);
-    g_free(data);
-}
-
-static void checkbox_clicked_cb(GtkWidget *check, gpointer user_data)
-{
-    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
-    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
-    SpiceUsbDevice *device;
-
-    device = g_object_get_data(G_OBJECT(check), "usb-device");
-
-    if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(check))) {
-        connect_cb_data *data = g_new(connect_cb_data, 1);
-        data->check = g_object_ref(check);
-        data->self  = g_object_ref(self);
-        spice_usb_device_manager_connect_device_async(priv->manager,
-                                                      device,
-                                                      NULL,
-                                                      connect_cb,
-                                                      data);
-    } else {
-        spice_usb_device_manager_disconnect_device(priv->manager,
-                                                   device);
-    }
-    spice_usb_device_widget_update_status(self);
-}
-
-static void checkbox_usb_device_destroy_notify(gpointer data)
-{
-    g_boxed_free(spice_usb_device_get_type(), data);
-}
-
-static void device_added_cb(SpiceUsbDeviceManager *manager,
-    SpiceUsbDevice *device, gpointer user_data)
-{
-    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
-    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
-    GtkWidget *align, *check;
-    gchar *desc;
-
-    desc = spice_usb_device_get_description(device,
-                                            priv->device_format_string);
-    check = gtk_check_button_new_with_label(desc);
-    g_free(desc);
-
-    if (spice_usb_device_manager_is_device_connected(priv->manager,
-                                                     device))
-        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), TRUE);
-
-    g_object_set_data_full(
-            G_OBJECT(check), "usb-device",
-            g_boxed_copy(spice_usb_device_get_type(), device),
-            checkbox_usb_device_destroy_notify);
-    g_signal_connect(G_OBJECT(check), "clicked",
-                     G_CALLBACK(checkbox_clicked_cb), self);
-
-    align = gtk_alignment_new(0, 0, 0, 0);
-    gtk_alignment_set_padding(GTK_ALIGNMENT(align), 0, 0, 12, 0);
-    gtk_container_add(GTK_CONTAINER(align), check);
-    gtk_box_pack_end(GTK_BOX(self), align, FALSE, FALSE, 0);
-    spice_usb_device_widget_update_status(self);
-    gtk_widget_show_all(align);
-}
-
-static void destroy_widget_by_usb_device(GtkWidget *widget, gpointer user_data)
-{
-    if (get_usb_device(widget) == user_data)
-        gtk_widget_destroy(widget);
-}
-
-static void device_removed_cb(SpiceUsbDeviceManager *manager,
-    SpiceUsbDevice *device, gpointer user_data)
-{
-    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
-
-    gtk_container_foreach(GTK_CONTAINER(self),
-                          destroy_widget_by_usb_device, device);
-
-    spice_usb_device_widget_update_status(self);
-}
-
-static void set_inactive_by_usb_device(GtkWidget *widget, gpointer user_data)
-{
-    if (get_usb_device(widget) == user_data) {
-        GtkWidget *check = gtk_bin_get_child(GTK_BIN(widget));
-        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), FALSE);
-    }
-}
-
-static void device_error_cb(SpiceUsbDeviceManager *manager,
-    SpiceUsbDevice *device, GError *err, gpointer user_data)
-{
-    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
-
-    gtk_container_foreach(GTK_CONTAINER(self),
-                          set_inactive_by_usb_device, device);
-
-    spice_usb_device_widget_update_status(self);
-}
diff --git a/gtk/usb-device-widget.h b/gtk/usb-device-widget.h
deleted file mode 100644
index b68cc6b..0000000
--- a/gtk/usb-device-widget.h
+++ /dev/null
@@ -1,81 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2012 Red Hat, Inc.
-
-   Red Hat Authors:
-   Hans de Goede <hdegoede at redhat.com>
-
-   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_USB_DEVICE_WIDGET_H__
-#define __SPICE_USB_DEVICE_WIDGET_H__
-
-#include <gtk/gtk.h>
-#include "spice-client.h"
-
-G_BEGIN_DECLS
-
-#define SPICE_TYPE_USB_DEVICE_WIDGET            (spice_usb_device_widget_get_type ())
-#define SPICE_USB_DEVICE_WIDGET(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), SPICE_TYPE_USB_DEVICE_WIDGET, SpiceUsbDeviceWidget))
-#define SPICE_USB_DEVICE_WIDGET_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SPICE_TYPE_USB_DEVICE_WIDGET, SpiceUsbDeviceWidgetClass))
-#define SPICE_IS_USB_DEVICE_WIDGET(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SPICE_TYPE_USB_DEVICE_WIDGET))
-#define SPICE_IS_USB_DEVICE_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SPICE_TYPE_USB_DEVICE_WIDGET))
-#define SPICE_USB_DEVICE_WIDGET_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SPICE_TYPE_USB_DEVICE_WIDGET, SpiceUsbDeviceWidgetClass))
-
-typedef struct _SpiceUsbDeviceWidget SpiceUsbDeviceWidget;
-typedef struct _SpiceUsbDeviceWidgetClass SpiceUsbDeviceWidgetClass;
-typedef struct _SpiceUsbDeviceWidgetPrivate SpiceUsbDeviceWidgetPrivate;
-
-/**
- * SpiceUsbDeviceWidget:
- *
- * The #SpiceUsbDeviceWidget struct is opaque and should not be accessed directly.
- */
-struct _SpiceUsbDeviceWidget
-{
-    GtkVBox parent;
-
-    /*< private >*/
-    SpiceUsbDeviceWidgetPrivate *priv;
-    /* Do not add fields to this struct */
-};
-
-/**
- * SpiceUsbDeviceWidgetClass:
- * @connect_failed: Signal class handler for the #SpiceUsbDeviceWidget::connect-failed signal.
- *
- * Class structure for #SpiceUsbDeviceWidget.
- */
-struct _SpiceUsbDeviceWidgetClass
-{
-    GtkVBoxClass parent_class;
-
-    /* signals */
-    void (*connect_failed) (SpiceUsbDeviceWidget *widget,
-                            SpiceUsbDevice *device, GError *error);
-    /*< private >*/
-    /*
-     * If adding fields to this struct, remove corresponding
-     * amount of padding to avoid changing overall struct size
-     */
-    gchar _spice_reserved[SPICE_RESERVED_PADDING];
-};
-
-GType spice_usb_device_widget_get_type(void);
-GtkWidget *spice_usb_device_widget_new(SpiceSession    *session,
-                                       const gchar     *device_format_string);
-
-G_END_DECLS
-
-#endif /* __SPICE_USB_DEVICE_WIDGET_H__ */
diff --git a/gtk/usbutil.c b/gtk/usbutil.c
deleted file mode 100644
index 16d757b..0000000
--- a/gtk/usbutil.c
+++ /dev/null
@@ -1,323 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2012 Red Hat, Inc.
-
-   Red Hat Authors:
-   Hans de Goede <hdegoede at redhat.com>
-
-   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-object.h>
-#include <glib/gi18n.h>
-#include <ctype.h>
-#include <stdlib.h>
-
-#include "glib-compat.h"
-
-#ifdef USE_USBREDIR
-#ifdef __linux__
-#include <stdio.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#endif
-#include "usbutil.h"
-#include "spice-util-priv.h"
-
-#define VENDOR_NAME_LEN (122 - sizeof(void *))
-#define PRODUCT_NAME_LEN 126
-
-typedef struct _usb_product_info {
-    guint16 product_id;
-    char name[PRODUCT_NAME_LEN];
-} usb_product_info;
-
-typedef struct _usb_vendor_info {
-    usb_product_info *product_info;
-    int product_count;
-    guint16 vendor_id;
-    char name[VENDOR_NAME_LEN];
-} usb_vendor_info;
-
-static GStaticMutex usbids_load_mutex = G_STATIC_MUTEX_INIT;
-static int usbids_vendor_count = 0; /* < 0: failed, 0: empty, > 0: loaded */
-static usb_vendor_info *usbids_vendor_info = NULL;
-
-G_GNUC_INTERNAL
-const char *spice_usbutil_libusb_strerror(enum libusb_error error_code)
-{
-    switch (error_code) {
-    case LIBUSB_SUCCESS:
-        return "Success";
-    case LIBUSB_ERROR_IO:
-        return "Input/output error";
-    case LIBUSB_ERROR_INVALID_PARAM:
-        return "Invalid parameter";
-    case LIBUSB_ERROR_ACCESS:
-        return "Access denied (insufficient permissions)";
-    case LIBUSB_ERROR_NO_DEVICE:
-        return "No such device (it may have been disconnected)";
-    case LIBUSB_ERROR_NOT_FOUND:
-        return "Entity not found";
-    case LIBUSB_ERROR_BUSY:
-        return "Resource busy";
-    case LIBUSB_ERROR_TIMEOUT:
-        return "Operation timed out";
-    case LIBUSB_ERROR_OVERFLOW:
-        return "Overflow";
-    case LIBUSB_ERROR_PIPE:
-        return "Pipe error";
-    case LIBUSB_ERROR_INTERRUPTED:
-        return "System call interrupted (perhaps due to signal)";
-    case LIBUSB_ERROR_NO_MEM:
-        return "Insufficient memory";
-    case LIBUSB_ERROR_NOT_SUPPORTED:
-        return "Operation not supported or unimplemented on this platform";
-    case LIBUSB_ERROR_OTHER:
-        return "Other error";
-    }
-    return "Unknown error";
-}
-
-#ifdef __linux__
-/* <Sigh> libusb does not allow getting the manufacturer and product strings
-   without opening the device, so grab them directly from sysfs */
-static gchar *spice_usbutil_get_sysfs_attribute(int bus, int address,
-                                                const char *attribute)
-{
-    struct stat stat_buf;
-    char filename[256];
-    gchar *contents;
-
-    snprintf(filename, sizeof(filename), "/dev/bus/usb/%03d/%03d",
-             bus, address);
-    if (stat(filename, &stat_buf) != 0)
-        return NULL;
-
-    snprintf(filename, sizeof(filename), "/sys/dev/char/%d:%d/%s",
-             major(stat_buf.st_rdev), minor(stat_buf.st_rdev), attribute);
-    if (!g_file_get_contents(filename, &contents, NULL, NULL))
-        return NULL;
-
-    /* Remove the newline at the end */
-    contents[strlen(contents) - 1] = '\0';
-
-    return contents;
-}
-#endif
-
-static gboolean spice_usbutil_parse_usbids(gchar *path)
-{
-    gchar *contents, *line, **lines;
-    usb_product_info *product_info;
-    int i, j, id, product_count = 0;
-
-    usbids_vendor_count = 0;
-    if (!g_file_get_contents(path, &contents, NULL, NULL)) {
-        usbids_vendor_count = -1;
-        return FALSE;
-    }
-
-    lines = g_strsplit(contents, "\n", -1);
-
-    for (i = 0; lines[i]; i++) {
-        if (!isxdigit(lines[i][0]) || !isxdigit(lines[i][1]))
-            continue;
-
-        for (j = 1; lines[i + j] &&
-                   (lines[i + j][0] == '\t' ||
-                    lines[i + j][0] == '#'  ||
-                    lines[i + j][0] == '\0'); j++) {
-            if (lines[i + j][0] == '\t' && isxdigit(lines[i + j][1]))
-                product_count++;
-        }
-        i += j - 1;
-
-        usbids_vendor_count++;
-    }
-
-    usbids_vendor_info = g_new(usb_vendor_info, usbids_vendor_count);
-    product_info = g_new(usb_product_info, product_count);
-
-    usbids_vendor_count = 0;
-    for (i = 0; lines[i]; i++) {
-        line = lines[i];
-
-        if (!isxdigit(line[0]) || !isxdigit(line[1]))
-            continue;
-
-        id = strtoul(line, &line, 16);
-        while (isspace(line[0]))
-            line++;
-
-        usbids_vendor_info[usbids_vendor_count].vendor_id = id;
-        snprintf(usbids_vendor_info[usbids_vendor_count].name,
-                 VENDOR_NAME_LEN, "%s", line);
-
-        product_count = 0;
-        for (j = 1; lines[i + j] &&
-                   (lines[i + j][0] == '\t' ||
-                    lines[i + j][0] == '#'  ||
-                    lines[i + j][0] == '\0'); j++) {
-            line = lines[i + j];
-
-            if (line[0] != '\t' || !isxdigit(line[1]))
-                continue;
-
-            id = strtoul(line + 1, &line, 16);
-            while (isspace(line[0]))
-                line++;
-            product_info[product_count].product_id = id;
-            snprintf(product_info[product_count].name,
-                     PRODUCT_NAME_LEN, "%s", line);
-
-            product_count++;
-        }
-        i += j - 1;
-
-        usbids_vendor_info[usbids_vendor_count].product_count = product_count;
-        usbids_vendor_info[usbids_vendor_count].product_info  = product_info;
-        product_info += product_count;
-        usbids_vendor_count++;
-    }
-
-    g_strfreev(lines);
-    g_free(contents);
-
-#if 0 /* Testing only */
-    for (i = 0; i < usbids_vendor_count; i++) {
-        printf("%04x  %s\n", usbids_vendor_info[i].vendor_id,
-               usbids_vendor_info[i].name);
-        product_info = usbids_vendor_info[i].product_info;
-        for (j = 0; j < usbids_vendor_info[i].product_count; j++) {
-            printf("\t%04x  %s\n", product_info[j].product_id,
-                   product_info[j].name);
-        }
-    }
-#endif
-
-    return TRUE;
-}
-
-static gboolean spice_usbutil_load_usbids(void)
-{
-    gboolean success = FALSE;
-
-    g_static_mutex_lock(&usbids_load_mutex);
-    if (usbids_vendor_count) {
-        success = usbids_vendor_count > 0;
-        goto leave;
-    }
-
-#ifdef WITH_USBIDS
-    success = spice_usbutil_parse_usbids(USB_IDS);
-#else
-    {
-        const gchar * const *dirs = g_get_system_data_dirs();
-        gchar *path = NULL;
-        int i;
-
-        for (i = 0; dirs[i]; ++i) {
-            path = g_build_filename(dirs[i], "hwdata", "usb.ids", NULL);
-            success = spice_usbutil_parse_usbids(path);
-            SPICE_DEBUG("loading %s success: %s", path, spice_yes_no(success));
-            g_free(path);
-
-            if (success)
-                goto leave;
-        }
-    }
-#endif
-
-leave:
-    g_static_mutex_unlock(&usbids_load_mutex);
-    return success;
-}
-
-G_GNUC_INTERNAL
-void spice_usb_util_get_device_strings(int bus, int address,
-                                       int vendor_id, int product_id,
-                                       gchar **manufacturer, gchar **product)
-{
-    usb_product_info *product_info;
-    int i, j;
-
-    g_return_if_fail(manufacturer != NULL);
-    g_return_if_fail(product != NULL);
-
-    *manufacturer = NULL;
-    *product = NULL;
-
-#if __linux__
-    *manufacturer = spice_usbutil_get_sysfs_attribute(bus, address, "manufacturer");
-    *product = spice_usbutil_get_sysfs_attribute(bus, address, "product");
-#endif
-
-    if ((!*manufacturer || !*product) &&
-        spice_usbutil_load_usbids()) {
-
-        for (i = 0; i < usbids_vendor_count; i++) {
-            if ((int)usbids_vendor_info[i].vendor_id != vendor_id)
-                continue;
-
-            if (!*manufacturer && usbids_vendor_info[i].name[0])
-                *manufacturer = g_strdup(usbids_vendor_info[i].name);
-
-            product_info = usbids_vendor_info[i].product_info;
-            for (j = 0; j < usbids_vendor_info[i].product_count; j++) {
-                if ((int)product_info[j].product_id != product_id)
-                    continue;
-
-                if (!*product && product_info[j].name[0])
-                    *product = g_strdup(product_info[j].name);
-
-                break;
-            }
-            break;
-        }
-    }
-
-    if (!*manufacturer)
-        *manufacturer = g_strdup(_("USB"));
-    if (!*product)
-        *product = g_strdup(_("Device"));
-
-    /* Some devices have unwanted whitespace in their strings */
-    g_strstrip(*manufacturer);
-    g_strstrip(*product);
-
-    /* Some devices repeat the manufacturer at the beginning of product */
-    if (g_str_has_prefix(*product, *manufacturer) &&
-            strlen(*product) > strlen(*manufacturer)) {
-        gchar *tmp = g_strdup(*product + strlen(*manufacturer));
-        g_free(*product);
-        *product = tmp;
-        g_strstrip(*product);
-    }
-}
-
-#endif
-
-#ifdef USBUTIL_TEST
-int main()
-{
-    if (spice_usbutil_load_usbids())
-        exit(0);
-
-    exit(1);
-}
-#endif
diff --git a/gtk/usbutil.h b/gtk/usbutil.h
deleted file mode 100644
index de5e92a..0000000
--- a/gtk/usbutil.h
+++ /dev/null
@@ -1,39 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2012 Red Hat, Inc.
-
-   Red Hat Authors:
-   Hans de Goede <hdegoede at redhat.com>
-
-   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_USBUTIL_H__
-#define __SPICE_USBUTIL_H__
-
-#include <glib.h>
-
-#ifdef USE_USBREDIR
-#include <libusb.h>
-
-G_BEGIN_DECLS
-
-const char *spice_usbutil_libusb_strerror(enum libusb_error error_code);
-void spice_usb_util_get_device_strings(int bus, int address,
-                                       int vendor_id, int product_id,
-                                       gchar **manufacturer, gchar **product);
-
-G_END_DECLS
-
-#endif /* USE_USBREDIR */
-#endif /* __SPICE_USBUTIL_H__ */
diff --git a/gtk/vmcstream.c b/gtk/vmcstream.c
deleted file mode 100644
index 483dd5a..0000000
--- a/gtk/vmcstream.c
+++ /dev/null
@@ -1,535 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-  Copyright (C) 2013 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 <string.h>
-
-#include "vmcstream.h"
-#include "spice-channel-priv.h"
-#include "gio-coroutine.h"
-#include "glib-compat.h"
-
-struct _SpiceVmcInputStream
-{
-    GInputStream parent_instance;
-    GSimpleAsyncResult *result;
-    struct coroutine *coroutine;
-
-    SpiceChannel *channel;
-    gboolean all;
-    guint8 *buffer;
-    gsize count;
-    gsize pos;
-
-    GCancellable *cancellable;
-    gulong cancel_id;
-};
-
-struct _SpiceVmcInputStreamClass
-{
-    GInputStreamClass parent_class;
-};
-
-static gssize   spice_vmc_input_stream_read        (GInputStream        *stream,
-                                                    void                *buffer,
-                                                    gsize                count,
-                                                    GCancellable        *cancellable,
-                                                    GError             **error);
-static void     spice_vmc_input_stream_read_async  (GInputStream        *stream,
-                                                    void                *buffer,
-                                                    gsize                count,
-                                                    int                  io_priority,
-                                                    GCancellable        *cancellable,
-                                                    GAsyncReadyCallback  callback,
-                                                    gpointer             user_data);
-static gssize   spice_vmc_input_stream_read_finish (GInputStream        *stream,
-                                                    GAsyncResult        *result,
-                                                    GError             **error);
-static gssize   spice_vmc_input_stream_skip        (GInputStream        *stream,
-                                                    gsize                count,
-                                                    GCancellable        *cancellable,
-                                                    GError             **error);
-static gboolean spice_vmc_input_stream_close       (GInputStream        *stream,
-                                                    GCancellable        *cancellable,
-                                                    GError             **error);
-
-G_DEFINE_TYPE(SpiceVmcInputStream, spice_vmc_input_stream, G_TYPE_INPUT_STREAM)
-
-
-static void
-spice_vmc_input_stream_class_init(SpiceVmcInputStreamClass *klass)
-{
-    GInputStreamClass *istream_class;
-
-    istream_class = G_INPUT_STREAM_CLASS(klass);
-    istream_class->read_fn = spice_vmc_input_stream_read;
-    istream_class->read_async = spice_vmc_input_stream_read_async;
-    istream_class->read_finish = spice_vmc_input_stream_read_finish;
-    istream_class->skip = spice_vmc_input_stream_skip;
-    istream_class->close_fn = spice_vmc_input_stream_close;
-}
-
-static void
-spice_vmc_input_stream_init(SpiceVmcInputStream *self)
-{
-}
-
-static SpiceVmcInputStream *
-spice_vmc_input_stream_new(void)
-{
-    SpiceVmcInputStream *self;
-
-    self = g_object_new(SPICE_TYPE_VMC_INPUT_STREAM, NULL);
-
-    return self;
-}
-
-/* coroutine */
-/**
- * Feed a SpiceVmc stream with new data from a coroutine
- *
- * The other end will be waiting on read_async() until data is fed
- * here.
- */
-G_GNUC_INTERNAL void
-spice_vmc_input_stream_co_data(SpiceVmcInputStream *self,
-                               const gpointer d, gsize size)
-{
-    guint8 *data = d;
-
-    g_return_if_fail(SPICE_IS_VMC_INPUT_STREAM(self));
-    g_return_if_fail(self->coroutine == NULL);
-
-    self->coroutine = coroutine_self();
-
-    while (size > 0) {
-        SPICE_DEBUG("spicevmc co_data %p", self->result);
-        if (!self->result)
-            coroutine_yield(NULL);
-
-        g_return_if_fail(self->result != NULL);
-
-        gsize min = MIN(self->count, size);
-        memcpy(self->buffer, data, min);
-
-        size -= min;
-        data += min;
-
-        SPICE_DEBUG("spicevmc co_data complete: %" G_GSIZE_FORMAT
-                    "/%" G_GSIZE_FORMAT, min, self->count);
-
-        self->pos += min;
-        self->buffer += min;
-
-        if (self->all && min > 0 && self->pos != self->count)
-            continue;
-
-        g_simple_async_result_set_op_res_gssize(self->result, self->pos);
-
-        g_simple_async_result_complete_in_idle(self->result);
-        g_clear_object(&self->result);
-        if (self->cancellable) {
-            g_cancellable_disconnect(self->cancellable, self->cancel_id);
-            g_clear_object(&self->cancellable);
-        }
-    }
-
-    self->coroutine = NULL;
-}
-
-static void
-read_cancelled(GCancellable *cancellable,
-               gpointer user_data)
-{
-    SpiceVmcInputStream *self = SPICE_VMC_INPUT_STREAM(user_data);
-
-    SPICE_DEBUG("read cancelled, %p", self->result);
-    g_simple_async_result_set_error(self->result,
-                                    G_IO_ERROR, G_IO_ERROR_CANCELLED,
-                                    "read cancelled");
-    g_simple_async_result_complete_in_idle(self->result);
-
-    g_clear_object(&self->result);
-
-    /* See FIXME */
-    /* if (self->cancellable) { */
-    /*     g_cancellable_disconnect(self->cancellable, self->cancel_id); */
-    /*     g_clear_object(&self->cancellable); */
-    /* } */
-}
-
-G_GNUC_INTERNAL void
-spice_vmc_input_stream_read_all_async(GInputStream        *stream,
-                                      void                *buffer,
-                                      gsize                count,
-                                      int                  io_priority,
-                                      GCancellable        *cancellable,
-                                      GAsyncReadyCallback  callback,
-                                      gpointer             user_data)
-{
-    SpiceVmcInputStream *self = SPICE_VMC_INPUT_STREAM(stream);
-    GSimpleAsyncResult *result;
-
-    /* no concurrent read permitted by ginputstream */
-    g_return_if_fail(self->result == NULL);
-    g_return_if_fail(self->cancellable == NULL);
-    self->all = TRUE;
-    self->buffer = buffer;
-    self->count = count;
-    self->pos = 0;
-    result = g_simple_async_result_new(G_OBJECT(self),
-                                       callback,
-                                       user_data,
-                                       spice_vmc_input_stream_read_async);
-    self->result = result;
-    self->cancellable = g_object_ref(cancellable);
-    if (cancellable)
-        self->cancel_id =
-            g_cancellable_connect(cancellable, G_CALLBACK(read_cancelled), self, NULL);
-
-    if (self->coroutine)
-        coroutine_yieldto(self->coroutine, NULL);
-}
-
-G_GNUC_INTERNAL gssize
-spice_vmc_input_stream_read_all_finish(GInputStream *stream,
-                                       GAsyncResult *result,
-                                       GError **error)
-{
-    GSimpleAsyncResult *simple;
-    SpiceVmcInputStream *self = SPICE_VMC_INPUT_STREAM(stream);
-
-    g_return_val_if_fail(g_simple_async_result_is_valid(result,
-                                                        G_OBJECT(self),
-                                                        spice_vmc_input_stream_read_async),
-                         -1);
-
-    simple = (GSimpleAsyncResult *)result;
-
-    /* FIXME: calling _finish() is required. Disconnecting in
-       read_cancelled() causes a deadlock. #705395 */
-    if (self->cancellable) {
-        g_cancellable_disconnect(self->cancellable, self->cancel_id);
-        g_clear_object(&self->cancellable);
-    }
-
-    if (g_simple_async_result_propagate_error(simple, error))
-        return -1;
-
-    return g_simple_async_result_get_op_res_gssize(simple);
-}
-
-static void
-spice_vmc_input_stream_read_async(GInputStream        *stream,
-                                  void                *buffer,
-                                  gsize                count,
-                                  int                  io_priority,
-                                  GCancellable        *cancellable,
-                                  GAsyncReadyCallback  callback,
-                                  gpointer             user_data)
-{
-    SpiceVmcInputStream *self = SPICE_VMC_INPUT_STREAM(stream);
-    GSimpleAsyncResult *result;
-
-    /* no concurrent read permitted by ginputstream */
-    g_return_if_fail(self->result == NULL);
-    g_return_if_fail(self->cancellable == NULL);
-    self->all = FALSE;
-    self->buffer = buffer;
-    self->count = count;
-    self->pos = 0;
-    result = g_simple_async_result_new(G_OBJECT(self),
-                                       callback,
-                                       user_data,
-                                       spice_vmc_input_stream_read_async);
-    self->result = result;
-    self->cancellable = g_object_ref(cancellable);
-    if (cancellable)
-        self->cancel_id =
-            g_cancellable_connect(cancellable, G_CALLBACK(read_cancelled), self, NULL);
-
-    if (self->coroutine)
-        coroutine_yieldto(self->coroutine, NULL);
-}
-
-static gssize
-spice_vmc_input_stream_read_finish(GInputStream *stream,
-                                   GAsyncResult *result,
-                                   GError **error)
-{
-    GSimpleAsyncResult *simple;
-    SpiceVmcInputStream *self = SPICE_VMC_INPUT_STREAM(stream);
-
-    g_return_val_if_fail(g_simple_async_result_is_valid(result,
-                                                        G_OBJECT(self),
-                                                        spice_vmc_input_stream_read_async),
-                         -1);
-
-    simple = (GSimpleAsyncResult *)result;
-
-    /* FIXME: calling _finish() is required. Disconnecting in
-       read_cancelled() causes a deadlock. #705395 */
-    if (self->cancellable) {
-        g_cancellable_disconnect(self->cancellable, self->cancel_id);
-        g_clear_object(&self->cancellable);
-    }
-
-    if (g_simple_async_result_propagate_error(simple, error))
-        return -1;
-
-    return g_simple_async_result_get_op_res_gssize(simple);
-}
-
-static gssize
-spice_vmc_input_stream_read(GInputStream  *stream,
-                            void          *buffer,
-                            gsize          count,
-                            GCancellable  *cancellable,
-                            GError       **error)
-{
-    g_return_val_if_reached(-1);
-}
-
-static gssize
-spice_vmc_input_stream_skip(GInputStream  *stream,
-                            gsize          count,
-                            GCancellable  *cancellable,
-                            GError       **error)
-{
-    g_return_val_if_reached(-1);
-}
-
-static gboolean
-spice_vmc_input_stream_close(GInputStream  *stream,
-                             GCancellable  *cancellable,
-                             GError       **error)
-{
-    SPICE_DEBUG("fake close");
-    return TRUE;
-}
-
-/* OUTPUT */
-
-struct _SpiceVmcOutputStream
-{
-    GOutputStream parent_instance;
-
-    SpiceChannel *channel; /* weak */
-};
-
-struct _SpiceVmcOutputStreamClass
-{
-    GOutputStreamClass parent_class;
-};
-
-static gssize   spice_vmc_output_stream_write_fn     (GOutputStream   *stream,
-                                                      const void      *buffer,
-                                                      gsize            count,
-                                                      GCancellable    *cancellable,
-                                                      GError         **error);
-static gssize   spice_vmc_output_stream_write_finish (GOutputStream   *stream,
-                                                      GAsyncResult    *result,
-                                                      GError         **error);
-static void     spice_vmc_output_stream_write_async  (GOutputStream   *stream,
-                                                      const void      *buffer,
-                                                      gsize            count,
-                                                      int              io_priority,
-                                                      GCancellable    *cancellable,
-                                                      GAsyncReadyCallback callback,
-                                                      gpointer         user_data);
-
-G_DEFINE_TYPE(SpiceVmcOutputStream, spice_vmc_output_stream, G_TYPE_OUTPUT_STREAM)
-
-
-static void
-spice_vmc_output_stream_class_init(SpiceVmcOutputStreamClass *klass)
-{
-    GOutputStreamClass *ostream_class;
-
-    ostream_class = G_OUTPUT_STREAM_CLASS(klass);
-    ostream_class->write_fn = spice_vmc_output_stream_write_fn;
-    ostream_class->write_async = spice_vmc_output_stream_write_async;
-    ostream_class->write_finish = spice_vmc_output_stream_write_finish;
-}
-
-static void
-spice_vmc_output_stream_init(SpiceVmcOutputStream *self)
-{
-}
-
-static SpiceVmcOutputStream *
-spice_vmc_output_stream_new(SpiceChannel *channel)
-{
-    SpiceVmcOutputStream *self;
-
-    self = g_object_new(SPICE_TYPE_VMC_OUTPUT_STREAM, NULL);
-    self->channel = channel;
-
-    return self;
-}
-
-static gssize
-spice_vmc_output_stream_write_fn(GOutputStream   *stream,
-                                 const void      *buffer,
-                                 gsize            count,
-                                 GCancellable    *cancellable,
-                                 GError         **error)
-{
-    SpiceVmcOutputStream *self = SPICE_VMC_OUTPUT_STREAM(stream);
-    SpiceMsgOut *msg_out;
-
-    msg_out = spice_msg_out_new(SPICE_CHANNEL(self->channel),
-                                SPICE_MSGC_SPICEVMC_DATA);
-    spice_marshaller_add(msg_out->marshaller, buffer, count);
-    spice_msg_out_send(msg_out);
-
-    return count;
-}
-
-static gssize
-spice_vmc_output_stream_write_finish(GOutputStream *stream,
-                                     GAsyncResult *simple,
-                                     GError **error)
-{
-    SpiceVmcOutputStream *self = SPICE_VMC_OUTPUT_STREAM(stream);
-    GSimpleAsyncResult *res =
-        g_simple_async_result_get_op_res_gpointer(G_SIMPLE_ASYNC_RESULT(simple));
-
-    SPICE_DEBUG("spicevmc write finish");
-    return spice_vmc_write_finish(self->channel, G_ASYNC_RESULT(res), error);
-}
-
-static void
-write_cb(GObject *source_object,
-         GAsyncResult *res,
-         gpointer user_data)
-{
-    GSimpleAsyncResult *simple = user_data;
-
-    g_simple_async_result_set_op_res_gpointer(simple, res, NULL);
-
-    g_simple_async_result_complete(simple);
-    g_object_unref(simple);
-}
-
-static void
-spice_vmc_output_stream_write_async(GOutputStream *stream,
-                                    const void *buffer,
-                                    gsize count,
-                                    int io_priority,
-                                    GCancellable *cancellable,
-                                    GAsyncReadyCallback callback,
-                                    gpointer user_data)
-{
-    SpiceVmcOutputStream *self = SPICE_VMC_OUTPUT_STREAM(stream);
-    GSimpleAsyncResult *simple;
-
-    SPICE_DEBUG("spicevmc write async");
-    /* an AsyncResult to forward async op to channel */
-    simple = g_simple_async_result_new(G_OBJECT(self), callback, user_data,
-                                       spice_vmc_output_stream_write_async);
-
-    spice_vmc_write_async(self->channel, buffer, count,
-                          cancellable, write_cb,
-                          simple);
-}
-
-/* STREAM */
-
-struct _SpiceVmcStream
-{
-    GIOStream parent_instance;
-
-    SpiceChannel *channel; /* weak */
-    SpiceVmcInputStream *in;
-    SpiceVmcOutputStream *out;
-};
-
-struct _SpiceVmcStreamClass
-{
-    GIOStreamClass parent_class;
-};
-
-static void            spice_vmc_stream_finalize          (GObject   *object);
-static GInputStream *  spice_vmc_stream_get_input_stream  (GIOStream *stream);
-static GOutputStream * spice_vmc_stream_get_output_stream (GIOStream *stream);
-
-G_DEFINE_TYPE(SpiceVmcStream, spice_vmc_stream, G_TYPE_IO_STREAM)
-
-static void
-spice_vmc_stream_class_init(SpiceVmcStreamClass *klass)
-{
-    GObjectClass *object_class;
-    GIOStreamClass *iostream_class;
-
-    object_class = G_OBJECT_CLASS(klass);
-    object_class->finalize = spice_vmc_stream_finalize;
-
-    iostream_class = G_IO_STREAM_CLASS(klass);
-    iostream_class->get_input_stream = spice_vmc_stream_get_input_stream;
-    iostream_class->get_output_stream = spice_vmc_stream_get_output_stream;
-}
-
-static void
-spice_vmc_stream_finalize(GObject *object)
-{
-    SpiceVmcStream *self = SPICE_VMC_STREAM(object);
-
-    g_clear_object(&self->in);
-    g_clear_object(&self->out);
-
-    G_OBJECT_CLASS(spice_vmc_stream_parent_class)->finalize(object);
-}
-
-static void
-spice_vmc_stream_init(SpiceVmcStream *self)
-{
-}
-
-G_GNUC_INTERNAL SpiceVmcStream *
-spice_vmc_stream_new(SpiceChannel *channel)
-{
-    SpiceVmcStream *self;
-
-    self = g_object_new(SPICE_TYPE_VMC_STREAM, NULL);
-    self->channel = channel;
-
-    return self;
-}
-
-static GInputStream *
-spice_vmc_stream_get_input_stream(GIOStream *stream)
-{
-    SpiceVmcStream *self = SPICE_VMC_STREAM(stream);
-
-    if (!self->in)
-        self->in = spice_vmc_input_stream_new();
-
-    return G_INPUT_STREAM(self->in);
-}
-
-static GOutputStream *
-spice_vmc_stream_get_output_stream(GIOStream *stream)
-{
-    SpiceVmcStream *self = SPICE_VMC_STREAM(stream);
-
-    if (!self->out)
-        self->out = spice_vmc_output_stream_new(self->channel);
-
-    return G_OUTPUT_STREAM(self->out);
-}
diff --git a/gtk/vmcstream.h b/gtk/vmcstream.h
deleted file mode 100644
index 1316b77..0000000
--- a/gtk/vmcstream.h
+++ /dev/null
@@ -1,81 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2013 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_VMC_STREAM_H__
-#define __SPICE_VMC_STREAM_H__
-
-#include <gio/gio.h>
-
-#include "spice-types.h"
-
-G_BEGIN_DECLS
-
-#define SPICE_TYPE_VMC_INPUT_STREAM         (spice_vmc_input_stream_get_type ())
-#define SPICE_VMC_INPUT_STREAM(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), SPICE_TYPE_VMC_INPUT_STREAM, SpiceVmcInputStream))
-#define SPICE_VMC_INPUT_STREAM_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), SPICE_TYPE_VMC_INPUT_STREAM, SpiceVmcInputStreamClass))
-#define SPICE_IS_VMC_INPUT_STREAM(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), SPICE_TYPE_VMC_INPUT_STREAM))
-#define SPICE_IS_VMC_INPUT_STREAM_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), SPICE_TYPE_VMC_INPUT_STREAM))
-#define SPICE_VMC_INPUT_STREAM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), SPICE_TYPE_VMC_INPUT_STREAM, SpiceVmcInputStreamClass))
-
-typedef struct _SpiceVmcInputStreamClass     SpiceVmcInputStreamClass;
-typedef struct _SpiceVmcInputStream          SpiceVmcInputStream;
-
-GType          spice_vmc_input_stream_get_type   (void) G_GNUC_CONST;
-void           spice_vmc_input_stream_co_data    (SpiceVmcInputStream *input,
-                                                  const gpointer data,
-                                                  gsize size);
-
-void           spice_vmc_input_stream_read_all_async(GInputStream        *stream,
-                                                     void                *buffer,
-                                                     gsize                count,
-                                                     int                  io_priority,
-                                                     GCancellable        *cancellable,
-                                                     GAsyncReadyCallback  callback,
-                                                     gpointer             user_data);
-gssize         spice_vmc_input_stream_read_all_finish(GInputStream       *stream,
-                                                      GAsyncResult       *result,
-                                                      GError            **error);
-
-
-#define SPICE_TYPE_VMC_OUTPUT_STREAM         (spice_vmc_output_stream_get_type ())
-#define SPICE_VMC_OUTPUT_STREAM(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), SPICE_TYPE_VMC_OUTPUT_STREAM, SpiceVmcOutputStream))
-#define SPICE_VMC_OUTPUT_STREAM_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), SPICE_TYPE_VMC_OUTPUT_STREAM, SpiceVmcOutputStreamClass))
-#define SPICE_IS_VMC_OUTPUT_STREAM(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), SPICE_TYPE_VMC_OUTPUT_STREAM))
-#define SPICE_IS_VMC_OUTPUT_STREAM_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), SPICE_TYPE_VMC_OUTPUT_STREAM))
-#define SPICE_VMC_OUTPUT_STREAM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), SPICE_TYPE_VMC_OUTPUT_STREAM, SpiceVmcOutputStreamClass))
-
-typedef struct _SpiceVmcOutputStreamClass     SpiceVmcOutputStreamClass;
-typedef struct _SpiceVmcOutputStream          SpiceVmcOutputStream;
-
-GType           spice_vmc_output_stream_get_type (void) G_GNUC_CONST;
-
-#define SPICE_TYPE_VMC_STREAM                (spice_vmc_stream_get_type ())
-#define SPICE_VMC_STREAM(o)                  (G_TYPE_CHECK_INSTANCE_CAST ((o), SPICE_TYPE_VMC_STREAM, SpiceVmcStream))
-#define SPICE_VMC_STREAM_CLASS(k)            (G_TYPE_CHECK_CLASS_CAST((k), SPICE_TYPE_VMC_STREAM, SpiceVmcStreamClass))
-#define SPICE_IS_VMC_STREAM(o)               (G_TYPE_CHECK_INSTANCE_TYPE ((o), SPICE_TYPE_VMC_STREAM))
-#define SPICE_IS_VMC_STREAM_CLASS(k)         (G_TYPE_CHECK_CLASS_TYPE ((k), SPICE_TYPE_VMC_STREAM))
-#define SPICE_VMC_STREAM_GET_CLASS(o)        (G_TYPE_INSTANCE_GET_CLASS ((o), SPICE_TYPE_VMC_STREAM, SpiceVmcStreamClass))
-
-typedef struct _SpiceVmcStreamClass           SpiceVmcStreamClass;
-typedef struct _SpiceVmcStream                SpiceVmcStream;
-
-GType           spice_vmc_stream_get_type        (void) G_GNUC_CONST;
-SpiceVmcStream* spice_vmc_stream_new             (SpiceChannel *channel);
-
-G_END_DECLS
-
-#endif /* __SPICE_VMC_STREAM_H__ */
diff --git a/gtk/vncdisplaykeymap.c b/gtk/vncdisplaykeymap.c
deleted file mode 100644
index 6bf351f..0000000
--- a/gtk/vncdisplaykeymap.c
+++ /dev/null
@@ -1,323 +0,0 @@
-/*
- * Copyright (C) 2008  Anthony Liguori <anthony at codemonkey.ws>
- * Copyright (C) 2009-2010 Daniel P. Berrange <dan at berrange.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License version 2 as
- * published by the Free Software Foundation.
- *
- */
-#include "config.h"
-
-#include <gtk/gtk.h>
-#include <gdk/gdk.h>
-#include <gdk/gdkkeysyms.h>
-#include "gtk-compat.h"
-#include "vncdisplaykeymap.h"
-
-#include "spice-util.h"
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "vnc-keymap"
-#define VNC_DEBUG(message) SPICE_DEBUG(message);
-
-/*
- * This table is taken from QEMU x_keymap.c, under the terms:
- *
- * Copyright (c) 2003 Fabrice Bellard
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-
-
-/* Compatability code to allow build on Gtk2 and Gtk3 */
-#ifndef GDK_Tab
-#define GDK_Tab GDK_KEY_Tab
-#endif
-
-/* keycode translation for sending ISO_Left_Send
- * to vncserver
- */
-static struct {
-	GdkKeymapKey *keys;
-	gint n_keys;
-	guint keyval;
-} untranslated_keys[] = {{NULL, 0, GDK_Tab}};
-
-static unsigned int ref_count_for_untranslated_keys = 0;
-
-#ifdef GDK_WINDOWING_WAYLAND
-#include <gdk/gdkwayland.h>
-#endif
-
-#ifdef GDK_WINDOWING_BROADWAY
-#include <gdk/gdkbroadway.h>
-#endif
-
-#if defined(GDK_WINDOWING_X11) || defined(GDK_WINDOWING_WAYLAND)
-/* Xorg Linux + evdev (offset evdev keycodes) */
-#include "vncdisplaykeymap_xorgevdev2xtkbd.c"
-#endif
-
-#ifdef GDK_WINDOWING_X11
-#include <gdk/gdkx.h>
-#include <X11/XKBlib.h>
-#include <stdbool.h>
-#include <string.h>
-
-/* Xorg Linux + kbd (offset + mangled XT keycodes) */
-#include "vncdisplaykeymap_xorgkbd2xtkbd.c"
-/* Xorg OS-X aka XQuartz (offset OS-X keycodes) */
-#include "vncdisplaykeymap_xorgxquartz2xtkbd.c"
-/* Xorg Cygwin aka XWin (offset + mangled XT keycodes) */
-#include "vncdisplaykeymap_xorgxwin2xtkbd.c"
-
-/* Gtk2 compat */
-#ifndef GDK_IS_X11_WINDOW
-#define GDK_IS_X11_WINDOW(win) (win == win)
-#endif
-#endif
-
-#ifdef GDK_WINDOWING_WIN32
-/* Win32 native virtual keycodes */
-#include "vncdisplaykeymap_win322xtkbd.c"
-
-/* Gtk2 compat */
-#ifndef GDK_IS_WIN32_WINDOW
-#define GDK_IS_WIN32_WINDOW(win) (win == win)
-#endif
-#endif
-
-#ifdef GDK_WINDOWING_QUARTZ
-/* OS-X native keycodes */
-#include "vncdisplaykeymap_osx2xtkbd.c"
-
-/* Gtk2 compat */
-#ifndef GDK_IS_QUARTZ_WINDOW
-#define GDK_IS_QUARTZ_WINDOW(win) (win == win)
-#endif
-#endif
-
-#ifdef GDK_WINDOWING_BROADWAY
-/* X11 keysyms */
-#include "vncdisplaykeymap_x112xtkbd.c"
-
-/* Gtk2 compat */
-#ifndef GDK_IS_BROADWAY_WINDOW
-#define GDK_IS_BROADWAY_WINDOW(win) (win == win)
-#endif
-
-#endif
-
-#ifdef GDK_WINDOWING_X11
-
-#define STRPREFIX(a,b) (strncmp((a),(b),strlen((b))) == 0)
-
-static gboolean check_for_xwin(GdkDisplay *dpy)
-{
-	char *vendor = ServerVendor(gdk_x11_display_get_xdisplay(dpy));
-
-	if (strstr(vendor, "Cygwin/X"))
-		return TRUE;
-
-	return FALSE;
-}
-
-static gboolean check_for_xquartz(GdkDisplay *dpy)
-{
-	int nextensions;
-	int i;
-	gboolean match = FALSE;
-	char **extensions = XListExtensions(gdk_x11_display_get_xdisplay(dpy),
-					    &nextensions);
-	for (i = 0 ; extensions != NULL && i < nextensions ; i++) {
-		if (strcmp(extensions[i], "Apple-WM") == 0 ||
-		    strcmp(extensions[i], "Apple-DRI") == 0)
-			match = TRUE;
-	}
-	if (extensions)
-		XFreeExtensionList(extensions);
-
-	return match;
-}
-#endif
-
-const guint16 *vnc_display_keymap_gdk2xtkbd_table(GdkWindow *window,
-                                                  size_t *maplen)
-{
-#ifdef GDK_WINDOWING_X11
-	if (GDK_IS_X11_WINDOW(window)) {
-		XkbDescPtr desc;
-		const gchar *keycodes = NULL;
-                GdkDisplay *dpy = gdk_window_get_display(window);
-
-		/* There is no easy way to determine what X11 server
-		 * and platform & keyboard driver is in use. Thus we
-		 * do best guess heuristics.
-		 *
-		 * This will need more work for people with other
-		 * X servers..... patches welcomed.
-		 */
-
-		Display *xdisplay = gdk_x11_display_get_xdisplay(dpy);
-		desc = XkbGetMap(xdisplay,
-				      XkbGBN_AllComponentsMask,
-				      XkbUseCoreKbd);
-		if (desc) {
-			if (XkbGetNames(xdisplay, XkbKeycodesNameMask, desc) == Success) {
-				keycodes = gdk_x11_get_xatom_name(desc->names->keycodes);
-				if (!keycodes)
-					g_warning("could not lookup keycode name");
-			}
-			XkbFreeKeyboard(desc, XkbGBN_AllComponentsMask, True);
-		}
-
-		if (check_for_xwin(dpy)) {
-			VNC_DEBUG("Using xwin keycode mapping");
-			*maplen = G_N_ELEMENTS(keymap_xorgxwin2xtkbd);
-			return keymap_xorgxwin2xtkbd;
-		} else if (check_for_xquartz(dpy)) {
-			VNC_DEBUG("Using xquartz keycode mapping");
-			*maplen = G_N_ELEMENTS(keymap_xorgxquartz2xtkbd);
-			return keymap_xorgxquartz2xtkbd;
-		} else if (keycodes && STRPREFIX(keycodes, "evdev")) {
-			VNC_DEBUG("Using evdev keycode mapping");
-			*maplen = G_N_ELEMENTS(keymap_xorgevdev2xtkbd);
-			return keymap_xorgevdev2xtkbd;
-		} else if (keycodes && STRPREFIX(keycodes, "xfree86")) {
-			VNC_DEBUG("Using xfree86 keycode mapping");
-			*maplen = G_N_ELEMENTS(keymap_xorgkbd2xtkbd);
-			return keymap_xorgkbd2xtkbd;
-		} else {
-			g_warning("Unknown keycode mapping '%s'.\n"
-				  "Please report to gtk-vnc-list at gnome.org\n"
-				  "including the following information:\n"
-				  "\n"
-				  "  - Operating system\n"
-				  "  - GDK build\n"
-				  "  - X11 Server\n"
-				  "  - xprop -root\n"
-				  "  - xdpyinfo\n",
-				  keycodes);
-			return NULL;
-		}
-	}
-#endif
-
-#ifdef GDK_WINDOWING_WIN32
-	if (GDK_IS_WIN32_WINDOW(window)) {
-		VNC_DEBUG("Using Win32 virtual keycode mapping");
-		*maplen = G_N_ELEMENTS(keymap_win322xtkbd);
-		return keymap_win322xtkbd;
-	}
-#endif
-
-#ifdef GDK_WINDOWING_QUARTZ
-	if (GDK_IS_QUARTZ_WINDOW(window)) {
-		VNC_DEBUG("Using OS-X virtual keycode mapping");
-		*maplen = G_N_ELEMENTS(keymap_osx2xtkbd);
-		return keymap_osx2xtkbd;
-	}
-#endif
-
-#ifdef GDK_WINDOWING_WAYLAND
-	if (GDK_IS_WAYLAND_WINDOW(window)) {
-		VNC_DEBUG("Using Wayland Xorg/evdev virtual keycode mapping");
-		*maplen = G_N_ELEMENTS(keymap_xorgevdev2xtkbd);
-		return keymap_xorgevdev2xtkbd;
-        }
-#endif
-
-#ifdef GDK_WINDOWING_BROADWAY
-	if (GDK_IS_BROADWAY_WINDOW(window)) {
-                g_warning("experimental: using broadway, x11 virtual keysym mapping - with very limited support. See also https://bugzilla.gnome.org/show_bug.cgi?id=700105");
-
-			*maplen = G_N_ELEMENTS(keymap_x112xtkbd);
-			return keymap_x112xtkbd;
-        }
-#endif
-
-	g_warning("Unsupported GDK Windowing platform.\n"
-		  "Disabling extended keycode tables.\n"
-		  "Please report to gtk-vnc-list at gnome.org\n"
-		  "including the following information:\n"
-		  "\n"
-		  "  - Operating system\n"
-		  "  - GDK Windowing system build\n");
-	return NULL;
-}
-
-guint16 vnc_display_keymap_gdk2xtkbd(const guint16 *keycode_map,
-				     size_t keycode_maplen,
-				     guint16 keycode)
-{
-	if (!keycode_map)
-		return 0;
-	if (keycode >= keycode_maplen)
-		return 0;
-	return keycode_map[keycode];
-}
-
-/* Set the keymap entries */
-void vnc_display_keyval_set_entries(void)
-{
-	size_t i;
-	if (ref_count_for_untranslated_keys == 0)
-		for (i = 0; i < sizeof(untranslated_keys) / sizeof(untranslated_keys[0]); i++)
-			gdk_keymap_get_entries_for_keyval(gdk_keymap_get_default(),
-							  untranslated_keys[i].keyval,
-							  &untranslated_keys[i].keys,
-							  &untranslated_keys[i].n_keys);
-	ref_count_for_untranslated_keys++;
-}
-
-/* Free the keymap entries */
-void vnc_display_keyval_free_entries(void)
-{
-	size_t i;
-
-	if (ref_count_for_untranslated_keys == 0)
-		return;
-
-	ref_count_for_untranslated_keys--;
-	if (ref_count_for_untranslated_keys == 0)
-		for (i = 0; i < sizeof(untranslated_keys) / sizeof(untranslated_keys[0]); i++)
-			g_free(untranslated_keys[i].keys);
-
-}
-
-/* Get the keyval from the keycode without the level. */
-guint vnc_display_keyval_from_keycode(guint keycode, guint keyval)
-{
-	size_t i;
-	for (i = 0; i < sizeof(untranslated_keys) / sizeof(untranslated_keys[0]); i++) {
-		if (keycode == untranslated_keys[i].keys[0].keycode) {
-			return untranslated_keys[i].keyval;
-		}
-	}
-
-	return keyval;
-}
-/*
- * Local variables:
- *  c-indent-level: 8
- *  c-basic-offset: 8
- *  tab-width: 8
- * End:
- */
diff --git a/gtk/vncdisplaykeymap.h b/gtk/vncdisplaykeymap.h
deleted file mode 100644
index 3ec55d5..0000000
--- a/gtk/vncdisplaykeymap.h
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * GTK VNC Widget
- *
- * Copyright (C) 2006  Anthony Liguori <anthony at codemonkey.ws>
- * Copyright (C) 2009-2010 Daniel P. Berrange <dan at berrange.com>
- *
- * 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.0 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, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
- */
-
-#ifndef VNC_DISPLAY_KEYMAP_H
-#define VNC_DISPLAY_KEYMAP_H
-
-#include <glib.h>
-
-const guint16 *vnc_display_keymap_gdk2xtkbd_table(GdkWindow *window,
-                                                  size_t *maplen);
-guint16 vnc_display_keymap_gdk2xtkbd(const guint16 *keycode_map,
-                                     size_t keycode_maplen,
-                                     guint16 keycode);
-void vnc_display_keyval_set_entries(void);
-void vnc_display_keyval_free_entries(void);
-guint vnc_display_keyval_from_keycode(guint keycode, guint keyval);
-
-#endif /* VNC_DISPLAY_KEYMAP_H */
diff --git a/gtk/win-usb-clerk.h b/gtk/win-usb-clerk.h
deleted file mode 100644
index 24da3b4..0000000
--- a/gtk/win-usb-clerk.h
+++ /dev/null
@@ -1,36 +0,0 @@
-#ifndef _H_USBCLERK
-#define _H_USBCLERK
-
-#include <windows.h>
-
-#define USB_CLERK_PIPE_NAME     TEXT("\\\\.\\pipe\\usbclerkpipe")
-#define USB_CLERK_MAGIC         0xDADA
-#define USB_CLERK_VERSION       0x0003
-
-typedef struct USBClerkHeader {
-    UINT16 magic;
-    UINT16 version;
-    UINT16 type;
-    UINT16 size;
-} USBClerkHeader;
-
-enum {
-    USB_CLERK_DRIVER_INSTALL = 1,
-    USB_CLERK_DRIVER_REMOVE,
-    USB_CLERK_REPLY,
-    USB_CLERK_DRIVER_SESSION_INSTALL,
-    USB_CLERK_END_MESSAGE,
-};
-
-typedef struct USBClerkDriverOp {
-    USBClerkHeader hdr;
-    UINT16 vid;
-    UINT16 pid;
-} USBClerkDriverOp;
-
-typedef struct USBClerkReply {
-    USBClerkHeader hdr;
-    UINT32 status;
-} USBClerkReply;
-
-#endif
diff --git a/gtk/win-usb-dev.c b/gtk/win-usb-dev.c
deleted file mode 100644
index 1e4b2d6..0000000
--- a/gtk/win-usb-dev.c
+++ /dev/null
@@ -1,542 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2012 Red Hat, Inc.
-
-   Red Hat Authors:
-   Arnon Gilboa <agilboa at redhat.com>
-   Uri Lublin   <uril at redhat.com>
-
-   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 <windows.h>
-#include <libusb.h>
-#include "win-usb-dev.h"
-#include "spice-marshal.h"
-#include "spice-util.h"
-#include "usbutil.h"
-
-#define G_UDEV_CLIENT_GET_PRIVATE(obj) \
-    (G_TYPE_INSTANCE_GET_PRIVATE((obj), G_UDEV_TYPE_CLIENT, GUdevClientPrivate))
-
-struct _GUdevClientPrivate {
-    libusb_context *ctx;
-    gssize udev_list_size;
-    GList *udev_list;
-    HWND hwnd;
-};
-
-#define G_UDEV_CLIENT_WINCLASS_NAME  TEXT("G_UDEV_CLIENT")
-
-static void g_udev_client_initable_iface_init(GInitableIface  *iface);
-
-G_DEFINE_TYPE_WITH_CODE(GUdevClient, g_udev_client, G_TYPE_OBJECT,
-                        G_IMPLEMENT_INTERFACE(G_TYPE_INITABLE, g_udev_client_initable_iface_init));
-
-
-typedef struct _GUdevDeviceInfo GUdevDeviceInfo;
-
-struct _GUdevDeviceInfo {
-    guint16 bus;
-    guint16 addr;
-    guint16 vid;
-    guint16 pid;
-    guint16 class;
-    gchar sclass[4];
-    gchar sbus[4];
-    gchar saddr[4];
-    gchar svid[8];
-    gchar spid[8];
-};
-
-struct _GUdevDevicePrivate
-{
-    /* FixMe: move above fields to this structure and access them directly */
-    GUdevDeviceInfo *udevinfo;
-};
-
-G_DEFINE_TYPE(GUdevDevice, g_udev_device, G_TYPE_OBJECT)
-
-
-enum
-{
-    UEVENT_SIGNAL,
-    LAST_SIGNAL,
-};
-
-static guint signals[LAST_SIGNAL] = { 0, };
-static GUdevClient *singleton = NULL;
-
-static GUdevDevice *g_udev_device_new(GUdevDeviceInfo *udevinfo);
-static LRESULT CALLBACK wnd_proc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam);
-static gboolean get_usb_dev_info(libusb_device *dev, GUdevDeviceInfo *udevinfo);
-
-//uncomment to debug gudev device lists.
-//#define DEBUG_GUDEV_DEVICE_LISTS
-
-#ifdef DEBUG_GUDEV_DEVICE_LISTS
-static void g_udev_device_print_list(GList *l, const gchar *msg);
-#else
-static void g_udev_device_print_list(GList *l, const gchar *msg) {}
-#endif
-static void g_udev_device_print(GUdevDevice *udev, const gchar *msg);
-
-static gboolean g_udev_skip_search(GUdevDevice *udev);
-
-GQuark g_udev_client_error_quark(void)
-{
-    return g_quark_from_static_string("win-gudev-client-error-quark");
-}
-
-GUdevClient *g_udev_client_new(const gchar* const *subsystems)
-{
-    if (!singleton) {
-        singleton = g_initable_new(G_UDEV_TYPE_CLIENT, NULL, NULL, NULL);
-        return singleton;
-    } else {
-        return g_object_ref(singleton);
-    }
-}
-
-
-/*
- * devs [in,out] an empty devs list in, full devs list out
- * Returns: number-of-devices, or a negative value on error.
- */
-static ssize_t
-g_udev_client_list_devices(GUdevClient *self, GList **devs,
-                           GError **err, const gchar *name)
-{
-    gssize rc;
-    libusb_device **lusb_list, **dev;
-    GUdevClientPrivate *priv;
-    GUdevDeviceInfo *udevinfo;
-    GUdevDevice *udevice;
-    ssize_t n;
-
-    g_return_val_if_fail(G_UDEV_IS_CLIENT(self), -1);
-    g_return_val_if_fail(devs != NULL, -2);
-
-    priv = self->priv;
-
-    g_return_val_if_fail(self->priv->ctx != NULL, -3);
-
-    rc = libusb_get_device_list(priv->ctx, &lusb_list);
-    if (rc < 0) {
-        const char *errstr = spice_usbutil_libusb_strerror(rc);
-        g_warning("%s: libusb_get_device_list failed", name);
-        g_set_error(err, G_UDEV_CLIENT_ERROR, G_UDEV_CLIENT_LIBUSB_FAILED,
-                    "%s: Error getting device list from libusb: %s [%"G_GSSIZE_FORMAT"]",
-                    name, errstr, rc);
-        return -4;
-    }
-
-    n = 0;
-    for (dev = lusb_list; *dev; dev++) {
-        udevinfo = g_new0(GUdevDeviceInfo, 1);
-        get_usb_dev_info(*dev, udevinfo);
-        udevice = g_udev_device_new(udevinfo);
-        if (g_udev_skip_search(udevice)) {
-            g_object_unref(udevice);
-            continue;
-        }
-        *devs = g_list_prepend(*devs, udevice);
-        n++;
-    }
-    libusb_free_device_list(lusb_list, 1);
-
-    return n;
-}
-
-static void g_udev_client_free_device_list(GList **devs)
-{
-    g_return_if_fail(devs != NULL);
-    if (*devs) {
-        g_list_free_full(*devs, g_object_unref);
-        *devs = NULL;
-    }
-}
-
-
-static gboolean
-g_udev_client_initable_init(GInitable *initable, GCancellable *cancellable,
-                            GError **err)
-{
-    GUdevClient *self;
-    GUdevClientPrivate *priv;
-    WNDCLASS wcls;
-    int rc;
-
-    g_return_val_if_fail(G_UDEV_IS_CLIENT(initable), FALSE);
-    g_return_val_if_fail(cancellable == NULL, FALSE);
-
-    self = G_UDEV_CLIENT(initable);
-    priv = self->priv;
-
-    rc = libusb_init(&priv->ctx);
-    if (rc < 0) {
-        const char *errstr = spice_usbutil_libusb_strerror(rc);
-        g_warning("Error initializing USB support: %s [%i]", errstr, rc);
-        g_set_error(err, G_UDEV_CLIENT_ERROR, G_UDEV_CLIENT_LIBUSB_FAILED,
-                    "Error initializing USB support: %s [%i]", errstr, rc);
-        return FALSE;
-    }
-
-    /* get initial device list */
-    priv->udev_list_size = g_udev_client_list_devices(self, &priv->udev_list,
-                                                      err, __FUNCTION__);
-    if (priv->udev_list_size < 0) {
-        goto g_udev_client_init_failed;
-    }
-
-    g_udev_device_print_list(priv->udev_list, "init: first list is: ");
-
-    /* create a hidden window */
-    memset(&wcls, 0, sizeof(wcls));
-    wcls.lpfnWndProc = wnd_proc;
-    wcls.lpszClassName = G_UDEV_CLIENT_WINCLASS_NAME;
-    if (!RegisterClass(&wcls)) {
-        DWORD e = GetLastError();
-        g_warning("RegisterClass failed , %ld", (long)e);
-        g_set_error(err, G_UDEV_CLIENT_ERROR, G_UDEV_CLIENT_WINAPI_FAILED,
-                    "RegisterClass failed: %ld", (long)e);
-        goto g_udev_client_init_failed;
-    }
-    priv->hwnd = CreateWindow(G_UDEV_CLIENT_WINCLASS_NAME,
-                              NULL, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL);
-    if (!priv->hwnd) {
-        DWORD e = GetLastError();
-        g_warning("CreateWindow failed: %ld", (long)e);
-        g_set_error(err, G_UDEV_CLIENT_ERROR, G_UDEV_CLIENT_LIBUSB_FAILED,
-                    "CreateWindow failed: %ld", (long)e);
-        goto g_udev_client_init_failed_unreg;
-    }
-
-    return TRUE;
-
- g_udev_client_init_failed_unreg:
-    UnregisterClass(G_UDEV_CLIENT_WINCLASS_NAME, NULL);
- g_udev_client_init_failed:
-    libusb_exit(priv->ctx);
-    priv->ctx = NULL;
-
-    return FALSE;
-}
-
-static void g_udev_client_initable_iface_init(GInitableIface *iface)
-{
-    iface->init = g_udev_client_initable_init;
-}
-
-GList *g_udev_client_query_by_subsystem(GUdevClient *self, const gchar *subsystem)
-{
-    GList *l = g_list_copy(self->priv->udev_list);
-    g_list_foreach(l, (GFunc)g_object_ref, NULL);
-    return l;
-}
-
-static void g_udev_client_init(GUdevClient *self)
-{
-    self->priv = G_UDEV_CLIENT_GET_PRIVATE(self);
-}
-
-static void g_udev_client_finalize(GObject *gobject)
-{
-    GUdevClient *self = G_UDEV_CLIENT(gobject);
-    GUdevClientPrivate *priv = self->priv;
-
-    singleton = NULL;
-    DestroyWindow(priv->hwnd);
-    UnregisterClass(G_UDEV_CLIENT_WINCLASS_NAME, NULL);
-    g_udev_client_free_device_list(&priv->udev_list);
-
-    /* free libusb context initializing by libusb_init() */
-    g_warn_if_fail(priv->ctx != NULL);
-    libusb_exit(priv->ctx);
-
-    /* Chain up to the parent class */
-    if (G_OBJECT_CLASS(g_udev_client_parent_class)->finalize)
-        G_OBJECT_CLASS(g_udev_client_parent_class)->finalize(gobject);
-}
-
-static void g_udev_client_class_init(GUdevClientClass *klass)
-{
-    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
-
-    gobject_class->finalize = g_udev_client_finalize;
-
-    signals[UEVENT_SIGNAL] =
-        g_signal_new("uevent",
-                     G_OBJECT_CLASS_TYPE(klass),
-                     G_SIGNAL_RUN_FIRST,
-                     G_STRUCT_OFFSET(GUdevClientClass, uevent),
-                     NULL, NULL,
-                     g_cclosure_user_marshal_VOID__BOXED_BOXED,
-                     G_TYPE_NONE,
-                     2,
-                     G_TYPE_STRING,
-                     G_UDEV_TYPE_DEVICE);
-
-    g_type_class_add_private(klass, sizeof(GUdevClientPrivate));
-}
-
-static gboolean get_usb_dev_info(libusb_device *dev, GUdevDeviceInfo *udevinfo)
-{
-    struct libusb_device_descriptor desc;
-
-    g_return_val_if_fail(dev, FALSE);
-    g_return_val_if_fail(udevinfo, FALSE);
-
-    if (libusb_get_device_descriptor(dev, &desc) < 0) {
-        g_warning("cannot get device descriptor %p", dev);
-        return FALSE;
-    }
-
-    udevinfo->bus   = libusb_get_bus_number(dev);
-    udevinfo->addr  = libusb_get_device_address(dev);
-    udevinfo->class = desc.bDeviceClass;
-    udevinfo->vid   = desc.idVendor;
-    udevinfo->pid   = desc.idProduct;
-    snprintf(udevinfo->sclass, sizeof(udevinfo->sclass), "%d", udevinfo->class);
-    snprintf(udevinfo->sbus,   sizeof(udevinfo->sbus),   "%d", udevinfo->bus);
-    snprintf(udevinfo->saddr,  sizeof(udevinfo->saddr),  "%d", udevinfo->addr);
-    snprintf(udevinfo->svid,   sizeof(udevinfo->svid),   "%d", udevinfo->vid);
-    snprintf(udevinfo->spid,   sizeof(udevinfo->spid),   "%d", udevinfo->pid);
-    return TRUE;
-}
-
-/* Only vid:pid are compared */
-static gboolean gudev_devices_are_equal(GUdevDevice *a, GUdevDevice *b)
-{
-    GUdevDeviceInfo *ai, *bi;
-    gboolean same_vid;
-    gboolean same_pid;
-
-    ai = a->priv->udevinfo;
-    bi = b->priv->udevinfo;
-
-    same_vid  = (ai->vid == bi->vid);
-    same_pid  = (ai->pid == bi->pid);
-
-    return (same_pid && same_vid);
-}
-
-
-/* Assumes each event stands for a single device change (at most) */
-static void handle_dev_change(GUdevClient *self)
-{
-    GUdevClientPrivate *priv = self->priv;
-    GUdevDevice *changed_dev = NULL;
-    ssize_t dev_count;
-    int is_dev_change;
-    GError *err = NULL;
-    GList *now_devs = NULL;
-    GList *llist, *slist; /* long-list and short-list*/
-    GList *lit, *sit; /* iterators for long-list and short-list */
-    GUdevDevice *ldev, *sdev; /* devices on long-list and short-list */
-
-    dev_count = g_udev_client_list_devices(self, &now_devs, &err,
-                                           __FUNCTION__);
-    g_return_if_fail(dev_count >= 0);
-
-    SPICE_DEBUG("number of current devices %"G_GSSIZE_FORMAT
-                ", I know about %"G_GSSIZE_FORMAT" devices",
-                dev_count, priv->udev_list_size);
-
-    is_dev_change = dev_count - priv->udev_list_size;
-    if (is_dev_change == 0) {
-        g_udev_client_free_device_list(&now_devs);
-        return;
-    }
-
-    if (is_dev_change > 0) {
-        llist  = now_devs;
-        slist = priv->udev_list;
-    } else {
-        llist = priv->udev_list;
-        slist  = now_devs;
-    }
-
-    g_udev_device_print_list(llist, "handle_dev_change: long list:");
-    g_udev_device_print_list(slist, "handle_dev_change: short list:");
-
-    /* Go over the longer list */
-    for (lit = g_list_first(llist); lit != NULL; lit=g_list_next(lit)) {
-        ldev = lit->data;
-        /* Look for dev in the shorther list */
-        for (sit = g_list_first(slist); sit != NULL; sit=g_list_next(sit)) {
-            sdev = sit->data;
-            if (gudev_devices_are_equal(ldev, sdev))
-                break;
-        }
-        if (sit == NULL) {
-            /* Found a device which appears only in the longer list */
-            changed_dev = ldev;
-            break;
-        }
-    }
-
-    if (!changed_dev) {
-        g_warning("couldn't find any device change");
-        goto leave;
-    }
-
-    if (is_dev_change > 0) {
-        g_udev_device_print(changed_dev, ">>> USB device inserted");
-        g_signal_emit(self, signals[UEVENT_SIGNAL], 0, "add", changed_dev);
-    } else {
-        g_udev_device_print(changed_dev, "<<< USB device removed");
-        g_signal_emit(self, signals[UEVENT_SIGNAL], 0, "remove", changed_dev);
-    }
-
-leave:
-    /* keep most recent info: free previous list, and keep current list */
-    g_udev_client_free_device_list(&priv->udev_list);
-    priv->udev_list = now_devs;
-    priv->udev_list_size = dev_count;
-}
-
-static LRESULT CALLBACK wnd_proc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
-{
-    /* Only DBT_DEVNODES_CHANGED recieved */
-    if (message == WM_DEVICECHANGE) {
-        handle_dev_change(singleton);
-    }
-    return DefWindowProc(hwnd, message, wparam, lparam);
-}
-
-/*** GUdevDevice ***/
-
-static void g_udev_device_finalize(GObject *object)
-{
-    GUdevDevice *device =  G_UDEV_DEVICE(object);
-
-    g_free(device->priv->udevinfo);
-    if (G_OBJECT_CLASS(g_udev_device_parent_class)->finalize != NULL)
-        (* G_OBJECT_CLASS(g_udev_device_parent_class)->finalize)(object);
-}
-
-static void g_udev_device_class_init(GUdevDeviceClass *klass)
-{
-    GObjectClass *gobject_class = (GObjectClass *) klass;
-
-    gobject_class->finalize = g_udev_device_finalize;
-    g_type_class_add_private (klass, sizeof(GUdevDevicePrivate));
-}
-
-static void g_udev_device_init(GUdevDevice *device)
-{
-    device->priv = G_TYPE_INSTANCE_GET_PRIVATE(device, G_UDEV_TYPE_DEVICE, GUdevDevicePrivate);
-}
-
-static GUdevDevice *g_udev_device_new(GUdevDeviceInfo *udevinfo)
-{
-    GUdevDevice *device;
-
-    g_return_val_if_fail(udevinfo != NULL, NULL);
-
-    device =  G_UDEV_DEVICE(g_object_new(G_UDEV_TYPE_DEVICE, NULL));
-    device->priv->udevinfo = udevinfo;
-    return device;
-}
-
-const gchar *g_udev_device_get_property(GUdevDevice *udev, const gchar *property)
-{
-    GUdevDeviceInfo* udevinfo;
-
-    g_return_val_if_fail(G_UDEV_DEVICE(udev), NULL);
-    g_return_val_if_fail(property != NULL, NULL);
-
-    udevinfo = udev->priv->udevinfo;
-    g_return_val_if_fail(udevinfo != NULL, NULL);
-
-    if (g_strcmp0(property, "BUSNUM") == 0) {
-        return udevinfo->sbus;
-    } else if (g_strcmp0(property, "DEVNUM") == 0) {
-        return udevinfo->saddr;
-    } else if (g_strcmp0(property, "DEVTYPE") == 0) {
-        return "usb_device";
-    } else if (g_strcmp0(property, "VID") == 0) {
-        return udevinfo->svid;
-    } else if (g_strcmp0(property, "PID") == 0) {
-        return udevinfo->spid;
-    }
-
-    g_warn_if_reached();
-    return NULL;
-}
-
-const gchar *g_udev_device_get_sysfs_attr(GUdevDevice *udev, const gchar *attr)
-{
-    GUdevDeviceInfo* udevinfo;
-
-    g_return_val_if_fail(G_UDEV_DEVICE(udev), NULL);
-    g_return_val_if_fail(attr != NULL, NULL);
-
-    udevinfo = udev->priv->udevinfo;
-    g_return_val_if_fail(udevinfo != NULL, NULL);
-
-
-    if (g_strcmp0(attr, "bDeviceClass") == 0) {
-        return udevinfo->sclass;
-    }
-    g_warn_if_reached();
-    return NULL;
-}
-
-#ifdef DEBUG_GUDEV_DEVICE_LISTS
-static void g_udev_device_print_list(GList *l, const gchar *msg)
-{
-    GList *it;
-
-    for (it = g_list_first(l); it != NULL; it=g_list_next(it)) {
-        g_udev_device_print(it->data, msg);
-    }
-}
-#endif
-
-static void g_udev_device_print(GUdevDevice *udev, const gchar *msg)
-{
-    GUdevDeviceInfo* udevinfo;
-
-    g_return_if_fail(G_UDEV_DEVICE(udev));
-
-    udevinfo = udev->priv->udevinfo;
-    g_return_if_fail(udevinfo != NULL);
-
-    SPICE_DEBUG("%s: %d.%d 0x%04x:0x%04x class %d", msg,
-                udevinfo->bus, udevinfo->addr,
-                udevinfo->vid, udevinfo->pid, udevinfo->class);
-}
-
-static gboolean g_udev_skip_search(GUdevDevice *udev)
-{
-    GUdevDeviceInfo* udevinfo;
-    gboolean skip;
-
-    g_return_val_if_fail(G_UDEV_DEVICE(udev), FALSE);
-
-    udevinfo = udev->priv->udevinfo;
-    g_return_val_if_fail(udevinfo != NULL, FALSE);
-
-    skip = ((udevinfo->addr == 0xff) ||  /* root hub (HCD) */
-#if defined(LIBUSBX_API_VERSION) && (LIBUSBX_API_VERSION >= 0x010000FF)
-            (udevinfo->addr == 1) || /* root hub addr for libusbx >= 1.0.13 */
-#endif
-            (udevinfo->class == LIBUSB_CLASS_HUB) || /* hub*/
-            (udevinfo->addr == 0)); /* bad address */
-    return skip;
-}
diff --git a/gtk/win-usb-dev.h b/gtk/win-usb-dev.h
deleted file mode 100644
index b5c4fce..0000000
--- a/gtk/win-usb-dev.h
+++ /dev/null
@@ -1,110 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2012 Red Hat, Inc.
-
-   Red Hat Authors:
-   Arnon Gilboa <agilboa at redhat.com>
-   Uri Lublin   <uril at redhat.com>
-
-   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 __WIN_USB_DEV_H__
-#define __WIN_USB_DEV_H__
-
-#include <gtk/gtk.h>
-
-G_BEGIN_DECLS
-
-/* GUdevDevice */
-
-#define G_UDEV_TYPE_DEVICE         (g_udev_device_get_type())
-#define G_UDEV_DEVICE(o)           (G_TYPE_CHECK_INSTANCE_CAST((o), G_UDEV_TYPE_DEVICE, GUdevDevice))
-#define G_UDEV_DEVICE_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), G_UDEV_TYPE_DEVICE, GUdevDeviceClass))
-#define G_UDEV_IS_DEVICE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_UDEV_TYPE_DEVICE))
-#define G_UDEV_IS_DEVICE_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE((k), G_UDEV_TYPE_DEVICE))
-#define G_UDEV_DEVICE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), G_UDEV_TYPE_DEVICE, GUdevDeviceClass))
-
-typedef struct _GUdevDevice GUdevDevice;
-typedef struct _GUdevDeviceClass GUdevDeviceClass;
-typedef struct _GUdevDevicePrivate GUdevDevicePrivate;
-
-struct _GUdevDevice
-{
-  GObject parent;
-  GUdevDevicePrivate *priv;
-};
-
-struct _GUdevDeviceClass
-{
-  GObjectClass parent_class;
-};
-
-/* GUdevClient */
-
-#define G_UDEV_TYPE_CLIENT         (g_udev_client_get_type())
-#define G_UDEV_CLIENT(o)           (G_TYPE_CHECK_INSTANCE_CAST((o), G_UDEV_TYPE_CLIENT, GUdevClient))
-#define G_UDEV_CLIENT_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), G_UDEV_TYPE_CLIENT, GUdevClientClass))
-#define G_UDEV_IS_CLIENT(o)        (G_TYPE_CHECK_INSTANCE_TYPE((o), G_UDEV_TYPE_CLIENT))
-#define G_UDEV_IS_CLIENT_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE((k), G_UDEV_TYPE_CLIENT))
-#define G_UDEV_CLIENT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), G_UDEV_TYPE_CLIENT, GUdevClientClass))
-
-typedef struct _GUdevClient GUdevClient;
-typedef struct _GUdevClientClass GUdevClientClass;
-typedef struct _GUdevClientPrivate GUdevClientPrivate;
-
-struct _GUdevClient
-{
-    GObject parent;
-
-    GUdevClientPrivate *priv;
-};
-
-struct _GUdevClientClass
-{
-    GObjectClass parent_class;
-
-    /* signals */
-    void (*uevent)(GUdevClient *client, const gchar *action, GUdevDevice  *device);
-};
-
-GType g_udev_client_get_type(void) G_GNUC_CONST;
-GUdevClient *g_udev_client_new(const gchar* const *subsystems);
-GList *g_udev_client_query_by_subsystem(GUdevClient *client, const gchar *subsystem);
-
-GType g_udev_device_get_type(void) G_GNUC_CONST;
-const gchar *g_udev_device_get_property(GUdevDevice *udev, const gchar *property);
-const gchar *g_udev_device_get_sysfs_attr(GUdevDevice *udev, const gchar *attr);
-
-GQuark g_udev_client_error_quark(void);
-#define G_UDEV_CLIENT_ERROR g_udev_client_error_quark()
-
-/**
- * GUdevClientError:
- * @G_UDEV_CLIENT_ERROR_FAILED: generic error code
- * @G_UDEV_CLIENT_LIBUSB_FAILED: a libusb call failed
- * @G_UDEV_CLIENT_WINAPI_FAILED: a winapi call failed
- *
- * Error codes returned by spice-client API.
- */
-typedef enum
-{
-    G_UDEV_CLIENT_ERROR_FAILED = 1,
-    G_UDEV_CLIENT_LIBUSB_FAILED,
-    G_UDEV_CLIENT_WINAPI_FAILED
-} GUdevClientError;
-
-
-G_END_DECLS
-
-#endif /* __WIN_USB_DEV_H__ */
diff --git a/gtk/win-usb-driver-install.c b/gtk/win-usb-driver-install.c
deleted file mode 100644
index 674a7c6..0000000
--- a/gtk/win-usb-driver-install.c
+++ /dev/null
@@ -1,398 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2011 Red Hat, Inc.
-
-   Red Hat Authors:
-   Uri Lublin <uril at redhat.com>
-
-   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/>.
-*/
-
-/*
- * Some notes:
- * Each installer (instance) opens a named-pipe to talk with win-usb-clerk.
- * Each installer (instance) requests driver installation for a single device.
- */
-
-#include "config.h"
-
-#include <windows.h>
-#include <gio/gio.h>
-#include <gio/gwin32inputstream.h>
-#include <gio/gwin32outputstream.h>
-#include "spice-util.h"
-#include "win-usb-clerk.h"
-#include "win-usb-driver-install.h"
-#include "usb-device-manager-priv.h"
-
-/* ------------------------------------------------------------------ */
-/* gobject glue                                                       */
-
-#define SPICE_WIN_USB_DRIVER_GET_PRIVATE(obj)     \
-    (G_TYPE_INSTANCE_GET_PRIVATE ((obj), SPICE_TYPE_WIN_USB_DRIVER, SpiceWinUsbDriverPrivate))
-
-struct _SpiceWinUsbDriverPrivate {
-    USBClerkReply         reply;
-    GSimpleAsyncResult    *result;
-    GCancellable          *cancellable;
-    HANDLE                handle;
-    SpiceUsbDevice        *device;
-};
-
-
-
-G_DEFINE_TYPE(SpiceWinUsbDriver, spice_win_usb_driver, G_TYPE_OBJECT);
-
-static void spice_win_usb_driver_init(SpiceWinUsbDriver *self)
-{
-    self->priv = SPICE_WIN_USB_DRIVER_GET_PRIVATE(self);
-}
-
-static void spice_win_usb_driver_close(SpiceWinUsbDriver *self)
-{
-    if (self->priv->handle) {
-        CloseHandle(self->priv->handle);
-        self->priv->handle = 0;
-    }
-}
-
-static void spice_win_usb_driver_finalize(GObject *gobject)
-{
-    SpiceWinUsbDriver *self = SPICE_WIN_USB_DRIVER(gobject);
-    SpiceWinUsbDriverPrivate *priv = self->priv;
-
-    spice_win_usb_driver_close(self);
-    g_clear_object(&priv->result);
-
-    if (G_OBJECT_CLASS(spice_win_usb_driver_parent_class)->finalize)
-        G_OBJECT_CLASS(spice_win_usb_driver_parent_class)->finalize(gobject);
-}
-
-static void spice_win_usb_driver_class_init(SpiceWinUsbDriverClass *klass)
-{
-    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
-
-    gobject_class->finalize     = spice_win_usb_driver_finalize;
-
-    g_type_class_add_private(klass, sizeof(SpiceWinUsbDriverPrivate));
-}
-
-/* ------------------------------------------------------------------ */
-/* callbacks                                                          */
-
-void win_usb_driver_handle_reply_cb(GObject *gobject,
-                                    GAsyncResult *read_res,
-                                    gpointer user_data)
-{
-    SpiceWinUsbDriver *self;
-    SpiceWinUsbDriverPrivate *priv;
-
-    GInputStream *istream;
-    GError *err = NULL;
-    gssize bytes;
-
-    g_return_if_fail(SPICE_IS_WIN_USB_DRIVER(user_data));
-    self = SPICE_WIN_USB_DRIVER(user_data);
-    priv = self->priv;
-    istream = G_INPUT_STREAM(gobject);
-
-    bytes = g_input_stream_read_finish(istream, read_res, &err);
-
-    SPICE_DEBUG("Finished reading reply-msg from usbclerk: bytes=%ld "
-                "err_exist?=%d", (long)bytes, err!=NULL);
-
-    g_warn_if_fail(g_input_stream_close(istream, NULL, NULL));
-    g_clear_object(&istream);
-
-    if (err) {
-        g_warning("failed to read reply from usbclerk (%s)", err->message);
-        g_simple_async_result_take_error(priv->result, err);
-        goto failed_reply;
-    }
-
-    if (bytes == 0) {
-        g_warning("unexpected EOF from usbclerk");
-        g_simple_async_result_set_error(priv->result,
-                                        SPICE_WIN_USB_DRIVER_ERROR,
-                                        SPICE_WIN_USB_DRIVER_ERROR_FAILED,
-                                        "unexpected EOF from usbclerk");
-        goto failed_reply;
-    }
-
-    if (bytes != sizeof(priv->reply)) {
-        g_warning("usbclerk size mismatch: read %"G_GSSIZE_FORMAT" bytes,expected "
-                  "%"G_GSSIZE_FORMAT" (header %"G_GSSIZE_FORMAT", size in header %d)",
-                  bytes, sizeof(priv->reply), sizeof(priv->reply.hdr), priv->reply.hdr.size);
-        /* For now just warn, do not fail */
-    }
-
-    if (priv->reply.hdr.magic != USB_CLERK_MAGIC) {
-        g_warning("usbclerk magic mismatch: mine=0x%04x  server=0x%04x",
-                  USB_CLERK_MAGIC, priv->reply.hdr.magic);
-        g_simple_async_result_set_error(priv->result,
-                                        SPICE_WIN_USB_DRIVER_ERROR,
-                                        SPICE_WIN_USB_DRIVER_ERROR_MESSAGE,
-                                        "usbclerk magic mismatch");
-        goto failed_reply;
-    }
-
-    if (priv->reply.hdr.version != USB_CLERK_VERSION) {
-        g_warning("usbclerk version mismatch: mine=0x%04x  server=0x%04x",
-                  USB_CLERK_VERSION, priv->reply.hdr.version);
-        g_simple_async_result_set_error(priv->result,
-                                        SPICE_WIN_USB_DRIVER_ERROR,
-                                        SPICE_WIN_USB_DRIVER_ERROR_MESSAGE,
-                                        "usbclerk version mismatch");
-    }
-
-    if (priv->reply.hdr.type != USB_CLERK_REPLY) {
-        g_warning("usbclerk message with unexpected type %d",
-                  priv->reply.hdr.type);
-        g_simple_async_result_set_error(priv->result,
-                                        SPICE_WIN_USB_DRIVER_ERROR,
-                                        SPICE_WIN_USB_DRIVER_ERROR_MESSAGE,
-                                        "usbclerk message with unexpected type");
-        goto failed_reply;
-    }
-
-    if (priv->reply.hdr.size != bytes) {
-        g_warning("usbclerk message size mismatch: read %"G_GSSIZE_FORMAT" bytes  hdr.size=%d",
-                  bytes, priv->reply.hdr.size);
-        g_simple_async_result_set_error(priv->result,
-                                        SPICE_WIN_USB_DRIVER_ERROR,
-                                        SPICE_WIN_USB_DRIVER_ERROR_MESSAGE,
-                                        "usbclerk message with unexpected size");
-        goto failed_reply;
-    }
-
- failed_reply:
-    g_simple_async_result_complete_in_idle(priv->result);
-    g_clear_object(&priv->result);
-}
-
-/* ------------------------------------------------------------------ */
-/* helper functions                                                   */
-
-static
-gboolean spice_win_usb_driver_send_request(SpiceWinUsbDriver *self, guint16 op,
-                                           guint16 vid, guint16 pid, GError **err)
-{
-    USBClerkDriverOp req;
-    GOutputStream *ostream;
-    SpiceWinUsbDriverPrivate *priv;
-    gsize bytes;
-    gboolean ret;
-
-    SPICE_DEBUG("sending a request to usbclerk service (op=%d vid=0x%04x pid=0x%04x",
-                op, vid, pid);
-
-    g_return_val_if_fail(SPICE_IS_WIN_USB_DRIVER(self), FALSE);
-    priv = self->priv;
-
-    memset(&req, 0, sizeof(req));
-    req.hdr.magic   = USB_CLERK_MAGIC;
-    req.hdr.version = USB_CLERK_VERSION;
-    req.hdr.type    = op;
-    req.hdr.size    = sizeof(req);
-    req.vid = vid;
-    req.pid = pid;
-
-    ostream = g_win32_output_stream_new(priv->handle, FALSE);
-
-    ret = g_output_stream_write_all(ostream, &req, sizeof(req), &bytes, NULL, err);
-    g_warn_if_fail(g_output_stream_close(ostream, NULL, NULL));
-    g_object_unref(ostream);
-    SPICE_DEBUG("write_all request returned %d written bytes %"G_GSIZE_FORMAT
-                " expecting %"G_GSIZE_FORMAT,
-                ret, bytes, sizeof(req));
-    return ret;
-}
-
-static
-void spice_win_usb_driver_read_reply_async(SpiceWinUsbDriver *self)
-{
-    SpiceWinUsbDriverPrivate *priv;
-    GInputStream  *istream;
-
-    g_return_if_fail(SPICE_IS_WIN_USB_DRIVER(self));
-    priv = self->priv;
-
-    SPICE_DEBUG("waiting for a reply from usbclerk");
-
-    istream = g_win32_input_stream_new(priv->handle, FALSE);
-
-    g_input_stream_read_async(istream, &priv->reply, sizeof(priv->reply),
-                              G_PRIORITY_DEFAULT, priv->cancellable,
-                              win_usb_driver_handle_reply_cb, self);
-}
-
-
-/* ------------------------------------------------------------------ */
-/* private api                                                        */
-
-
-G_GNUC_INTERNAL
-SpiceWinUsbDriver *spice_win_usb_driver_new(void)
-{
-    GObject *obj;
-
-    obj = g_object_new(SPICE_TYPE_WIN_USB_DRIVER, NULL);
-
-    return SPICE_WIN_USB_DRIVER(obj);
-}
-
-static
-void spice_win_usb_driver_op(SpiceWinUsbDriver *self,
-                             SpiceUsbDevice *device,
-                             guint16 op_type,
-                             GCancellable *cancellable,
-                             GAsyncReadyCallback callback,
-                             gpointer user_data)
-{
-    guint16 vid, pid;
-    GError *err = NULL;
-    GSimpleAsyncResult *result;
-    SpiceWinUsbDriverPrivate *priv;
-
-    g_return_if_fail(SPICE_IS_WIN_USB_DRIVER(self));
-    g_return_if_fail(device != NULL);
-
-    priv = self->priv;
-
-    result = g_simple_async_result_new(G_OBJECT(self), callback, user_data,
-                                       spice_win_usb_driver_op);
-
-    if (priv->result) { /* allow one install/uninstall request at a time */
-        g_warning("Another request exists -- try later");
-        g_simple_async_result_set_error(result,
-                  SPICE_WIN_USB_DRIVER_ERROR, SPICE_WIN_USB_DRIVER_ERROR_FAILED,
-                  "Another request exists -- try later");
-        goto failed_request;
-    }
-
-
-    vid = spice_usb_device_get_vid(device);
-    pid = spice_usb_device_get_pid(device);
-
-    if (! priv->handle ) {
-        SPICE_DEBUG("win-usb-driver-install: connecting to usbclerk named pipe");
-        priv->handle = CreateFile(USB_CLERK_PIPE_NAME,
-                                  GENERIC_READ | GENERIC_WRITE,
-                                  0, NULL,
-                                  OPEN_EXISTING,
-                                  FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
-                                  NULL);
-        if (priv->handle == INVALID_HANDLE_VALUE) {
-            DWORD errval  = GetLastError();
-            gchar *errstr = g_win32_error_message(errval);
-            g_warning("failed to create a named pipe to usbclerk (%ld) %s",
-                      errval,errstr);
-            g_simple_async_result_set_error(result,
-                      G_IO_ERROR, G_IO_ERROR_FAILED,
-                      "Failed to create named pipe (%ld) %s", errval, errstr);
-            goto failed_request;
-        }
-    }
-
-    if (!spice_win_usb_driver_send_request(self, op_type,
-                                           vid, pid, &err)) {
-        g_warning("failed to send a request to usbclerk %s", err->message);
-        g_simple_async_result_take_error(result, err);
-        goto failed_request;
-    }
-
-    /* set up for async read */
-    priv->result = result;
-    priv->device = device;
-    priv->cancellable = cancellable;
-
-    spice_win_usb_driver_read_reply_async(self);
-
-    return;
-
- failed_request:
-    g_simple_async_result_complete_in_idle(result);
-    g_clear_object(&result);
-}
-
-
-
-/**
- * spice_win_usb_driver_install:
- * Start libusb driver installation for @device
- *
- * A new NamedPipe is created for each request.
- *
- * Returns: TRUE if a request was sent to usbclerk
- *          FALSE upon failure to send a request.
- */
-G_GNUC_INTERNAL
-void spice_win_usb_driver_install(SpiceWinUsbDriver *self,
-                                  SpiceUsbDevice *device,
-                                  GCancellable *cancellable,
-                                  GAsyncReadyCallback callback,
-                                  gpointer user_data)
-{
-    SPICE_DEBUG("Win usb driver installation started");
-
-    spice_win_usb_driver_op(self, device, USB_CLERK_DRIVER_SESSION_INSTALL,
-                            cancellable, callback, user_data);
-}
-
-G_GNUC_INTERNAL
-void spice_win_usb_driver_uninstall(SpiceWinUsbDriver *self,
-                                    SpiceUsbDevice *device,
-                                    GCancellable *cancellable,
-                                    GAsyncReadyCallback callback,
-                                    gpointer user_data)
-{
-    SPICE_DEBUG("Win usb driver uninstall operation started");
-
-    spice_win_usb_driver_op(self, device, USB_CLERK_DRIVER_REMOVE, cancellable,
-                            callback, user_data);
-}
-
-
-/**
- * Returns: currently returns 0 (failure) and 1 (success)
- * possibly later we'll add error-codes
- */
-G_GNUC_INTERNAL
-gint spice_win_usb_driver_install_finish(SpiceWinUsbDriver *self,
-                                          GAsyncResult *res, GError **err)
-{
-    GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT(res);
-
-    g_return_val_if_fail(SPICE_IS_WIN_USB_DRIVER(self), 0);
-    g_return_val_if_fail(g_simple_async_result_is_valid(res, G_OBJECT(self),
-                                                        spice_win_usb_driver_op),
-                         FALSE);
-    if (g_simple_async_result_propagate_error(result, err))
-        return 0;
-
-    return self->priv->reply.status;
-}
-
-G_GNUC_INTERNAL
-SpiceUsbDevice *spice_win_usb_driver_get_device(SpiceWinUsbDriver *self)
-{
-    g_return_val_if_fail(SPICE_IS_WIN_USB_DRIVER(self), 0);
-
-    return self->priv->device;
-}
-
-GQuark spice_win_usb_driver_error_quark(void)
-{
-    return g_quark_from_static_string("spice-win-usb-driver-error-quark");
-}
diff --git a/gtk/win-usb-driver-install.h b/gtk/win-usb-driver-install.h
deleted file mode 100644
index 034abf9..0000000
--- a/gtk/win-usb-driver-install.h
+++ /dev/null
@@ -1,104 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2011 Red Hat, Inc.
-
-   Red Hat Authors:
-   Uri Lublin <uril at redhat.com>
-
-   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_WIN_USB_DRIVER_H
-#define SPICE_WIN_USB_DRIVER_H
-
-#include "usb-device-manager.h"
-
-G_BEGIN_DECLS
-
-GQuark win_usb_driver_error_quark(void);
-
-
-#define SPICE_TYPE_WIN_USB_DRIVER      (spice_win_usb_driver_get_type ())
-#define SPICE_WIN_USB_DRIVER(obj)      (G_TYPE_CHECK_INSTANCE_CAST ((obj),    \
-            SPICE_TYPE_WIN_USB_DRIVER, SpiceWinUsbDriver))
-#define SPICE_IS_WIN_USB_DRIVER(obj)   (G_TYPE_CHECK_INSTANCE_TYPE ((obj),    \
-            SPICE_TYPE_WIN_USB_DRIVER))
-#define SPICE_WIN_USB_DRIVER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass),  \
-            SPICE_TYPE_WIN_USB_DRIVER, SpiceWinUsbDriverClass))
-#define SPICE_IS_WIN_USB_DRIVER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),\
-            SPICE_TYPE_WIN_USB_DRIVER))
-#define SPICE_WIN_USB_DRIVER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),\
-            SPICE_TYPE_WIN_USB_DRIVER, SpiceWinUsbDriverClass))
-
-typedef struct _SpiceWinUsbDriver          SpiceWinUsbDriver;
-typedef struct _SpiceWinUsbDriverClass     SpiceWinUsbDriverClass;
-typedef struct _SpiceWinUsbDriverPrivate   SpiceWinUsbDriverPrivate;
-
-struct _SpiceWinUsbDriver
-{
-    GObject parent;
-
-    /*< private >*/
-    SpiceWinUsbDriverPrivate *priv;
-    /* Do not add fields to this struct */
-};
-
-struct _SpiceWinUsbDriverClass
-{
-    GObjectClass parent_class;
-};
-
-GType spice_win_usb_driver_get_type(void);
-
-SpiceWinUsbDriver *spice_win_usb_driver_new(void);
-
-
-void spice_win_usb_driver_install(SpiceWinUsbDriver *self,
-                                  SpiceUsbDevice *device,
-                                  GCancellable *cancellable,
-                                  GAsyncReadyCallback callback,
-                                  gpointer user_data);
-
-void spice_win_usb_driver_uninstall(SpiceWinUsbDriver *self,
-                                    SpiceUsbDevice *device,
-                                    GCancellable *cancellable,
-                                    GAsyncReadyCallback callback,
-                                    gpointer user_data);
-
-gint spice_win_usb_driver_install_finish(SpiceWinUsbDriver *self,
-                                         GAsyncResult *res, GError **err);
-
-
-SpiceUsbDevice *spice_win_usb_driver_get_device(SpiceWinUsbDriver *self);
-
-#define SPICE_WIN_USB_DRIVER_ERROR spice_win_usb_driver_error_quark()
-
-/**
- * SpiceWinUsbDriverError:
- * @SPICE_WIN_USB_DRIVER_ERROR_FAILED: generic error code
- * @SPICE_WIN_USB_DRIVER_ERROR_MESSAGE: bad message read from clerk
- *
- * Error codes returned by spice-client API.
- */
-typedef enum
-{
-    SPICE_WIN_USB_DRIVER_ERROR_FAILED,
-    SPICE_WIN_USB_DRIVER_ERROR_MESSAGE,
-} SpiceWinUsbDriverError;
-
-GQuark spice_win_usb_driver_error_quark(void);
-
-G_END_DECLS
-
-#endif /* SPICE_WIN_USB_DRIVER_H */
diff --git a/gtk/wocky-http-proxy.c b/gtk/wocky-http-proxy.c
deleted file mode 100644
index ce23b0e..0000000
--- a/gtk/wocky-http-proxy.c
+++ /dev/null
@@ -1,537 +0,0 @@
- /* wocky-http-proxy.c: Source for WockyHttpProxy
- *
- * Copyright (C) 2010 Collabora, Ltd.
- * Copyright (C) 2014 Red Hat, Inc.
- * @author Nicolas Dufresne <nicolas.dufresne at collabora.co.uk>
- * @author Marc-André Lureau <marcandre.lureau at redhat.com>
- *
- * 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, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
- */
-
-#include "config.h"
-
-#include "glib-compat.h"
-#include "wocky-http-proxy.h"
-
-#include <string.h>
-#include <stdlib.h>
-
-
-struct _WockyHttpProxy
-{
-  GObject parent;
-};
-
-struct _WockyHttpProxyClass
-{
-  GObjectClass parent_class;
-};
-
-static void wocky_http_proxy_iface_init (GProxyInterface *proxy_iface);
-
-#define wocky_http_proxy_get_type _wocky_http_proxy_get_type
-G_DEFINE_TYPE_WITH_CODE (WockyHttpProxy, wocky_http_proxy, G_TYPE_OBJECT,
-  G_IMPLEMENT_INTERFACE (G_TYPE_PROXY,
-    wocky_http_proxy_iface_init)
-  g_io_extension_point_set_required_type (
-    g_io_extension_point_register (G_PROXY_EXTENSION_POINT_NAME),
-    G_TYPE_PROXY);
-  g_io_extension_point_implement (G_PROXY_EXTENSION_POINT_NAME,
-    g_define_type_id, "http", 0))
-
-static void
-wocky_http_proxy_init (WockyHttpProxy *proxy)
-{
-}
-
-#define HTTP_END_MARKER "\r\n\r\n"
-
-static gchar *
-create_request (GProxyAddress *proxy_address, gboolean *has_cred)
-{
-  const gchar *hostname;
-  gint port;
-  const gchar *username;
-  const gchar *password;
-  GString *request;
-  gchar *ascii_hostname;
-
-  if (has_cred)
-    *has_cred = FALSE;
-
-  hostname = g_proxy_address_get_destination_hostname (proxy_address);
-  port = g_proxy_address_get_destination_port (proxy_address);
-  username = g_proxy_address_get_username (proxy_address);
-  password = g_proxy_address_get_password (proxy_address);
-
-  request = g_string_new (NULL);
-
-  ascii_hostname = g_hostname_to_ascii (hostname);
-  g_string_append_printf (request,
-      "CONNECT %s:%i HTTP/1.0\r\n"
-        "Host: %s:%i\r\n"
-        "Proxy-Connection: keep-alive\r\n"
-        "User-Agent: GLib/%i.%i\r\n",
-      ascii_hostname, port,
-      ascii_hostname, port,
-      GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION);
-  g_free (ascii_hostname);
-
-  if (username != NULL && password != NULL)
-    {
-      gchar *cred;
-      gchar *base64_cred;
-
-      if (has_cred)
-        *has_cred = TRUE;
-
-      cred = g_strdup_printf ("%s:%s", username, password);
-      base64_cred = g_base64_encode ((guchar *) cred, strlen (cred));
-      g_free (cred);
-      g_string_append_printf (request,
-          "Proxy-Authorization: Basic %s\r\n",
-          base64_cred);
-      g_free (base64_cred);
-    }
-
-  g_string_append (request, "\r\n");
-
-  return g_string_free (request, FALSE);
-}
-
-static gboolean
-check_reply (const gchar *buffer, gboolean has_cred, GError **error)
-{
-  gint err_code;
-  const gchar *ptr = buffer + 7;
-
-  if (strncmp (buffer, "HTTP/1.", 7) != 0
-      || (*ptr != '0' && *ptr != '1'))
-    {
-      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PROXY_FAILED,
-          "Bad HTTP proxy reply");
-      return FALSE;
-    }
-
-  ptr++;
-  while (*ptr == ' ') ptr++;
-
-  err_code = atoi (ptr);
-
-  if (err_code < 200 || err_code >= 300)
-    {
-      const gchar *msg_start;
-      gchar *msg;
-
-      while (g_ascii_isdigit (*ptr))
-        ptr++;
-
-      while (*ptr == ' ')
-        ptr++;
-
-      msg_start = ptr;
-
-      ptr = strchr (msg_start, '\r');
-
-      if (ptr == NULL)
-        ptr = strchr (msg_start, '\0');
-
-      msg = g_strndup (msg_start, ptr - msg_start);
-
-      if (err_code == 407)
-        {
-          if (has_cred)
-            g_set_error (error, G_IO_ERROR, G_IO_ERROR_PROXY_AUTH_FAILED,
-                "HTTP proxy authentication failed");
-          else
-            g_set_error (error, G_IO_ERROR, G_IO_ERROR_PROXY_NEED_AUTH,
-                "HTTP proxy authentication required");
-        }
-      else if (msg[0] == '\0')
-        g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PROXY_FAILED,
-            "Connection failed due to broken HTTP reply");
-      else
-        g_set_error (error, G_IO_ERROR, G_IO_ERROR_PROXY_FAILED,
-            "HTTP proxy connection failed: %i %s",
-            err_code, msg);
-
-      g_free (msg);
-      return FALSE;
-    }
-
-  return TRUE;
-}
-
-static GIOStream *
-wocky_http_proxy_connect (GProxy *proxy,
-    GIOStream *io_stream,
-    GProxyAddress *proxy_address,
-    GCancellable *cancellable,
-    GError **error)
-{
-  GInputStream *in;
-  GOutputStream *out;
-  GDataInputStream *data_in = NULL;
-  gchar *buffer = NULL;
-  gboolean has_cred;
-  GIOStream *tlsconn = NULL;
-
-  if (WOCKY_IS_HTTPS_PROXY (proxy))
-    {
-      tlsconn = g_tls_client_connection_new (io_stream,
-                                             G_SOCKET_CONNECTABLE(proxy_address),
-                                             error);
-      if (!tlsconn)
-          goto error;
-
-      GTlsCertificateFlags tls_validation_flags = G_TLS_CERTIFICATE_VALIDATE_ALL;
-#ifdef DEBUG
-      tls_validation_flags &= ~(G_TLS_CERTIFICATE_UNKNOWN_CA | G_TLS_CERTIFICATE_BAD_IDENTITY);
-#endif
-      g_tls_client_connection_set_validation_flags (G_TLS_CLIENT_CONNECTION (tlsconn),
-                                                    tls_validation_flags);
-      if (!g_tls_connection_handshake (G_TLS_CONNECTION (tlsconn), cancellable, error))
-          goto error;
-
-      io_stream = tlsconn;
-    }
-
-  in = g_io_stream_get_input_stream (io_stream);
-  out = g_io_stream_get_output_stream (io_stream);
-
-  data_in = g_data_input_stream_new (in);
-  g_filter_input_stream_set_close_base_stream (G_FILTER_INPUT_STREAM (data_in),
-      FALSE);
-
-  buffer = create_request (proxy_address, &has_cred);
-  if (!g_output_stream_write_all (out, buffer, strlen (buffer), NULL,
-        cancellable, error))
-      goto error;
-
-  g_free (buffer);
-  buffer = g_data_input_stream_read_until (data_in, HTTP_END_MARKER, NULL,
-      cancellable, error);
-  g_object_unref (data_in);
-  data_in = NULL;
-
-  if (buffer == NULL)
-    {
-      if (error && (*error == NULL))
-        g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PROXY_FAILED,
-            "HTTP proxy server closed connection unexpectedly.");
-      goto error;
-    }
-
-  if (!check_reply (buffer, has_cred, error))
-    goto error;
-
-  g_free (buffer);
-
-  g_object_ref (io_stream);
-  g_clear_object (&tlsconn);
-
-  return io_stream;
-
-error:
-  g_clear_object (&tlsconn);
-  g_clear_object (&data_in);
-  g_free (buffer);
-  return NULL;
-}
-
-
-typedef struct
-{
-  GSimpleAsyncResult *simple;
-  GIOStream *io_stream;
-  gchar *buffer;
-  gssize length;
-  gssize offset;
-  GDataInputStream *data_in;
-  gboolean has_cred;
-  GCancellable *cancellable;
-} ConnectAsyncData;
-
-static void request_write_cb (GObject *source,
-    GAsyncResult *res,
-    gpointer user_data);
-static void reply_read_cb (GObject *source,
-    GAsyncResult *res,
-    gpointer user_data);
-
-static void
-free_connect_data (ConnectAsyncData *data)
-{
-  if (data->io_stream != NULL)
-    g_object_unref (data->io_stream);
-
-  g_free (data->buffer);
-
-  if (data->data_in != NULL)
-    g_object_unref (data->data_in);
-
-  if (data->cancellable != NULL)
-    g_object_unref (data->cancellable);
-
-  g_slice_free (ConnectAsyncData, data);
-}
-
-static void
-complete_async_from_error (ConnectAsyncData *data, GError *error)
-{
-  GSimpleAsyncResult *simple = data->simple;
-
-  if (error == NULL)
-    g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_PROXY_FAILED,
-        "HTTP proxy server closed connection unexpectedly.");
-
-  g_simple_async_result_set_from_error (data->simple, error);
-  g_error_free (error);
-  g_simple_async_result_set_op_res_gpointer (simple, NULL, NULL);
-  g_simple_async_result_complete (simple);
-  g_object_unref (simple);
-}
-
-static void
-do_write (GAsyncReadyCallback callback, ConnectAsyncData *data)
-{
-  GOutputStream *out;
-  out = g_io_stream_get_output_stream (data->io_stream);
-  g_output_stream_write_async (out,
-      data->buffer + data->offset,
-      data->length - data->offset,
-      G_PRIORITY_DEFAULT, data->cancellable,
-      callback, data);
-}
-
-static void
-stream_connected (ConnectAsyncData *data,
-                  GIOStream *io_stream)
-{
-  GInputStream *in;
-
-  data->io_stream = g_object_ref (io_stream);
-  in = g_io_stream_get_input_stream (io_stream);
-  data->data_in = g_data_input_stream_new (in);
-  g_filter_input_stream_set_close_base_stream (G_FILTER_INPUT_STREAM (data->data_in),
-                                               FALSE);
-
-  do_write (request_write_cb, data);
-}
-
-static void
-handshake_completed (GObject *source_object,
-                     GAsyncResult *res,
-                     gpointer user_data)
-{
-  GTlsConnection *conn = G_TLS_CONNECTION (source_object);
-  ConnectAsyncData *data = user_data;
-  GError *error = NULL;
-
-  if (!g_tls_connection_handshake_finish (conn, res, &error))
-    {
-      complete_async_from_error (data, error);
-      return;
-    }
-
-  stream_connected (data, G_IO_STREAM (conn));
-}
-
-static void
-wocky_http_proxy_connect_async (GProxy *proxy,
-    GIOStream *io_stream,
-    GProxyAddress *proxy_address,
-    GCancellable *cancellable,
-    GAsyncReadyCallback callback,
-    gpointer user_data)
-{
-  GSimpleAsyncResult *simple;
-  ConnectAsyncData *data;
-
-  simple = g_simple_async_result_new (G_OBJECT (proxy),
-                                      callback, user_data,
-                                      wocky_http_proxy_connect_async);
-
-  data = g_slice_new0 (ConnectAsyncData);
-  if (cancellable != NULL)
-    data->cancellable = g_object_ref (cancellable);
-  data->simple = simple;
-
-  data->buffer = create_request (proxy_address, &data->has_cred);
-  data->length = strlen (data->buffer);
-  data->offset = 0;
-
-  g_simple_async_result_set_op_res_gpointer (simple, data,
-                                             (GDestroyNotify) free_connect_data);
-
-  if (WOCKY_IS_HTTPS_PROXY (proxy))
-    {
-      GError *error = NULL;
-      GIOStream *tlsconn;
-
-      tlsconn = g_tls_client_connection_new (io_stream,
-                                             G_SOCKET_CONNECTABLE(proxy_address),
-                                             &error);
-      if (!tlsconn)
-        {
-          complete_async_from_error (data, error);
-          return;
-        }
-
-      g_return_if_fail (tlsconn != NULL);
-
-      GTlsCertificateFlags tls_validation_flags = G_TLS_CERTIFICATE_VALIDATE_ALL;
-#ifdef DEBUG
-      tls_validation_flags &= ~(G_TLS_CERTIFICATE_UNKNOWN_CA | G_TLS_CERTIFICATE_BAD_IDENTITY);
-#endif
-      g_tls_client_connection_set_validation_flags (G_TLS_CLIENT_CONNECTION (tlsconn),
-                                                    tls_validation_flags);
-      g_tls_connection_handshake_async (G_TLS_CONNECTION (tlsconn),
-                                        G_PRIORITY_DEFAULT, cancellable,
-                                        handshake_completed, data);
-    }
-  else
-    {
-      stream_connected (data, io_stream);
-    }
-}
-
-static void
-request_write_cb (GObject *source,
-    GAsyncResult *res,
-    gpointer user_data)
-{
-  GError *error = NULL;
-  ConnectAsyncData *data = user_data;
-  gssize written;
-
-  written = g_output_stream_write_finish (G_OUTPUT_STREAM (source),
-      res, &error);
-  if (written < 0)
-    {
-      complete_async_from_error (data, error);
-      return;
-    }
-
-  data->offset += written;
-
-   if (data->offset == data->length)
-    {
-      g_free (data->buffer);
-      data->buffer = NULL;
-
-      g_data_input_stream_read_until_async (data->data_in,
-          HTTP_END_MARKER,
-          G_PRIORITY_DEFAULT,
-          data->cancellable,
-          reply_read_cb, data);
-
-    }
-  else
-    {
-      do_write (request_write_cb, data);
-    }
-}
-
-static void
-reply_read_cb (GObject *source,
-    GAsyncResult *res,
-    gpointer user_data)
-{
-  GError *error = NULL;
-  ConnectAsyncData *data = user_data;
-
-  data->buffer = g_data_input_stream_read_until_finish (data->data_in,
-      res, NULL, &error);
-
-  if (data->buffer == NULL)
-    {
-      complete_async_from_error (data, error);
-      return;
-    }
-
-  if (!check_reply (data->buffer, data->has_cred, &error))
-    {
-      complete_async_from_error (data, error);
-      return;
-    }
-
-  g_simple_async_result_complete (data->simple);
-  g_object_unref (data->simple);
-}
-
-static GIOStream *
-wocky_http_proxy_connect_finish (GProxy *proxy,
-    GAsyncResult *result,
-    GError **error)
-{
-  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result);
-  ConnectAsyncData *data = g_simple_async_result_get_op_res_gpointer (simple);
-
-  if (g_simple_async_result_propagate_error (simple, error))
-    return NULL;
-
-  return g_object_ref (data->io_stream);
-}
-
-static gboolean
-wocky_http_proxy_supports_hostname (GProxy *proxy)
-{
-  return TRUE;
-}
-
-static void
-wocky_http_proxy_class_init (WockyHttpProxyClass *class)
-{
-}
-
-static void
-wocky_http_proxy_iface_init (GProxyInterface *proxy_iface)
-{
-  proxy_iface->connect  = wocky_http_proxy_connect;
-  proxy_iface->connect_async = wocky_http_proxy_connect_async;
-  proxy_iface->connect_finish = wocky_http_proxy_connect_finish;
-  proxy_iface->supports_hostname = wocky_http_proxy_supports_hostname;
-}
-
-struct _WockyHttpsProxy
-{
-  WockyHttpProxy parent;
-};
-
-struct _WockyHttpsProxyClass
-{
-  WockyHttpProxyClass parent_class;
-};
-
-#define wocky_https_proxy_get_type _wocky_https_proxy_get_type
-G_DEFINE_TYPE_WITH_CODE (WockyHttpsProxy, wocky_https_proxy, WOCKY_TYPE_HTTP_PROXY,
-  G_IMPLEMENT_INTERFACE (G_TYPE_PROXY,
-    wocky_http_proxy_iface_init)
-  g_io_extension_point_set_required_type (
-    g_io_extension_point_register (G_PROXY_EXTENSION_POINT_NAME),
-    G_TYPE_PROXY);
-  g_io_extension_point_implement (G_PROXY_EXTENSION_POINT_NAME,
-    g_define_type_id, "https", 0))
-
-static void
-wocky_https_proxy_init (WockyHttpsProxy *proxy)
-{
-}
-
-static void
-wocky_https_proxy_class_init (WockyHttpsProxyClass *class)
-{
-}
diff --git a/gtk/wocky-http-proxy.h b/gtk/wocky-http-proxy.h
deleted file mode 100644
index 9484b51..0000000
--- a/gtk/wocky-http-proxy.h
+++ /dev/null
@@ -1,56 +0,0 @@
- /* wocky-http-proxy.h: Header for WockyHttpProxy
- *
- * Copyright (C) 2010 Collabora, Ltd.
- * @author Nicolas Dufresne <nicolas.dufresne at collabora.co.uk>
- *
- *
- * 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, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
- */
-#ifndef _WOCKY_HTTP_PROXY_H_
-#define _WOCKY_HTTP_PROXY_H_
-
-#include <gio/gio.h>
-
-G_BEGIN_DECLS
-
-#define WOCKY_TYPE_HTTP_PROXY         (_wocky_http_proxy_get_type ())
-#define WOCKY_HTTP_PROXY(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), WOCKY_TYPE_HTTP_PROXY, WockyHttpProxy))
-#define WOCKY_HTTP_PROXY_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), WOCKY_TYPE_HTTP_PROXY, WockyHttpProxyClass))
-#define WOCKY_IS_HTTP_PROXY(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), WOCKY_TYPE_HTTP_PROXY))
-#define WOCKY_IS_HTTP_PROXY_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), WOCKY_TYPE_HTTP_PROXY))
-#define WOCKY_HTTP_PROXY_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), WOCKY_TYPE_HTTP_PROXY, WockyHttpProxyClass))
-
-typedef struct _WockyHttpProxy        WockyHttpProxy;
-typedef struct _WockyHttpProxyClass   WockyHttpProxyClass;
-
-GType _wocky_http_proxy_get_type (void);
-
-#if GLIB_CHECK_VERSION(2, 28, 0)
-#define WOCKY_TYPE_HTTPS_PROXY         (_wocky_https_proxy_get_type ())
-#define WOCKY_HTTPS_PROXY(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), WOCKY_TYPE_HTTPS_PROXY, WockyHttpsProxy))
-#define WOCKY_HTTPS_PROXY_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), WOCKY_TYPE_HTTPS_PROXY, WockyHttpsProxyClass))
-#define WOCKY_IS_HTTPS_PROXY(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), WOCKY_TYPE_HTTPS_PROXY))
-#define WOCKY_IS_HTTPS_PROXY_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), WOCKY_TYPE_HTTPS_PROXY))
-#define WOCKY_HTTPS_PROXY_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), WOCKY_TYPE_HTTPS_PROXY, WockyHttpsProxyClass))
-
-typedef struct _WockyHttpsProxy        WockyHttpsProxy;
-typedef struct _WockyHttpsProxyClass   WockyHttpsProxyClass;
-
-GType _wocky_https_proxy_get_type (void);
-#endif
-
-G_END_DECLS
-
-#endif /* _WOCKY_HTTP_PROXY_H_ */
diff --git a/po/POTFILES.in b/po/POTFILES.in
index d7bf614..f629270 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -1,11 +1,11 @@
-gtk/channel-usbredir.c
-gtk/desktop-integration.c
-gtk/spice-channel.c
-gtk/spice-cmdline.c
-gtk/spice-option.c
-gtk/spicy-screenshot.c
-gtk/spicy-stats.c
-gtk/spicy.c
-gtk/usb-device-manager.c
-gtk/usb-device-widget.c
-gtk/usbutil.c
+src/channel-usbredir.c
+src/desktop-integration.c
+src/spice-channel.c
+src/spice-cmdline.c
+src/spice-option.c
+src/spicy-screenshot.c
+src/spicy-stats.c
+src/spicy.c
+src/usb-device-manager.c
+src/usb-device-widget.c
+src/usbutil.c
diff --git a/po/POTFILES.skip b/po/POTFILES.skip
index 971470e..646ac25 100644
--- a/po/POTFILES.skip
+++ b/po/POTFILES.skip
@@ -1,2 +1,3 @@
 data/spicy.desktop.in
-gtk/phodav/libphodav/chezdav.c
+spice-common/python_modules/spice_parser.py
+spice-common/spice_codegen.py
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..25e2255
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,703 @@
+NULL =
+SUBDIRS =
+
+if WITH_CONTROLLER
+SUBDIRS += controller
+endif
+
+# Avoid need for perl(Text::CSV) by end users
+KEYMAPS =					\
+	vncdisplaykeymap_xorgevdev2xtkbd.c	\
+	vncdisplaykeymap_xorgkbd2xtkbd.c	\
+	vncdisplaykeymap_xorgxquartz2xtkbd.c	\
+	vncdisplaykeymap_xorgxwin2xtkbd.c	\
+	vncdisplaykeymap_osx2xtkbd.c		\
+	vncdisplaykeymap_win322xtkbd.c		\
+	vncdisplaykeymap_x112xtkbd.c		\
+	$(NULL)
+
+# End users build dependencies can be cleaned
+GLIBGENS =					\
+	spice-glib-enums.c			\
+	spice-glib-enums.h			\
+	spice-marshal.c				\
+	spice-marshal.h				\
+	spice-widget-enums.c			\
+	spice-widget-enums.h			\
+	$(NULL)
+
+CLEANFILES = $(GLIBGENS)
+BUILT_SOURCES = $(GLIBGENS) $(KEYMAPS)
+
+EXTRA_DIST =					\
+	$(KEYMAPS)				\
+	decode-glz-tmpl.c			\
+	keymap-gen.pl				\
+	keymaps.csv				\
+	map-file				\
+	spice-glib-sym-file			\
+	spice-gtk-sym-file			\
+	spice-client-gtk-manual.defs		\
+	spice-client-gtk.override		\
+	spice-marshal.txt			\
+	spice-version.h.in			\
+	$(NULL)
+
+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
+endif
+
+lib_LTLIBRARIES = libspice-client-glib-2.0.la
+
+if WITH_GTK
+if HAVE_GTK_2
+lib_LTLIBRARIES += libspice-client-gtk-2.0.la
+else
+lib_LTLIBRARIES += libspice-client-gtk-3.0.la
+endif
+endif
+
+if HAVE_LD_VERSION_SCRIPT
+GLIB_SYMBOLS_LDFLAGS = -Wl,--version-script=${srcdir}/map-file
+GLIB_SYMBOLS_FILE = map-file
+GTK_SYMBOLS_LDFLAGS = $(GLIB_SYMBOLS_LDFLAGS)
+GTK_SYMBOLS_FILE = $(GLIB_SYMBOLS_FILE)
+else
+GLIB_SYMBOLS_LDFLAGS = -export-symbols ${srcdir}/spice-glib-sym-file
+GLIB_SYMBOLS_FILE = spice-glib-sym-file
+GTK_SYMBOLS_LDFLAGS = -export-symbols ${srcdir}/spice-gtk-sym-file
+GTK_SYMBOLS_FILE = spice-gtk-sym-file
+endif
+
+KEYMAP_GEN = $(srcdir)/keymap-gen.pl
+
+SPICE_COMMON_CPPFLAGS =						\
+	-DG_LOG_DOMAIN=\"GSpice\"				\
+	-DSPICE_NO_DEPRECATED					\
+	-DSPICE_GTK_LOCALEDIR=\"${SPICE_GTK_LOCALEDIR}\"	\
+	-DPNP_IDS=\""$(PNP_IDS)"\"				\
+	-DUSB_IDS=\""$(USB_IDS)"\"				\
+	-DSPICE_DISABLE_ABORT					\
+	-I$(top_srcdir)						\
+	$(COMMON_CFLAGS)					\
+	$(PIXMAN_CFLAGS)					\
+	$(PULSE_CFLAGS)						\
+	$(GTK_CFLAGS)						\
+	$(CAIRO_CFLAGS)						\
+	$(GLIB2_CFLAGS)						\
+	$(GIO_CFLAGS)						\
+	$(GOBJECT2_CFLAGS)					\
+	$(SSL_CFLAGS)						\
+	$(SASL_CFLAGS)						\
+	$(GST_CFLAGS)						\
+	$(SMARTCARD_CFLAGS)					\
+	$(USBREDIR_CFLAGS)					\
+	$(GUDEV_CFLAGS)						\
+	$(SOUP_CFLAGS)						\
+	$(PHODAV_CFLAGS)					\
+	$(LZ4_CFLAGS)					\
+	$(NULL)
+
+AM_CPPFLAGS =					\
+	$(SPICE_COMMON_CPPFLAGS)		\
+	$(SPICE_CFLAGS)				\
+	$(NULL)
+
+# http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html
+SPICE_GTK_LDFLAGS_COMMON =		\
+	-version-info 4:0:0		\
+	-no-undefined			\
+	$(GTK_SYMBOLS_LDFLAGS)		\
+	$(NULL)
+
+SPICE_GTK_LIBADD_COMMON =		\
+	libspice-client-glib-2.0.la	\
+	$(GTK_LIBS)			\
+	$(CAIRO_LIBS)			\
+	$(XRANDR_LIBS)			\
+	$(LIBM)				\
+	$(NULL)
+
+SPICE_GTK_SOURCES_COMMON =		\
+	glib-compat.h			\
+	gtk-compat.h			\
+	spice-util.c			\
+	spice-util-priv.h		\
+	spice-gtk-session.c		\
+	spice-gtk-session-priv.h	\
+	spice-widget.c			\
+	spice-widget-priv.h		\
+	vncdisplaykeymap.c		\
+	vncdisplaykeymap.h		\
+	spice-grabsequence.c		\
+	spice-grabsequence.h		\
+	desktop-integration.c		\
+	desktop-integration.h		\
+	usb-device-widget.c		\
+	$(NULL)
+
+nodist_SPICE_GTK_SOURCES_COMMON =	\
+	spice-widget-enums.c		\
+	spice-marshal.c			\
+	$(NULL)
+
+if WITH_X11
+SPICE_GTK_SOURCES_COMMON +=		\
+	spice-widget-x11.c		\
+	$(NULL)
+else
+SPICE_GTK_SOURCES_COMMON +=		\
+	spice-widget-cairo.c		\
+	$(NULL)
+endif
+
+if WITH_GTK
+if HAVE_GTK_2
+libspice_client_gtk_2_0_la_DEPEDENCIES = $(GTK_SYMBOLS_FILE)
+libspice_client_gtk_2_0_la_LDFLAGS = $(SPICE_GTK_LDFLAGS_COMMON)
+libspice_client_gtk_2_0_la_LIBADD = $(SPICE_GTK_LIBADD_COMMON)
+libspice_client_gtk_2_0_la_SOURCES = $(SPICE_GTK_SOURCES_COMMON)
+nodist_libspice_client_gtk_2_0_la_SOURCES = $(nodist_SPICE_GTK_SOURCES_COMMON)
+else
+libspice_client_gtk_3_0_la_DEPEDENCIES = $(GTK_SYMBOLS_FILE)
+libspice_client_gtk_3_0_la_LDFLAGS = $(SPICE_GTK_LDFLAGS_COMMON)
+libspice_client_gtk_3_0_la_LIBADD = $(SPICE_GTK_LIBADD_COMMON)
+libspice_client_gtk_3_0_la_SOURCES = $(SPICE_GTK_SOURCES_COMMON)
+nodist_libspice_client_gtk_3_0_la_SOURCES = $(nodist_SPICE_GTK_SOURCES_COMMON)
+endif
+
+libspice_client_gtkincludedir = $(includedir)/spice-client-gtk-$(SPICE_GTK_API_VERSION)
+libspice_client_gtkinclude_HEADERS =	\
+	spice-gtk-session.h		\
+	spice-widget.h			\
+	spice-grabsequence.h		\
+	usb-device-widget.h		\
+	$(NULL)
+
+nodist_libspice_client_gtkinclude_HEADERS =	\
+	spice-widget-enums.h			\
+	$(NULL)
+endif
+
+libspice_client_glib_2_0_la_DEPENDENCIES = $(GLIB_SYMBOLS_FILE)
+
+libspice_client_glib_2_0_la_LDFLAGS =	\
+	-version-info 13:0:5		\
+	-no-undefined			\
+	$(GLIB_SYMBOLS_LDFLAGS)		\
+	$(NULL)
+
+libspice_client_glib_2_0_la_LIBADD =					\
+	$(top_builddir)/spice-common/common/libspice-common.la		\
+	$(top_builddir)/spice-common/common/libspice-common-client.la	\
+	$(GLIB2_LIBS)							\
+	$(SOUP_LIBS)							\
+	$(GIO_LIBS)							\
+	$(GOBJECT2_LIBS)						\
+	$(JPEG_LIBS)							\
+	$(Z_LIBS)							\
+	$(LZ4_LIBS)							\
+	$(PIXMAN_LIBS)							\
+	$(SSL_LIBS)							\
+	$(PULSE_LIBS)							\
+	$(GST_LIBS)							\
+	$(SASL_LIBS)							\
+	$(SMARTCARD_LIBS)						\
+	$(USBREDIR_LIBS)						\
+	$(GUDEV_LIBS)							\
+	$(PHODAV_LIBS)							\
+	$(NULL)
+
+if WITH_POLKIT
+USB_ACL_HELPER_SRCS =				\
+	usb-acl-helper.c			\
+	usb-acl-helper.h			\
+	$(NULL)
+AM_CPPFLAGS += -DACL_HELPER_PATH="\"$(ACL_HELPER_DIR)\""
+else
+USB_ACL_HELPER_SRCS =
+endif
+
+libspice_client_glib_2_0_la_SOURCES =			\
+	bio-gio.c					\
+	bio-gio.h					\
+	glib-compat.c					\
+	glib-compat.h					\
+	spice-audio.c					\
+	spice-audio-priv.h				\
+	spice-common.h					\
+	spice-util.c					\
+	spice-util-priv.h				\
+	spice-option.h					\
+	spice-option.c					\
+							\
+	spice-client.c					\
+	spice-session.c					\
+	spice-session-priv.h				\
+	spice-channel.c					\
+	spice-channel-cache.h				\
+	spice-channel-priv.h				\
+	coroutine.h					\
+	gio-coroutine.c					\
+	gio-coroutine.h					\
+							\
+	channel-base.c					\
+	channel-webdav.c				\
+	channel-cursor.c				\
+	channel-display.c				\
+	channel-display-priv.h				\
+	channel-display-mjpeg.c				\
+	channel-inputs.c				\
+	channel-main.c					\
+	channel-playback.c				\
+	channel-playback-priv.h				\
+	channel-port.c					\
+	channel-record.c				\
+	channel-smartcard.c				\
+	channel-usbredir.c				\
+	channel-usbredir-priv.h				\
+	smartcard-manager.c				\
+	smartcard-manager-priv.h			\
+	spice-uri.c					\
+	spice-uri-priv.h				\
+	usb-device-manager.c				\
+	usb-device-manager-priv.h			\
+	usbutil.c					\
+	usbutil.h					\
+	$(USB_ACL_HELPER_SRCS)				\
+	vmcstream.c					\
+	vmcstream.h					\
+	wocky-http-proxy.c				\
+	wocky-http-proxy.h				\
+							\
+	decode.h					\
+	decode-glz.c					\
+	decode-jpeg.c					\
+	decode-zlib.c					\
+							\
+	client_sw_canvas.c	\
+	client_sw_canvas.h	\
+	$(NULL)
+
+nodist_libspice_client_glib_2_0_la_SOURCES =	\
+	spice-glib-enums.c			\
+	spice-marshal.c				\
+	spice-marshal.h				\
+	$(NULL)
+
+libspice_client_glibincludedir = $(includedir)/spice-client-glib-2.0
+libspice_client_glibinclude_HEADERS =	\
+	spice-audio.h			\
+	spice-client.h			\
+	spice-uri.h			\
+	spice-types.h			\
+	spice-session.h			\
+	spice-channel.h			\
+	spice-util.h			\
+	spice-option.h			\
+	spice-version.h			\
+	channel-cursor.h		\
+	channel-display.h		\
+	channel-inputs.h		\
+	channel-main.h			\
+	channel-playback.h		\
+	channel-port.h			\
+	channel-record.h		\
+	channel-smartcard.h		\
+	channel-usbredir.h		\
+	channel-webdav.h		\
+	usb-device-manager.h		\
+	smartcard-manager.h		\
+	$(NULL)
+
+nodist_libspice_client_glibinclude_HEADERS =	\
+	spice-glib-enums.h			\
+	$(NULL)
+
+# file for API compatibility, but we don't want warning during our compilation
+dist_libspice_client_glibinclude_DATA =	\
+	spice-channel-enums.h		\
+	$(NULL)
+
+if WITH_PULSE
+libspice_client_glib_2_0_la_SOURCES +=	\
+	spice-pulse.c			\
+	spice-pulse.h			\
+	$(NULL)
+endif
+
+if WITH_GSTAUDIO
+libspice_client_glib_2_0_la_SOURCES +=	\
+	spice-gstaudio.c		\
+	spice-gstaudio.h		\
+	$(NULL)
+endif
+
+if WITH_PHODAV
+libspice_client_glib_2_0_la_SOURCES +=	\
+	giopipe.c			\
+	giopipe.h			\
+	$(NULL)
+endif
+
+if WITH_UCONTEXT
+libspice_client_glib_2_0_la_SOURCES += continuation.h continuation.c coroutine_ucontext.c
+endif
+
+if WITH_WINFIBER
+libspice_client_glib_2_0_la_SOURCES += coroutine_winfibers.c
+endif
+
+if WITH_GTHREAD
+libspice_client_glib_2_0_la_SOURCES += coroutine_gthread.c
+libspice_client_glib_2_0_la_LIBADD += $(GTHREAD_LIBS)
+endif
+
+
+WIN_USB_FILES= \
+	win-usb-dev.h			\
+	win-usb-dev.c			\
+	win-usb-clerk.h			\
+	win-usb-driver-install.h	\
+	win-usb-driver-install.c	\
+	$(NULL)
+
+if OS_WIN32
+if WITH_USBREDIR
+libspice_client_glib_2_0_la_SOURCES += \
+	$(WIN_USB_FILES)
+endif
+libspice_client_glib_2_0_la_LIBADD += -lws2_32 -lgdi32
+endif
+
+spicy_SOURCES =					\
+	spicy.c					\
+	spice-cmdline.h				\
+	spice-cmdline.c				\
+	$(NULL)
+
+spicy_LDADD =						\
+	libspice-client-gtk-$(SPICE_GTK_API_VERSION).la	\
+	libspice-client-glib-2.0.la			\
+	$(XRANDR_LIBS)					\
+	$(GTHREAD_LIBS)					\
+	$(GTK_LIBS)					\
+	$(LIBM)						\
+	$(NULL)
+
+spicy_CPPFLAGS =			\
+	$(AM_CPPFLAGS)			\
+	$(XRANDR_CFLAGS)		\
+	$(GTHREAD_CFLAGS)		\
+	-DSPICE_DISABLE_DEPRECATED	\
+	$(NULL)
+
+
+if WITH_POLKIT
+spice_client_glib_usb_acl_helper_SOURCES =	\
+	glib-compat.c				\
+	glib-compat.h				\
+	spice-client-glib-usb-acl-helper.c	\
+	$(NULL)
+
+spice_client_glib_usb_acl_helper_LDADD =	\
+	$(GLIB2_LIBS)				\
+	$(GIO_LIBS)				\
+	$(POLKIT_LIBS)				\
+	$(ACL_LIBS)				\
+	$(PIE_LDFLAGS)				\
+	$(NULL)
+
+spice_client_glib_usb_acl_helper_CPPFLAGS =	\
+	$(SPICE_CFLAGS)				\
+	$(GLIB2_CFLAGS)				\
+	$(GIO_CFLAGS)				\
+	$(POLKIT_CFLAGS)			\
+	$(PIE_CFLAGS)				\
+	$(NULL)
+
+install-data-hook:
+	-chown root $(DESTDIR)$(acldir)/spice-client-glib-usb-acl-helper
+	-chmod u+s  $(DESTDIR)$(acldir)/spice-client-glib-usb-acl-helper
+
+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
+if HAVE_GTK_2
+$(libspice_client_gtk_2_0_la_SOURCES): spice-glib-enums.h spice-widget-enums.h
+else
+$(libspice_client_gtk_3_0_la_SOURCES): spice-glib-enums.h spice-widget-enums.h
+endif
+endif
+
+spice-marshal.c: spice-marshal.h
+spice-glib-enums.c: spice-glib-enums.h
+spice-widget-enums.c: spice-widget-enums.h
+
+spice-marshal.c: spice-marshal.txt
+	$(AM_V_GEN)echo "#include \"config.h\"" > $@ && \
+		echo "#include \"spice-marshal.h\"" > $@ && \
+		glib-genmarshal --body $< >> $@ || (rm -f $@ && exit 1)
+
+spice-marshal.h: spice-marshal.txt
+	$(AM_V_GEN)glib-genmarshal --header $< > $@ || (rm -f $@ && exit 1)
+
+spice-glib-enums.c: spice-channel.h channel-inputs.h spice-session.h
+	$(AM_V_GEN)glib-mkenums --fhead "#include \"config.h\"\n\n" \
+			--fhead "#include <glib-object.h>\n" \
+			--fhead "#include \"spice-glib-enums.h\"\n\n" \
+			--fprod "\n#include \"spice-session.h\"\n" \
+			--fprod "\n#include \"spice-channel.h\"\n" \
+			--fprod "\n#include \"channel-inputs.h\"\n" \
+			--vhead "static const G at Type@Value _ at enum_name@_values[] = {" \
+			--vprod "  { @VALUENAME@, \"@VALUENAME@\", \"@valuenick@\" }," \
+			--vtail "  { 0, NULL, NULL }\n};\n\n" \
+			--vtail "GType\n at enum_name@_get_type (void)\n{\n" \
+			--vtail "  static GType type = 0;\n" \
+			--vtail "  static volatile gsize type_volatile = 0;\n\n" \
+			--vtail "  if (g_once_init_enter(&type_volatile)) {\n" \
+			--vtail "    type = g_ at type@_register_static (\"@EnumName@\", _ at enum_name@_values);\n" \
+			--vtail "    g_once_init_leave(&type_volatile, type);\n" \
+			--vtail "  }\n\n" \
+			--vtail "  return type;\n}\n\n" \
+		$^ > $@
+
+spice-glib-enums.h: spice-channel.h channel-inputs.h spice-session.h
+	$(AM_V_GEN)glib-mkenums --fhead "#ifndef SPICE_GLIB_ENUMS_H\n" \
+			--fhead "#define SPICE_GLIB_ENUMS_H\n\n" \
+			--fhead "G_BEGIN_DECLS\n\n" \
+			--ftail "G_END_DECLS\n\n" \
+			--ftail "#endif /* SPICE_CHANNEL_ENUMS_H */\n" \
+			--eprod "#define SPICE_TYPE_ at ENUMSHORT@ @enum_name at _get_type()\n" \
+			--eprod "GType @enum_name at _get_type (void);\n" \
+		$^ >  $@
+
+spice-widget-enums.c: spice-widget.h
+	$(AM_V_GEN)glib-mkenums --fhead "#include \"config.h\"\n\n" \
+			--fhead "#include <glib-object.h>\n" \
+			--fhead "#include \"spice-widget-enums.h\"\n\n" \
+			--fprod "\n#include \"spice-widget.h\"\n" \
+			--vhead "static const G at Type@Value _ at enum_name@_values[] = {" \
+			--vprod "  { @VALUENAME@, \"@VALUENAME@\", \"@valuenick@\" }," \
+			--vtail "  { 0, NULL, NULL }\n};\n\n" \
+			--vtail "GType\n at enum_name@_get_type (void)\n{\n" \
+			--vtail "  static GType type = 0;\n" \
+			--vtail "  static volatile gsize type_volatile = 0;\n\n" \
+			--vtail "  if (g_once_init_enter(&type_volatile)) {\n" \
+			--vtail "    type = g_ at type@_register_static (\"@EnumName@\", _ at enum_name@_values);\n" \
+			--vtail "    g_once_init_leave(&type_volatile, type);\n" \
+			--vtail "  }\n\n" \
+			--vtail "  return type;\n}\n\n" \
+		$< > $@
+
+spice-widget-enums.h: spice-widget.h
+	$(AM_V_GEN)glib-mkenums --fhead "#ifndef SPICE_WIDGET_ENUMS_H\n" \
+			--fhead "#define SPICE_WIDGET_ENUMS_H\n\n" \
+			--fhead "G_BEGIN_DECLS\n\n" \
+			--ftail "G_END_DECLS\n\n" \
+			--ftail "#endif /* SPICE_WIDGET_ENUMS_H */\n" \
+			--eprod "#define SPICE_TYPE_ at ENUMSHORT@ @enum_name at _get_type()\n" \
+			--eprod "GType @enum_name at _get_type (void);\n" \
+		$< >  $@
+
+
+vncdisplaykeymap.c: $(KEYMAPS)
+
+$(KEYMAPS): $(KEYMAP_GEN) keymaps.csv
+
+# Note despite being autogenerated these are not part of CLEANFILES, they
+# are actually a part of EXTRA_DIST to avoid the need for perl(Text::CSV) by
+# end users
+vncdisplaykeymap_xorgevdev2xtkbd.c:
+	$(AM_V_GEN)$(KEYMAP_GEN) $(srcdir)/keymaps.csv xorgevdev xtkbd > $@ || rm $@
+
+vncdisplaykeymap_xorgkbd2xtkbd.c:
+	$(AM_V_GEN)$(KEYMAP_GEN) $(srcdir)/keymaps.csv xorgkbd xtkbd > $@ || rm $@
+
+vncdisplaykeymap_xorgxquartz2xtkbd.c:
+	$(AM_V_GEN)$(KEYMAP_GEN) $(srcdir)/keymaps.csv xorgxquartz xtkbd > $@ || rm $@
+
+vncdisplaykeymap_xorgxwin2xtkbd.c:
+	$(AM_V_GEN)$(KEYMAP_GEN) $(srcdir)/keymaps.csv xorgxwin xtkbd > $@ || rm $@
+
+vncdisplaykeymap_osx2xtkbd.c:
+	$(AM_V_GEN)$(KEYMAP_GEN) $(srcdir)/keymaps.csv osx xtkbd > $@ || rm $@
+
+vncdisplaykeymap_win322xtkbd.c:
+	$(AM_V_GEN)$(KEYMAP_GEN) $(srcdir)/keymaps.csv win32 xtkbd > $@ || rm $@
+
+vncdisplaykeymap_x112xtkbd.c:
+	$(AM_V_GEN)$(KEYMAP_GEN) $(srcdir)/keymaps.csv x11 xtkbd > $@ || rm $@
+
+if WITH_PYTHON
+pyexec_LTLIBRARIES = SpiceClientGtk.la
+
+# workaround for broken parallel install support in automake with LTLIBRARIES
+# http://debbugs.gnu.org/cgi/bugreport.cgi?bug=7328
+install_pyexecLTLIBRARIES = install-pyexecLTLIBRARIES
+$(install_pyexecLTLIBRARIES): install-libLTLIBRARIES
+
+SpiceClientGtk_la_LIBADD = libspice-client-gtk-2.0.la libspice-client-glib-2.0.la $(PYGTK_LIBS)
+SpiceClientGtk_la_CFLAGS = $(GTK_CFLAGS) $(PYTHON_INCLUDES) $(PYGTK_CFLAGS) $(WARN_PYFLAGS)
+SpiceClientGtk_la_LDFLAGS = -module -avoid-version -fPIC
+SpiceClientGtk_la_SOURCES = spice-client-gtk-module.c
+nodist_SpiceClientGtk_la_SOURCES = spice-client-gtk-module.defs.c
+
+CODEGENDIR = `pkg-config --variable=codegendir pygtk-2.0`
+DEFSDIR = `pkg-config --variable=defsdir pygtk-2.0`
+
+spice-client-gtk.defs: $(libspice_client_gtkinclude_HEADERS) $(nodist_libspice_client_gtkinclude_HEADERS) $(libspice_client_glibinclude_HEADERS) $(nodist_libspice_client_glibinclude_HEADERS)
+	$(AM_V_GEN)$(PYTHON) $(CODEGENDIR)/h2def.py \
+		-f $(srcdir)/spice-client-gtk-manual.defs \
+		$^ > $@
+
+spice-client-gtk-module.defs.c: spice-client-gtk.override spice-client-gtk.defs spice-client-gtk-manual.defs
+	@cat spice-client-gtk.defs $(srcdir)/spice-client-gtk-manual.defs > tmp.defs
+	$(AM_V_GEN)pygobject-codegen-2.0 --prefix spice \
+			  --register $(DEFSDIR)/gdk-types.defs \
+			  --register $(DEFSDIR)/gtk-types.defs \
+			  --override $(srcdir)/spice-client-gtk.override \
+			  tmp.defs > $@
+	@rm tmp.defs
+
+CLEANFILES += spice-client-gtk-module.defs.c spice-client-gtk.defs
+endif
+
+-include $(INTROSPECTION_MAKEFILE)
+
+if G_IR_SCANNER_SYMBOL_PREFIX
+PREFIX_ARGS = --symbol-prefix=spice --identifier-prefix=Spice
+else
+PREFIX_ARGS = --strip-prefix=Spice
+endif
+
+INTROSPECTION_GIRS =
+INTROSPECTION_SCANNER_ARGS = --warn-all --accept-unprefixed --add-include-path=$(builddir) $(PREFIX_ARGS)
+INTROSPECTION_COMPILER_ARGS = --includedir=$(builddir)
+
+if HAVE_INTROSPECTION
+glib_introspection_files =				\
+	$(libspice_client_glibinclude_HEADERS)		\
+	$(nodist_libspice_client_glibinclude_HEADERS)	\
+	spice-audio.c					\
+	spice-client.c					\
+	spice-session.c					\
+	spice-channel.c					\
+	spice-glib-enums.c				\
+	spice-option.c					\
+	spice-util.c					\
+	channel-webdav.c				\
+	channel-cursor.c				\
+	channel-display.c				\
+	channel-inputs.c				\
+	channel-main.c					\
+	channel-playback.c				\
+	channel-port.c					\
+	channel-record.c				\
+	channel-smartcard.c				\
+	channel-usbredir.c				\
+	smartcard-manager.c				\
+	usb-device-manager.c				\
+	$(NULL)
+
+gtk_introspection_files =				\
+	$(libspice_client_gtkinclude_HEADERS)		\
+	$(nodist_libspice_client_gtkinclude_HEADERS)	\
+	spice-gtk-session.c				\
+	spice-widget.c					\
+	spice-grabsequence.c				\
+	usb-device-widget.c				\
+	$(NULL)
+
+SpiceClientGLib-2.0.gir: libspice-client-glib-2.0.la
+SpiceClientGLib_2_0_gir_INCLUDES = GObject-2.0 Gio-2.0
+SpiceClientGLib_2_0_gir_CFLAGS = $(SPICE_COMMON_CPPFLAGS)
+SpiceClientGLib_2_0_gir_LIBS = libspice-client-glib-2.0.la
+SpiceClientGLib_2_0_gir_FILES = $(glib_introspection_files)
+SpiceClientGLib_2_0_gir_EXPORT_PACKAGES = spice-client-glib-2.0
+SpiceClientGLib_2_0_gir_SCANNERFLAGS = --c-include="spice-client.h"
+INTROSPECTION_GIRS += SpiceClientGLib-2.0.gir
+
+if WITH_GTK
+if HAVE_GTK_2
+SpiceClientGtk-2.0.gir: libspice-client-gtk-2.0.la SpiceClientGLib-2.0.gir
+SpiceClientGtk_2_0_gir_INCLUDES = GObject-2.0 Gtk-2.0 SpiceClientGLib-2.0
+SpiceClientGtk_2_0_gir_CFLAGS = $(SPICE_COMMON_CPPFLAGS)
+SpiceClientGtk_2_0_gir_LIBS = libspice-client-gtk-2.0.la libspice-client-glib-2.0.la
+SpiceClientGtk_2_0_gir_FILES = $(gtk_introspection_files)
+SpiceClientGtk_2_0_gir_EXPORT_PACKAGES = spice-client-gtk-2.0
+SpiceClientGtk_2_0_gir_SCANNERFLAGS = --c-include="spice-widget.h"
+else
+SpiceClientGtk-3.0.gir: libspice-client-gtk-3.0.la SpiceClientGLib-2.0.gir
+SpiceClientGtk_3_0_gir_INCLUDES = GObject-2.0 Gtk-3.0 SpiceClientGLib-2.0
+SpiceClientGtk_3_0_gir_CFLAGS = $(SPICE_COMMON_CPPFLAGS)
+SpiceClientGtk_3_0_gir_LIBS = libspice-client-gtk-3.0.la libspice-client-glib-2.0.la
+SpiceClientGtk_3_0_gir_FILES = $(gtk_introspection_files)
+SpiceClientGtk_3_0_gir_EXPORT_PACKAGES = spice-client-gtk-3.0
+SpiceClientGtk_3_0_gir_SCANNERFLAGS = --c-include="spice-widget.h"
+endif
+INTROSPECTION_GIRS += SpiceClientGtk-$(SPICE_GTK_API_VERSION).gir
+endif
+
+girdir = $(datadir)/gir-1.0
+gir_DATA = $(INTROSPECTION_GIRS)
+
+typelibsdir = $(libdir)/girepository-1.0
+typelibs_DATA = $(INTROSPECTION_GIRS:.gir=.typelib)
+
+CLEANFILES += $(gir_DATA) $(typelibs_DATA)
+endif
+
+update-map-file: $(libspice_client_gtkinclude_HEADERS) $(nodist_libspice_client_gtkinclude_HEADERS) $(libspice_client_glibinclude_HEADERS) $(nodist_libspice_client_glibinclude_HEADERS)
+	( echo "SPICEGTK_1 {" ; \
+	  echo "global:" ; \
+	  ctags -f - -I G_GNUC_CONST --c-kinds=p $^ | awk '/^spice_/ { print $$1 ";" }' | sort ; \
+	  echo "local:" ;  \
+	  echo "*;" ; \
+	  echo "};" ) > $(srcdir)/map-file
+
+update-glib-sym-file: $(libspice_client_glibinclude_HEADERS) $(nodist_libspice_client_glibinclude_HEADERS)
+	( ctags -f - -I G_GNUC_CONST --c-kinds=p $^ | awk '/^spice_/ { print $$1 }' | sort ; \
+	) > $(srcdir)/spice-glib-sym-file
+
+update-gtk-sym-file: $(libspice_client_gtkinclude_HEADERS) $(nodist_libspice_client_gtkinclude_HEADERS)
+	( ctags -f - -I G_GNUC_CONST --c-kinds=p $^ | awk '/^spice_/ { print $$1 }' | sort ; \
+	) > $(srcdir)/spice-gtk-sym-file
+
+update-symbol-files: update-map-file update-glib-sym-file update-gtk-sym-file
+
+-include $(top_srcdir)/git.mk
diff --git a/src/bio-gio.c b/src/bio-gio.c
new file mode 100644
index 0000000..108ac1a
--- /dev/null
+++ b/src/bio-gio.c
@@ -0,0 +1,114 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2012 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 <string.h>
+#include <glib.h>
+
+#include "spice-util.h"
+#include "bio-gio.h"
+
+typedef struct bio_gsocket_method {
+    BIO_METHOD method;
+    GIOStream *stream;
+} bio_gsocket_method;
+
+#define BIO_GET_GSOCKET(bio)  (((bio_gsocket_method*)bio->method)->gsocket)
+#define BIO_GET_ISTREAM(bio)  (g_io_stream_get_input_stream(((bio_gsocket_method*)bio->method)->stream))
+#define BIO_GET_OSTREAM(bio)  (g_io_stream_get_output_stream(((bio_gsocket_method*)bio->method)->stream))
+
+static int bio_gio_write(BIO *bio, const char *in, int inl)
+{
+    gssize ret;
+    GError *error = NULL;
+
+    ret = g_pollable_output_stream_write_nonblocking(G_POLLABLE_OUTPUT_STREAM(BIO_GET_OSTREAM(bio)),
+                                                     in, inl, NULL, &error);
+    BIO_clear_retry_flags(bio);
+
+    if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
+        BIO_set_retry_write(bio);
+    if (error != NULL) {
+        g_warning("%s", error->message);
+        g_clear_error(&error);
+    }
+
+    return ret;
+}
+
+static int bio_gio_read(BIO *bio, char *out, int outl)
+{
+    gssize ret;
+    GError *error = NULL;
+
+    ret = g_pollable_input_stream_read_nonblocking(G_POLLABLE_INPUT_STREAM(BIO_GET_ISTREAM(bio)),
+                                                   out, outl, NULL, &error);
+    BIO_clear_retry_flags(bio);
+
+    if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
+        BIO_set_retry_read(bio);
+    else if (error != NULL)
+        g_warning("%s", error->message);
+
+    g_clear_error(&error);
+
+    return ret;
+}
+
+static int bio_gio_destroy(BIO *bio)
+{
+    if (bio == NULL || bio->method == NULL)
+        return 0;
+
+    SPICE_DEBUG("bio gsocket destroy");
+    g_free(bio->method);
+    bio->method = NULL;;
+
+    return 1;
+}
+
+static int bio_gio_puts(BIO *bio, const char *str)
+{
+    int n, ret;
+
+    n = strlen(str);
+    ret = bio_gio_write(bio, str, n);
+
+    return ret;
+}
+
+G_GNUC_INTERNAL
+BIO* bio_new_giostream(GIOStream *stream)
+{
+    // TODO: make an actual new BIO type, or just switch to GTls already...
+    BIO *bio = BIO_new_socket(-1, BIO_NOCLOSE);
+
+    bio_gsocket_method *bio_method = g_new(bio_gsocket_method, 1);
+    bio_method->method = *bio->method;
+    bio_method->stream = stream;
+
+    bio->method->destroy(bio);
+    bio->method = (BIO_METHOD*)bio_method;
+
+    bio->method->bwrite = bio_gio_write;
+    bio->method->bread = bio_gio_read;
+    bio->method->bputs = bio_gio_puts;
+    bio->method->destroy = bio_gio_destroy;
+
+    return bio;
+}
diff --git a/src/bio-gio.h b/src/bio-gio.h
new file mode 100644
index 0000000..31fd369
--- /dev/null
+++ b/src/bio-gio.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2012 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 BIO_GIO_H_
+# define BIO_GIO_H_
+
+#include <openssl/bio.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+BIO* bio_new_giostream(GIOStream *stream);
+
+G_END_DECLS
+
+#endif /* !BIO_GIO_H_ */
diff --git a/src/channel-base.c b/src/channel-base.c
new file mode 100644
index 0000000..77d339c
--- /dev/null
+++ b/src/channel-base.c
@@ -0,0 +1,284 @@
+/* -*- 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-session-priv.h"
+#include "spice-channel-priv.h"
+
+/* coroutine context */
+G_GNUC_INTERNAL
+void spice_channel_handle_set_ack(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceChannelPrivate *c = channel->priv;
+    SpiceMsgSetAck* ack = spice_msg_in_parsed(in);
+    SpiceMsgOut *out = spice_msg_out_new(channel, SPICE_MSGC_ACK_SYNC);
+    SpiceMsgcAckSync sync = {
+        .generation = ack->generation,
+    };
+
+    c->message_ack_window = c->message_ack_count = ack->window;
+    c->marshallers->msgc_ack_sync(out->marshaller, &sync);
+    spice_msg_out_send_internal(out);
+}
+
+/* coroutine context */
+G_GNUC_INTERNAL
+void spice_channel_handle_ping(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceChannelPrivate *c = channel->priv;
+    SpiceMsgPing *ping = spice_msg_in_parsed(in);
+    SpiceMsgOut *pong = spice_msg_out_new(channel, SPICE_MSGC_PONG);
+
+    c->marshallers->msgc_pong(pong->marshaller, ping);
+    spice_msg_out_send_internal(pong);
+}
+
+/* coroutine context */
+G_GNUC_INTERNAL
+void spice_channel_handle_notify(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    static const char* severity_strings[] = {"info", "warn", "error"};
+    static const char* visibility_strings[] = {"!", "!!", "!!!"};
+
+    SpiceMsgNotify *notify = spice_msg_in_parsed(in);
+    const char *severity   = "?";
+    const char *visibility = "?";
+    const char *message_str = NULL;
+
+    if (notify->severity <= SPICE_NOTIFY_SEVERITY_ERROR) {
+        severity = severity_strings[notify->severity];
+    }
+    if (notify->visibilty <= SPICE_NOTIFY_VISIBILITY_HIGH) {
+        visibility = visibility_strings[notify->visibilty];
+    }
+
+    if (notify->message_len &&
+        notify->message_len <= in->dpos - sizeof(*notify)) {
+        message_str = (char*)notify->message;
+    }
+
+    CHANNEL_DEBUG(channel, "%s -- %s%s #%u%s%.*s", __FUNCTION__,
+            severity, visibility, notify->what,
+            message_str ? ": " : "", notify->message_len,
+            message_str ? message_str : "");
+}
+
+/* coroutine context */
+G_GNUC_INTERNAL
+void spice_channel_handle_disconnect(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceMsgDisconnect *disconnect = spice_msg_in_parsed(in);
+
+    CHANNEL_DEBUG(channel, "%s: ts: %" PRIu64", reason: %u", __FUNCTION__,
+                  disconnect->time_stamp, disconnect->reason);
+}
+
+typedef struct WaitForChannelData
+{
+    SpiceWaitForChannel *wait;
+    SpiceChannel *channel;
+} WaitForChannelData;
+
+/* coroutine and main context */
+static gboolean wait_for_channel(gpointer data)
+{
+    WaitForChannelData *wfc = data;
+    SpiceChannelPrivate *c = wfc->channel->priv;
+    SpiceChannel *wait_channel;
+
+    wait_channel = spice_session_lookup_channel(c->session, wfc->wait->channel_id, wfc->wait->channel_type);
+    g_return_val_if_fail(wait_channel != NULL, TRUE);
+
+    if (wait_channel->priv->last_message_serial >= wfc->wait->message_serial)
+        return TRUE;
+
+    return FALSE;
+}
+
+/* coroutine context */
+G_GNUC_INTERNAL
+void spice_channel_handle_wait_for_channels(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceChannelPrivate *c = channel->priv;
+    SpiceMsgWaitForChannels *wfc = spice_msg_in_parsed(in);
+    int i;
+
+    for (i = 0; i < wfc->wait_count; ++i) {
+        WaitForChannelData data = {
+            .wait = wfc->wait_list + i,
+            .channel = channel
+        };
+
+        CHANNEL_DEBUG(channel, "waiting for serial %" PRIu64 " (%d/%d)", data.wait->message_serial, i + 1, wfc->wait_count);
+        if (g_coroutine_condition_wait(&c->coroutine, wait_for_channel, &data))
+            CHANNEL_DEBUG(channel, "waiting for serial %"  PRIu64 ", done", data.wait->message_serial);
+        else
+            CHANNEL_DEBUG(channel, "waiting for serial %" PRIu64 ", cancelled", data.wait->message_serial);
+    }
+}
+
+static void
+get_msg_handler(SpiceChannel *channel, SpiceMsgIn *in, gpointer data)
+{
+    SpiceMsgIn **msg = data;
+
+    g_return_if_fail(msg != NULL);
+    g_return_if_fail(*msg == NULL);
+
+    spice_msg_in_ref(in);
+    *msg = in;
+}
+
+/* coroutine context */
+G_GNUC_INTERNAL
+void spice_channel_handle_migrate(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceMsgOut *out;
+    SpiceMsgIn *data = NULL;
+    SpiceMsgMigrate *mig = spice_msg_in_parsed(in);
+    SpiceChannelPrivate *c = channel->priv;
+
+    CHANNEL_DEBUG(channel, "%s: flags %u", __FUNCTION__, mig->flags);
+    if (mig->flags & SPICE_MIGRATE_NEED_FLUSH) {
+        /* if peer version > 1: pushing the mark msg before all other messgages and sending it,
+         * and only it */
+        if (c->peer_hdr.major_version == 1) {
+            /* iterate_write is blocking and flushing all pending write */
+            SPICE_CHANNEL_GET_CLASS(channel)->iterate_write(channel);
+        }
+        out = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_MIGRATE_FLUSH_MARK);
+        spice_msg_out_send_internal(out);
+    }
+    if (mig->flags & SPICE_MIGRATE_NEED_DATA_TRANSFER) {
+        spice_channel_recv_msg(channel, get_msg_handler, &data);
+        if (!data) {
+            g_critical("expected SPICE_MSG_MIGRATE_DATA, got empty message");
+            goto end;
+        } else if (spice_header_get_msg_type(data->header, c->use_mini_header) !=
+                   SPICE_MSG_MIGRATE_DATA) {
+            g_critical("expected SPICE_MSG_MIGRATE_DATA, got %d",
+                      spice_header_get_msg_type(data->header, c->use_mini_header));
+            goto end;
+        }
+    }
+
+    /* swapping channels sockets */
+    spice_session_channel_migrate(c->session, channel);
+
+    /* pushing the MIGRATE_DATA before all other pending messages */
+    if ((mig->flags & SPICE_MIGRATE_NEED_DATA_TRANSFER) && (data != NULL)) {
+        out = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_MIGRATE_DATA);
+        spice_marshaller_add(out->marshaller, data->data,
+                             spice_header_get_msg_size(data->header, c->use_mini_header));
+        spice_msg_out_send_internal(out);
+    }
+
+end:
+    if (data)
+        spice_msg_in_unref(data);
+}
+
+
+static void set_handlers(SpiceChannelClass *klass,
+                         const spice_msg_handler* handlers, const int n)
+{
+    int i;
+
+    g_array_set_size(klass->handlers, MAX(klass->handlers->len, n));
+    for (i = 0; i < n; i++) {
+        if (handlers[i])
+            g_array_index(klass->handlers, spice_msg_handler, i) = handlers[i];
+    }
+}
+
+static void spice_channel_add_base_handlers(SpiceChannelClass *klass)
+{
+    static const spice_msg_handler handlers[] = {
+        [ SPICE_MSG_SET_ACK ]                  = spice_channel_handle_set_ack,
+        [ SPICE_MSG_PING ]                     = spice_channel_handle_ping,
+        [ SPICE_MSG_NOTIFY ]                   = spice_channel_handle_notify,
+        [ SPICE_MSG_DISCONNECTING ]            = spice_channel_handle_disconnect,
+        [ SPICE_MSG_WAIT_FOR_CHANNELS ]        = spice_channel_handle_wait_for_channels,
+        [ SPICE_MSG_MIGRATE ]                  = spice_channel_handle_migrate,
+    };
+
+    set_handlers(klass, handlers, G_N_ELEMENTS(handlers));
+}
+
+G_GNUC_INTERNAL
+void spice_channel_set_handlers(SpiceChannelClass *klass,
+                                const spice_msg_handler* handlers, const int n)
+{
+    /* FIXME: use class private (requires glib 2.24) */
+    g_return_if_fail(klass->handlers == NULL);
+    klass->handlers = g_array_sized_new(FALSE, TRUE, sizeof(spice_msg_handler), n);
+
+    spice_channel_add_base_handlers(klass);
+    set_handlers(klass, handlers, n);
+}
+
+static void
+vmc_write_free_cb(uint8_t *data, void *user_data)
+{
+    GSimpleAsyncResult *result = user_data;
+
+    g_simple_async_result_complete_in_idle(result);
+    g_object_unref(result);
+}
+
+G_GNUC_INTERNAL
+void spice_vmc_write_async(SpiceChannel *self,
+                           const void *buffer, gsize count,
+                           GCancellable *cancellable,
+                           GAsyncReadyCallback callback,
+                           gpointer user_data)
+{
+    SpiceMsgOut *msg;
+    GSimpleAsyncResult *simple;
+
+    simple = g_simple_async_result_new(G_OBJECT(self), callback, user_data,
+                                       spice_port_write_async);
+    g_simple_async_result_set_op_res_gssize(simple, count);
+
+    msg = spice_msg_out_new(SPICE_CHANNEL(self), SPICE_MSGC_SPICEVMC_DATA);
+    spice_marshaller_add_ref_full(msg->marshaller, (uint8_t*)buffer, count,
+                                  vmc_write_free_cb, simple);
+    spice_msg_out_send(msg);
+}
+
+G_GNUC_INTERNAL
+gssize spice_vmc_write_finish(SpiceChannel *self,
+                              GAsyncResult *result, GError **error)
+{
+    GSimpleAsyncResult *simple;
+
+    g_return_val_if_fail(result != NULL, -1);
+
+    simple = (GSimpleAsyncResult *)result;
+
+    if (g_simple_async_result_propagate_error(simple, error))
+        return -1;
+
+    g_return_val_if_fail(g_simple_async_result_is_valid(result, G_OBJECT(self),
+                                                        spice_port_write_async), -1);
+
+    return g_simple_async_result_get_op_res_gssize(simple);
+}
diff --git a/src/channel-cursor.c b/src/channel-cursor.c
new file mode 100644
index 0000000..e6514a2
--- /dev/null
+++ b/src/channel-cursor.c
@@ -0,0 +1,529 @@
+/* -*- 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-compat.h"
+#include "spice-client.h"
+#include "spice-common.h"
+
+#include "spice-channel-priv.h"
+#include "spice-channel-cache.h"
+#include "spice-marshal.h"
+
+/**
+ * SECTION:channel-cursor
+ * @short_description: update cursor shape and position
+ * @title: Cursor Channel
+ * @section_id:
+ * @see_also: #SpiceChannel, and the GTK widget #SpiceDisplay
+ * @stability: Stable
+ * @include: channel-cursor.h
+ *
+ * The Spice protocol defines a set of messages for controlling cursor
+ * shape and position on the remote display area. The cursor changes
+ * that should be reflected on the display are notified by
+ * signals. See for example #SpiceCursorChannel::cursor-set
+ * #SpiceCursorChannel::cursor-move signals.
+ */
+
+#define SPICE_CURSOR_CHANNEL_GET_PRIVATE(obj)                                  \
+    (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_CURSOR_CHANNEL, SpiceCursorChannelPrivate))
+
+typedef struct display_cursor display_cursor;
+
+struct display_cursor {
+    SpiceCursorHeader           hdr;
+    gboolean                    default_cursor;
+    int                         refcount;
+    guint32                     data[];
+};
+
+struct _SpiceCursorChannelPrivate {
+    display_cache               *cursors;
+    gboolean                    init_done;
+};
+
+enum {
+    SPICE_CURSOR_SET,
+    SPICE_CURSOR_MOVE,
+    SPICE_CURSOR_HIDE,
+    SPICE_CURSOR_RESET,
+
+    SPICE_CURSOR_LAST_SIGNAL,
+};
+
+static guint signals[SPICE_CURSOR_LAST_SIGNAL];
+
+static display_cursor * display_cursor_ref(display_cursor *cursor);
+static void display_cursor_unref(display_cursor *cursor);
+static void channel_set_handlers(SpiceChannelClass *klass);
+
+G_DEFINE_TYPE(SpiceCursorChannel, spice_cursor_channel, SPICE_TYPE_CHANNEL)
+
+/* ------------------------------------------------------------------ */
+
+static void spice_cursor_channel_init(SpiceCursorChannel *channel)
+{
+    SpiceCursorChannelPrivate *c;
+
+    c = channel->priv = SPICE_CURSOR_CHANNEL_GET_PRIVATE(channel);
+
+    c->cursors = cache_new((GDestroyNotify)display_cursor_unref);
+}
+
+static void spice_cursor_channel_finalize(GObject *obj)
+{
+    SpiceCursorChannel *channel = SPICE_CURSOR_CHANNEL(obj);
+    SpiceCursorChannelPrivate *c = channel->priv;
+
+    g_clear_pointer(&c->cursors, cache_unref);
+
+    if (G_OBJECT_CLASS(spice_cursor_channel_parent_class)->finalize)
+        G_OBJECT_CLASS(spice_cursor_channel_parent_class)->finalize(obj);
+}
+
+/* coroutine context */
+static void spice_cursor_channel_reset(SpiceChannel *channel, gboolean migrating)
+{
+    SpiceCursorChannelPrivate *c = SPICE_CURSOR_CHANNEL(channel)->priv;
+
+    cache_clear(c->cursors);
+    c->init_done = FALSE;
+
+    SPICE_CHANNEL_CLASS(spice_cursor_channel_parent_class)->channel_reset(channel, migrating);
+}
+
+static void spice_cursor_channel_class_init(SpiceCursorChannelClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+    SpiceChannelClass *channel_class = SPICE_CHANNEL_CLASS(klass);
+
+    gobject_class->finalize     = spice_cursor_channel_finalize;
+    channel_class->channel_reset = spice_cursor_channel_reset;
+
+    /**
+     * SpiceCursorChannel::cursor-set:
+     * @cursor: the #SpiceCursorChannel that emitted the signal
+     * @width: width of the shape
+     * @height: height of the shape
+     * @hot_x: horizontal offset of the 'hotspot' of the cursor
+     * @hot_y: vertical offset of the 'hotspot' of the cursor
+     * @rgba: 32bits shape data, or %NULL if default cursor. It might
+     * be freed after the signal is emitted, so make sure to copy it
+     * if you need it later!
+     *
+     * The #SpiceCursorChannel::cursor-set signal is emitted to modify
+     * cursor aspect and position on the display area.
+     **/
+    signals[SPICE_CURSOR_SET] =
+        g_signal_new("cursor-set",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_FIRST,
+                     G_STRUCT_OFFSET(SpiceCursorChannelClass, cursor_set),
+                     NULL, NULL,
+                     g_cclosure_user_marshal_VOID__INT_INT_INT_INT_POINTER,
+                     G_TYPE_NONE,
+                     5,
+                     G_TYPE_INT, G_TYPE_INT,
+                     G_TYPE_INT, G_TYPE_INT,
+                     G_TYPE_POINTER);
+
+    /**
+     * SpiceCursorChannel::cursor-move:
+     * @cursor: the #SpiceCursorChannel that emitted the signal
+     * @x: x position
+     * @y: y position
+     *
+     * The #SpiceCursorChannel::cursor-move signal is emitted to update
+     * the cursor position on the display area.
+     **/
+    signals[SPICE_CURSOR_MOVE] =
+        g_signal_new("cursor-move",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_FIRST,
+                     G_STRUCT_OFFSET(SpiceCursorChannelClass, cursor_move),
+                     NULL, NULL,
+                     g_cclosure_user_marshal_VOID__INT_INT,
+                     G_TYPE_NONE,
+                     2,
+                     G_TYPE_INT, G_TYPE_INT);
+
+    /**
+     * SpiceCursorChannel::cursor-hide:
+     * @cursor: the #SpiceCursorChannel that emitted the signal
+     *
+     * The #SpiceCursorChannel::cursor-hide signal is emitted to hide
+     * the cursor/pointer on the display area.
+     **/
+    signals[SPICE_CURSOR_HIDE] =
+        g_signal_new("cursor-hide",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_FIRST,
+                     G_STRUCT_OFFSET(SpiceCursorChannelClass, cursor_hide),
+                     NULL, NULL,
+                     g_cclosure_marshal_VOID__VOID,
+                     G_TYPE_NONE,
+                     0);
+
+    /**
+     * SpiceCursorChannel::cursor-reset:
+     * @cursor: the #SpiceCursorChannel that emitted the signal
+     *
+     * The #SpiceCursorChannel::cursor-reset signal is emitted to
+     * reset the cursor to its default context.
+     **/
+    signals[SPICE_CURSOR_RESET] =
+        g_signal_new("cursor-reset",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_FIRST,
+                     G_STRUCT_OFFSET(SpiceCursorChannelClass, cursor_reset),
+                     NULL, NULL,
+                     g_cclosure_marshal_VOID__VOID,
+                     G_TYPE_NONE,
+                     0);
+
+    g_type_class_add_private(klass, sizeof(SpiceCursorChannelPrivate));
+    channel_set_handlers(SPICE_CHANNEL_CLASS(klass));
+}
+
+/* ------------------------------------------------------------------ */
+
+#ifdef DEBUG_CURSOR
+static void print_cursor(display_cursor *cursor, const guint8 *data)
+{
+    int x, y, bpl;
+    const guint8 *xor, *and;
+
+    bpl = (cursor->hdr.width + 7) / 8;
+    and = data;
+    xor = and + bpl * cursor->hdr.height;
+
+    printf("data (%d x %d):\n", cursor->hdr.width, cursor->hdr.height);
+    for (y = 0 ; y < cursor->hdr.height; ++y) {
+        for (x = 0 ; x < cursor->hdr.width / 8; x++) {
+            printf("%02X", and[x]);
+        }
+        and += bpl;
+        printf("\n");
+    }
+    printf("xor:\n");
+    for (y = 0 ; y < cursor->hdr.height; ++y) {
+        for (x = 0 ; x < cursor->hdr.width / 8; ++x) {
+            printf("%02X", xor[x]);
+        }
+        xor += bpl;
+        printf("\n");
+    }
+}
+#endif
+
+static void mono_cursor(display_cursor *cursor, const guint8 *data)
+{
+    int bpl = (cursor->hdr.width + 7) / 8;
+    const guint8 *xor, *and;
+    guint8 *dest;
+    dest = (uint8_t *)cursor->data;
+
+#ifdef DEBUG_CURSOR
+    print_cursor(cursor, data);
+#endif
+    and = data;
+    xor = and + bpl * cursor->hdr.height;
+    spice_mono_edge_highlight(cursor->hdr.width, cursor->hdr.height,
+                              and, xor, dest);
+}
+
+static guint8 get_pix_mask(const guint8 *data, gint offset, gint pix_index)
+{
+    return data[offset + (pix_index >> 3)] & (0x80 >> (pix_index % 8));
+}
+
+static guint32 get_pix_hack(gint pix_index, gint width)
+{
+    return (((pix_index % width) ^ (pix_index / width)) & 1) ? 0xc0303030 : 0x30505050;
+}
+
+static display_cursor * display_cursor_ref(display_cursor *cursor)
+{
+    g_return_val_if_fail(cursor != NULL, NULL);
+    g_return_val_if_fail(cursor->refcount > 0, NULL);
+
+    cursor->refcount++;
+    return cursor;
+}
+
+static void display_cursor_unref(display_cursor *cursor)
+{
+    g_return_if_fail(cursor != NULL);
+    g_return_if_fail(cursor->refcount > 0);
+
+    cursor->refcount--;
+    if (cursor->refcount == 0)
+        g_free(cursor);
+}
+
+static const char *cursor_type_to_string(int type)
+{
+    switch (type) {
+    case SPICE_CURSOR_TYPE_MONO:
+        return "mono";
+    case SPICE_CURSOR_TYPE_ALPHA:
+        return "alpha";
+    case SPICE_CURSOR_TYPE_COLOR32:
+        return "color32";
+    case SPICE_CURSOR_TYPE_COLOR16:
+        return "color16";
+    case SPICE_CURSOR_TYPE_COLOR4:
+        return "color4";
+    }
+    return "unknown";
+}
+
+static display_cursor *set_cursor(SpiceChannel *channel, SpiceCursor *scursor)
+{
+    SpiceCursorChannelPrivate *c = SPICE_CURSOR_CHANNEL(channel)->priv;
+    SpiceCursorHeader *hdr = &scursor->header;
+    display_cursor *cursor;
+    size_t size;
+    gint i, pix_mask, pix;
+    const guint8* data;
+    guint8 *rgba;
+    guint8 val;
+
+    CHANNEL_DEBUG(channel, "%s: flags %d, size %d", __FUNCTION__,
+                  scursor->flags, scursor->data_size);
+
+    if (scursor->flags & SPICE_CURSOR_FLAGS_NONE)
+        return NULL;
+
+    CHANNEL_DEBUG(channel, "%s: type %s(%d), %" PRIx64 ", %dx%d", __FUNCTION__,
+                  cursor_type_to_string(hdr->type), hdr->type, hdr->unique,
+                  hdr->width, hdr->height);
+
+    if (scursor->flags & SPICE_CURSOR_FLAGS_FROM_CACHE) {
+        cursor = cache_find(c->cursors, hdr->unique);
+        g_return_val_if_fail(cursor != NULL, NULL);
+        return display_cursor_ref(cursor);
+    }
+
+    g_return_val_if_fail(scursor->data_size != 0, NULL);
+
+    size = 4u * hdr->width * hdr->height;
+    cursor = g_malloc0(sizeof(*cursor) + size);
+    cursor->hdr = *hdr;
+    cursor->default_cursor = FALSE;
+    cursor->refcount = 1;
+    data = scursor->data;
+
+    switch (hdr->type) {
+    case SPICE_CURSOR_TYPE_MONO:
+        mono_cursor(cursor, data);
+        break;
+    case SPICE_CURSOR_TYPE_ALPHA:
+        memcpy(cursor->data, data, size);
+        break;
+    case SPICE_CURSOR_TYPE_COLOR32:
+        memcpy(cursor->data, data, size);
+        for (i = 0; i < hdr->width * hdr->height; i++) {
+            pix_mask = get_pix_mask(data, size, i);
+            if (pix_mask && *((guint32*)data + i) == 0xffffff) {
+                cursor->data[i] = get_pix_hack(i, hdr->width);
+            } else {
+                cursor->data[i] |= (pix_mask ? 0 : 0xff000000);
+            }
+        }
+        break;
+    case SPICE_CURSOR_TYPE_COLOR16:
+        for (i = 0; i < hdr->width * hdr->height; i++) {
+            pix_mask = get_pix_mask(data, size, i);
+            pix = *((guint16*)data + i);
+            if (pix_mask && pix == 0x7fff) {
+                cursor->data[i] = get_pix_hack(i, hdr->width);
+            } else {
+                cursor->data[i] |= ((pix & 0x1f) << 3) | ((pix & 0x3e0) << 6) |
+                    ((pix & 0x7c00) << 9) | (pix_mask ? 0 : 0xff000000);
+            }
+        }
+        break;
+    case SPICE_CURSOR_TYPE_COLOR4:
+        size = ((unsigned int)(SPICE_ALIGN(hdr->width, 2) / 2)) * hdr->height;
+        for (i = 0; i < hdr->width * hdr->height; i++) {
+            pix_mask = get_pix_mask(data, size + (sizeof(uint32_t) << 4), i);
+            int idx = (i & 1) ? (data[i >> 1] & 0x0f) : ((data[i >> 1] & 0xf0) >> 4);
+            pix = *((uint32_t*)(data + size) + idx);
+            if (pix_mask && pix == 0xffffff) {
+                cursor->data[i] = get_pix_hack(i, hdr->width);
+            } else {
+                cursor->data[i] = pix | (pix_mask ? 0 : 0xff000000);
+            }
+        }
+
+        break;
+    default:
+        g_warning("%s: unimplemented cursor type %d", __FUNCTION__,
+                  hdr->type);
+        cursor->default_cursor = TRUE;
+        goto cache_add;
+    }
+
+    rgba = (guint8*)cursor->data;
+    for (i = 0; i < hdr->width * hdr->height; i++) {
+        val = rgba[0];
+        rgba[0] = rgba[2];
+        rgba[2] = val;
+        rgba += 4;
+    }
+
+cache_add:
+    if (scursor->flags & SPICE_CURSOR_FLAGS_CACHE_ME) {
+        cache_add(c->cursors, hdr->unique, display_cursor_ref(cursor));
+    }
+
+    return cursor;
+}
+
+/* coroutine context */
+static void emit_cursor_set(SpiceChannel *channel, display_cursor *cursor)
+{
+    g_return_if_fail(cursor != NULL);
+    g_coroutine_signal_emit(channel, signals[SPICE_CURSOR_SET], 0,
+                            cursor->hdr.width, cursor->hdr.height,
+                            cursor->hdr.hot_spot_x, cursor->hdr.hot_spot_y,
+                            cursor->default_cursor ? NULL : cursor->data);
+}
+
+/* coroutine context */
+static void cursor_handle_init(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceMsgCursorInit *init = spice_msg_in_parsed(in);
+    SpiceCursorChannelPrivate *c = SPICE_CURSOR_CHANNEL(channel)->priv;
+    display_cursor *cursor;
+
+    g_return_if_fail(c->init_done == FALSE);
+
+    cache_clear(c->cursors);
+    cursor = set_cursor(channel, &init->cursor);
+    c->init_done = TRUE;
+    if (cursor)
+        emit_cursor_set(channel, cursor);
+    if (!init->visible || !cursor)
+        g_coroutine_signal_emit(channel, signals[SPICE_CURSOR_HIDE], 0);
+    if (cursor)
+        display_cursor_unref(cursor);
+}
+
+/* coroutine context */
+static void cursor_handle_reset(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceCursorChannelPrivate *c = SPICE_CURSOR_CHANNEL(channel)->priv;
+
+    CHANNEL_DEBUG(channel, "%s, init_done: %d", __FUNCTION__, c->init_done);
+
+    cache_clear(c->cursors);
+    g_coroutine_signal_emit(channel, signals[SPICE_CURSOR_RESET], 0);
+    c->init_done = FALSE;
+}
+
+/* coroutine context */
+static void cursor_handle_set(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceMsgCursorSet *set = spice_msg_in_parsed(in);
+    SpiceCursorChannelPrivate *c = SPICE_CURSOR_CHANNEL(channel)->priv;
+    display_cursor *cursor;
+
+    g_return_if_fail(c->init_done == TRUE);
+
+    cursor = set_cursor(channel, &set->cursor);
+    if (cursor)
+        emit_cursor_set(channel, cursor);
+    else
+        g_coroutine_signal_emit(channel, signals[SPICE_CURSOR_HIDE], 0);
+
+
+    if (cursor)
+        display_cursor_unref(cursor);
+}
+
+/* coroutine context */
+static void cursor_handle_move(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceMsgCursorMove *move = spice_msg_in_parsed(in);
+    SpiceCursorChannelPrivate *c = SPICE_CURSOR_CHANNEL(channel)->priv;
+
+    g_return_if_fail(c->init_done == TRUE);
+
+    g_coroutine_signal_emit(channel, signals[SPICE_CURSOR_MOVE], 0,
+                            move->position.x, move->position.y);
+}
+
+/* coroutine context */
+static void cursor_handle_hide(SpiceChannel *channel, SpiceMsgIn *in)
+{
+#ifdef EXTRA_CHECKS
+    SpiceCursorChannelPrivate *c = SPICE_CURSOR_CHANNEL(channel)->priv;
+
+    g_return_if_fail(c->init_done == TRUE);
+#endif
+
+    g_coroutine_signal_emit(channel, signals[SPICE_CURSOR_HIDE], 0);
+}
+
+/* coroutine context */
+static void cursor_handle_trail(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceCursorChannelPrivate *c = SPICE_CURSOR_CHANNEL(channel)->priv;
+
+    g_return_if_fail(c->init_done == TRUE);
+
+    g_warning("%s: TODO", __FUNCTION__);
+}
+
+/* coroutine context */
+static void cursor_handle_inval_one(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceCursorChannelPrivate *c = SPICE_CURSOR_CHANNEL(channel)->priv;
+    SpiceMsgDisplayInvalOne *zap = spice_msg_in_parsed(in);
+
+    g_return_if_fail(c->init_done == TRUE);
+
+    cache_remove(c->cursors, zap->id);
+}
+
+/* coroutine context */
+static void cursor_handle_inval_all(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceCursorChannelPrivate *c = SPICE_CURSOR_CHANNEL(channel)->priv;
+
+    cache_clear(c->cursors);
+}
+
+static void channel_set_handlers(SpiceChannelClass *klass)
+{
+    static const spice_msg_handler handlers[] = {
+        [ SPICE_MSG_CURSOR_INIT ]              = cursor_handle_init,
+        [ SPICE_MSG_CURSOR_RESET ]             = cursor_handle_reset,
+        [ SPICE_MSG_CURSOR_SET ]               = cursor_handle_set,
+        [ SPICE_MSG_CURSOR_MOVE ]              = cursor_handle_move,
+        [ SPICE_MSG_CURSOR_HIDE ]              = cursor_handle_hide,
+        [ SPICE_MSG_CURSOR_TRAIL ]             = cursor_handle_trail,
+        [ SPICE_MSG_CURSOR_INVAL_ONE ]         = cursor_handle_inval_one,
+        [ SPICE_MSG_CURSOR_INVAL_ALL ]         = cursor_handle_inval_all,
+    };
+
+    spice_channel_set_handlers(klass, handlers, G_N_ELEMENTS(handlers));
+}
diff --git a/src/channel-cursor.h b/src/channel-cursor.h
new file mode 100644
index 0000000..5b5ed47
--- /dev/null
+++ b/src/channel-cursor.h
@@ -0,0 +1,77 @@
+/* -*- 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_CLIENT_CURSOR_CHANNEL_H__
+#define __SPICE_CLIENT_CURSOR_CHANNEL_H__
+
+#include "spice-client.h"
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_CURSOR_CHANNEL            (spice_cursor_channel_get_type())
+#define SPICE_CURSOR_CHANNEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), SPICE_TYPE_CURSOR_CHANNEL, SpiceCursorChannel))
+#define SPICE_CURSOR_CHANNEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), SPICE_TYPE_CURSOR_CHANNEL, SpiceCursorChannelClass))
+#define SPICE_IS_CURSOR_CHANNEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), SPICE_TYPE_CURSOR_CHANNEL))
+#define SPICE_IS_CURSOR_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SPICE_TYPE_CURSOR_CHANNEL))
+#define SPICE_CURSOR_CHANNEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), SPICE_TYPE_CURSOR_CHANNEL, SpiceCursorChannelClass))
+
+typedef struct _SpiceCursorChannel SpiceCursorChannel;
+typedef struct _SpiceCursorChannelClass SpiceCursorChannelClass;
+typedef struct _SpiceCursorChannelPrivate SpiceCursorChannelPrivate;
+
+/**
+ * SpiceCursorChannel:
+ *
+ * The #SpiceCursorChannel struct is opaque and should not be accessed directly.
+ */
+struct _SpiceCursorChannel {
+    SpiceChannel parent;
+
+    /*< private >*/
+    SpiceCursorChannelPrivate *priv;
+    /* Do not add fields to this struct */
+};
+
+/**
+ * SpiceCursorChannelClass:
+ * @parent_class: Parent class.
+ * @cursor_set: Signal class handler for the #SpiceCursorChannel::cursor-set signal.
+ * @cursor_move: Signal class handler for the #SpiceCursorChannel::cursor-move signal.
+ * @cursor_hide: Signal class handler for the #SpiceCursorChannel::cursor-hide signal.
+ * @cursor_reset: Signal class handler for the #SpiceCursorChannel::cursor-reset signal.
+ *
+ * Class structure for #SpiceCursorChannel.
+ */
+struct _SpiceCursorChannelClass {
+    SpiceChannelClass parent_class;
+
+    /* signals */
+    void (*cursor_set)(SpiceCursorChannel *channel, gint width, gint height,
+                       gint hot_x, gint hot_y, gpointer rgba);
+    void (*cursor_move)(SpiceCursorChannel *channel, gint x, gint y);
+    void (*cursor_hide)(SpiceCursorChannel *channel);
+    void (*cursor_reset)(SpiceCursorChannel *channel);
+
+    /*< private >*/
+    /* Do not add fields to this struct */
+};
+
+GType spice_cursor_channel_get_type(void);
+
+G_END_DECLS
+
+#endif /* __SPICE_CLIENT_CURSOR_CHANNEL_H__ */
diff --git a/src/channel-display-mjpeg.c b/src/channel-display-mjpeg.c
new file mode 100644
index 0000000..95d5b33
--- /dev/null
+++ b/src/channel-display-mjpeg.c
@@ -0,0 +1,156 @@
+/* -*- 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-channel-priv.h"
+
+#include "channel-display-priv.h"
+
+static void mjpeg_src_init(struct jpeg_decompress_struct *cinfo)
+{
+    display_stream *st = SPICE_CONTAINEROF(cinfo->src, display_stream, mjpeg_src);
+    uint8_t *data;
+
+    cinfo->src->bytes_in_buffer = stream_get_current_frame(st, &data);
+    cinfo->src->next_input_byte = data;
+}
+
+static boolean mjpeg_src_fill(struct jpeg_decompress_struct *cinfo)
+{
+    g_critical("need more input data");
+    return 0;
+}
+
+static void mjpeg_src_skip(struct jpeg_decompress_struct *cinfo,
+                           long num_bytes)
+{
+    cinfo->src->next_input_byte += num_bytes;
+}
+
+static void mjpeg_src_term(struct jpeg_decompress_struct *cinfo)
+{
+    /* nothing */
+}
+
+G_GNUC_INTERNAL
+void stream_mjpeg_init(display_stream *st)
+{
+    st->mjpeg_cinfo.err = jpeg_std_error(&st->mjpeg_jerr);
+    jpeg_create_decompress(&st->mjpeg_cinfo);
+
+    st->mjpeg_src.init_source         = mjpeg_src_init;
+    st->mjpeg_src.fill_input_buffer   = mjpeg_src_fill;
+    st->mjpeg_src.skip_input_data     = mjpeg_src_skip;
+    st->mjpeg_src.resync_to_restart   = jpeg_resync_to_restart;
+    st->mjpeg_src.term_source         = mjpeg_src_term;
+    st->mjpeg_cinfo.src               = &st->mjpeg_src;
+}
+
+G_GNUC_INTERNAL
+void stream_mjpeg_data(display_stream *st)
+{
+    gboolean back_compat = st->channel->priv->peer_hdr.major_version == 1;
+    int width;
+    int height;
+    uint8_t *dest;
+    uint8_t *lines[4];
+
+    stream_get_dimensions(st, &width, &height);
+    dest = g_malloc0(width * height * 4);
+
+    g_free(st->out_frame);
+    st->out_frame = dest;
+
+    jpeg_read_header(&st->mjpeg_cinfo, 1);
+#ifdef JCS_EXTENSIONS
+    // requires jpeg-turbo
+    if (back_compat)
+        st->mjpeg_cinfo.out_color_space = JCS_EXT_RGBX;
+    else
+        st->mjpeg_cinfo.out_color_space = JCS_EXT_BGRX;
+#else
+#warning "You should consider building with libjpeg-turbo"
+    st->mjpeg_cinfo.out_color_space = JCS_RGB;
+#endif
+
+#ifndef SPICE_QUALITY
+    st->mjpeg_cinfo.dct_method = JDCT_IFAST;
+    st->mjpeg_cinfo.do_fancy_upsampling = FALSE;
+    st->mjpeg_cinfo.do_block_smoothing = FALSE;
+    st->mjpeg_cinfo.dither_mode = JDITHER_ORDERED;
+#endif
+    // TODO: in theory should check cinfo.output_height match with our height
+    jpeg_start_decompress(&st->mjpeg_cinfo);
+    /* rec_outbuf_height is the recommended size of the output buffer we
+     * pass to libjpeg for optimum performance
+     */
+    if (st->mjpeg_cinfo.rec_outbuf_height > G_N_ELEMENTS(lines)) {
+        jpeg_abort_decompress(&st->mjpeg_cinfo);
+        g_return_if_reached();
+    }
+
+    while (st->mjpeg_cinfo.output_scanline < st->mjpeg_cinfo.output_height) {
+        /* only used when JCS_EXTENSIONS is undefined */
+        G_GNUC_UNUSED unsigned int lines_read;
+
+        for (unsigned int j = 0; j < st->mjpeg_cinfo.rec_outbuf_height; j++) {
+            lines[j] = dest;
+#ifdef JCS_EXTENSIONS
+            dest += 4 * width;
+#else
+            dest += 3 * width;
+#endif
+        }
+        lines_read = jpeg_read_scanlines(&st->mjpeg_cinfo, lines,
+                                st->mjpeg_cinfo.rec_outbuf_height);
+#ifndef JCS_EXTENSIONS
+        {
+            uint8_t *s = lines[0];
+            uint32_t *d = (uint32_t *)s;
+
+            if (back_compat) {
+                for (unsigned int j = lines_read * width; j > 0; ) {
+                    j -= 1; // reverse order, bad for cache?
+                    d[j] = s[j * 3 + 0] |
+                        s[j * 3 + 1] << 8 |
+                        s[j * 3 + 2] << 16;
+                }
+            } else {
+                for (unsigned int j = lines_read * width; j > 0; ) {
+                    j -= 1; // reverse order, bad for cache?
+                    d[j] = s[j * 3 + 0] << 16 |
+                        s[j * 3 + 1] << 8 |
+                        s[j * 3 + 2];
+                }
+            }
+        }
+#endif
+        dest = &st->out_frame[st->mjpeg_cinfo.output_scanline * width * 4];
+    }
+    jpeg_finish_decompress(&st->mjpeg_cinfo);
+}
+
+G_GNUC_INTERNAL
+void stream_mjpeg_cleanup(display_stream *st)
+{
+    jpeg_destroy_decompress(&st->mjpeg_cinfo);
+    g_free(st->out_frame);
+    st->out_frame = NULL;
+}
diff --git a/src/channel-display-priv.h b/src/channel-display-priv.h
new file mode 100644
index 0000000..71f5d17
--- /dev/null
+++ b/src/channel-display-priv.h
@@ -0,0 +1,113 @@
+/* -*- 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 CHANNEL_DISPLAY_PRIV_H_
+# define CHANNEL_DISPLAY_PRIV_H_
+
+#include <pixman.h>
+#ifdef WIN32
+/* We need some hacks to avoid warnings from the jpeg headers */
+#define HAVE_BOOLEAN
+#define XMD_H
+#endif
+#include <jpeglib.h>
+
+#include "common/canvas_utils.h"
+#include "client_sw_canvas.h"
+#include "common/ring.h"
+#include "common/quic.h"
+#include "common/rop3.h"
+
+G_BEGIN_DECLS
+
+
+typedef struct display_surface {
+    guint32                     surface_id;
+    bool                        primary;
+    enum SpiceSurfaceFmt        format;
+    int                         width, height, stride, size;
+    int                         shmid;
+    uint8_t                     *data;
+    SpiceCanvas                 *canvas;
+    SpiceGlzDecoder             *glz_decoder;
+    SpiceZlibDecoder            *zlib_decoder;
+    SpiceJpegDecoder            *jpeg_decoder;
+} display_surface;
+
+typedef struct drops_sequence_stats {
+    uint32_t len;
+    uint32_t start_mm_time;
+    uint32_t duration;
+} drops_sequence_stats;
+
+typedef struct display_stream {
+    SpiceMsgIn                  *msg_create;
+    SpiceMsgIn                  *msg_clip;
+    SpiceMsgIn                  *msg_data;
+
+    /* from messages */
+    display_surface             *surface;
+    SpiceClip                   *clip;
+    QRegion                     region;
+    int                         have_region;
+    int                         codec;
+
+    /* mjpeg decoder */
+    struct jpeg_source_mgr         mjpeg_src;
+    struct jpeg_decompress_struct  mjpeg_cinfo;
+    struct jpeg_error_mgr          mjpeg_jerr;
+
+    uint8_t                     *out_frame;
+    GQueue                      *msgq;
+    guint                       timeout;
+    SpiceChannel                *channel;
+
+    /* stats */
+    uint32_t             first_frame_mm_time;
+    uint32_t             num_drops_on_receive;
+    uint64_t             arrive_late_time;
+    uint32_t             num_drops_on_playback;
+    uint32_t             num_input_frames;
+    drops_sequence_stats cur_drops_seq_stats;
+    GArray               *drops_seqs_stats_arr;
+    uint32_t             num_drops_seqs;
+
+    uint32_t             playback_sync_drops_seq_len;
+
+    /* playback quality report to server */
+    gboolean report_is_active;
+    uint32_t report_id;
+    uint32_t report_max_window;
+    uint32_t report_timeout;
+    uint64_t report_start_time;
+    uint32_t report_start_frame_time;
+    uint32_t report_num_frames;
+    uint32_t report_num_drops;
+    uint32_t report_drops_seq_len;
+} display_stream;
+
+void stream_get_dimensions(display_stream *st, int *width, int *height);
+uint32_t stream_get_current_frame(display_stream *st, uint8_t **data);
+
+/* channel-display-mjpeg.c */
+void stream_mjpeg_init(display_stream *st);
+void stream_mjpeg_data(display_stream *st);
+void stream_mjpeg_cleanup(display_stream *st);
+
+G_END_DECLS
+
+#endif // CHANNEL_DISPLAY_PRIV_H_
diff --git a/src/channel-display.c b/src/channel-display.c
new file mode 100644
index 0000000..efe2259
--- /dev/null
+++ b/src/channel-display.c
@@ -0,0 +1,1789 @@
+/* -*- 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"
+
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+#ifdef HAVE_SYS_SHM_H
+#include <sys/shm.h>
+#endif
+
+#ifdef HAVE_SYS_IPC_H
+#include <sys/ipc.h>
+#endif
+
+#include "glib-compat.h"
+#include "spice-client.h"
+#include "spice-common.h"
+
+#include "spice-marshal.h"
+#include "spice-channel-priv.h"
+#include "spice-session-priv.h"
+#include "channel-display-priv.h"
+#include "decode.h"
+
+/**
+ * SECTION:channel-display
+ * @short_description: remote display area
+ * @title: Display Channel
+ * @section_id:
+ * @see_also: #SpiceChannel, and the GTK widget #SpiceDisplay
+ * @stability: Stable
+ * @include: channel-display.h
+ *
+ * A class that handles the rendering of the remote display and inform
+ * of its updates.
+ *
+ * The creation of the main graphic buffer is signaled with
+ * #SpiceDisplayChannel::display-primary-create.
+ *
+ * The update of regions is notified by
+ * #SpiceDisplayChannel::display-invalidate signals.
+ */
+
+#define SPICE_DISPLAY_CHANNEL_GET_PRIVATE(obj)                                  \
+    (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_DISPLAY_CHANNEL, SpiceDisplayChannelPrivate))
+
+#define MONITORS_MAX 256
+
+struct _SpiceDisplayChannelPrivate {
+    GHashTable                  *surfaces;
+    display_surface             *primary;
+    display_cache               *images;
+    display_cache               *palettes;
+    SpiceImageCache             image_cache;
+    SpicePaletteCache           palette_cache;
+    SpiceImageSurfaces          image_surfaces;
+    SpiceGlzDecoderWindow       *glz_window;
+    display_stream              **streams;
+    int                         nstreams;
+    gboolean                    mark;
+    guint                       mark_false_event_id;
+    GArray                      *monitors;
+    guint                       monitors_max;
+    gboolean                    enable_adaptive_streaming;
+#ifdef G_OS_WIN32
+    HDC dc;
+#endif
+};
+
+G_DEFINE_TYPE(SpiceDisplayChannel, spice_display_channel, SPICE_TYPE_CHANNEL)
+
+/* Properties */
+enum {
+    PROP_0,
+    PROP_WIDTH,
+    PROP_HEIGHT,
+    PROP_MONITORS,
+    PROP_MONITORS_MAX
+};
+
+enum {
+    SPICE_DISPLAY_PRIMARY_CREATE,
+    SPICE_DISPLAY_PRIMARY_DESTROY,
+    SPICE_DISPLAY_INVALIDATE,
+    SPICE_DISPLAY_MARK,
+
+    SPICE_DISPLAY_LAST_SIGNAL,
+};
+
+static guint signals[SPICE_DISPLAY_LAST_SIGNAL];
+
+static void spice_display_channel_up(SpiceChannel *channel);
+static void channel_set_handlers(SpiceChannelClass *klass);
+
+static void clear_surfaces(SpiceChannel *channel, gboolean keep_primary);
+static void clear_streams(SpiceChannel *channel);
+static display_surface *find_surface(SpiceDisplayChannelPrivate *c, guint32 surface_id);
+static gboolean display_stream_render(display_stream *st);
+static void spice_display_channel_reset(SpiceChannel *channel, gboolean migrating);
+static void spice_display_channel_reset_capabilities(SpiceChannel *channel);
+static void destroy_canvas(display_surface *surface);
+static void _msg_in_unref_func(gpointer data, gpointer user_data);
+static void display_session_mm_time_reset_cb(SpiceSession *session, gpointer data);
+
+/* ------------------------------------------------------------------ */
+
+static void spice_display_channel_dispose(GObject *object)
+{
+    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(object)->priv;
+
+    if (c->mark_false_event_id != 0) {
+        g_source_remove(c->mark_false_event_id);
+        c->mark_false_event_id = 0;
+    }
+
+    if (G_OBJECT_CLASS(spice_display_channel_parent_class)->dispose)
+        G_OBJECT_CLASS(spice_display_channel_parent_class)->dispose(object);
+}
+
+static void spice_display_channel_finalize(GObject *object)
+{
+    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(object)->priv;
+
+    g_clear_pointer(&c->monitors, g_array_unref);
+    clear_surfaces(SPICE_CHANNEL(object), FALSE);
+    g_hash_table_unref(c->surfaces);
+    clear_streams(SPICE_CHANNEL(object));
+    g_clear_pointer(&c->palettes, cache_unref);
+
+    if (G_OBJECT_CLASS(spice_display_channel_parent_class)->finalize)
+        G_OBJECT_CLASS(spice_display_channel_parent_class)->finalize(object);
+}
+
+static void spice_display_channel_constructed(GObject *object)
+{
+    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(object)->priv;
+    SpiceSession *s = spice_channel_get_session(SPICE_CHANNEL(object));
+
+    g_return_if_fail(s != NULL);
+    spice_session_get_caches(s, &c->images, &c->glz_window);
+    c->palettes = cache_new(g_free);
+
+    g_return_if_fail(c->glz_window != NULL);
+    g_return_if_fail(c->images != NULL);
+    g_return_if_fail(c->palettes != NULL);
+
+    c->monitors = g_array_new(FALSE, TRUE, sizeof(SpiceDisplayMonitorConfig));
+    spice_g_signal_connect_object(s, "mm-time-reset",
+                                  G_CALLBACK(display_session_mm_time_reset_cb),
+                                  SPICE_CHANNEL(object), 0);
+
+
+    if (G_OBJECT_CLASS(spice_display_channel_parent_class)->constructed)
+        G_OBJECT_CLASS(spice_display_channel_parent_class)->constructed(object);
+}
+
+
+static void spice_display_get_property(GObject    *object,
+                                       guint       prop_id,
+                                       GValue     *value,
+                                       GParamSpec *pspec)
+{
+    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(object)->priv;
+
+    switch (prop_id) {
+    case PROP_WIDTH: {
+        g_value_set_uint(value, c->primary ? c->primary->width : 0);
+        break;
+    }
+    case PROP_HEIGHT: {
+        g_value_set_uint(value, c->primary ? c->primary->height : 0);
+        break;
+    }
+    case PROP_MONITORS: {
+        g_value_set_boxed(value, c->monitors);
+        break;
+    }
+    case PROP_MONITORS_MAX: {
+        g_value_set_uint(value, c->monitors_max);
+        break;
+    }
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void spice_display_set_property(GObject      *object,
+                                       guint         prop_id,
+                                       const GValue *value,
+                                       GParamSpec   *pspec)
+{
+    switch (prop_id) {
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+/* main or coroutine context */
+static void spice_display_channel_reset(SpiceChannel *channel, gboolean migrating)
+{
+    /* palettes, images, and glz_window are cleared in the session */
+    clear_streams(channel);
+    clear_surfaces(channel, TRUE);
+
+    SPICE_CHANNEL_CLASS(spice_display_channel_parent_class)->channel_reset(channel, migrating);
+}
+
+static void spice_display_channel_class_init(SpiceDisplayChannelClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+    SpiceChannelClass *channel_class = SPICE_CHANNEL_CLASS(klass);
+
+    gobject_class->finalize     = spice_display_channel_finalize;
+    gobject_class->dispose      = spice_display_channel_dispose;
+    gobject_class->get_property = spice_display_get_property;
+    gobject_class->set_property = spice_display_set_property;
+    gobject_class->constructed = spice_display_channel_constructed;
+
+    channel_class->channel_up   = spice_display_channel_up;
+    channel_class->channel_reset = spice_display_channel_reset;
+    channel_class->channel_reset_capabilities = spice_display_channel_reset_capabilities;
+
+    g_object_class_install_property
+        (gobject_class, PROP_HEIGHT,
+         g_param_spec_uint("height",
+                           "Display height",
+                           "The primary surface height",
+                           0, G_MAXUINT, 0,
+                           G_PARAM_READABLE |
+                           G_PARAM_STATIC_STRINGS));
+
+    g_object_class_install_property
+        (gobject_class, PROP_WIDTH,
+         g_param_spec_uint("width",
+                           "Display width",
+                           "The primary surface width",
+                           0, G_MAXUINT, 0,
+                           G_PARAM_READABLE |
+                           G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceDisplayChannel:monitors:
+     *
+     * Current monitors configuration.
+     *
+     * Since: 0.13
+     */
+    g_object_class_install_property
+        (gobject_class, PROP_MONITORS,
+         g_param_spec_boxed("monitors",
+                            "Display monitors",
+                            "The monitors configuration",
+                            G_TYPE_ARRAY,
+                            G_PARAM_READABLE |
+                            G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceDisplayChannel:monitors-max:
+     *
+     * The maximum number of monitors the server or guest supports.
+     * May change during client lifetime, for instance guest may
+     * reboot or dynamically adjust this.
+     *
+     * Since: 0.13
+     */
+    g_object_class_install_property
+        (gobject_class, PROP_MONITORS_MAX,
+         g_param_spec_uint("monitors-max",
+                           "Max display monitors",
+                           "The current maximum number of monitors",
+                           1, MONITORS_MAX, 1,
+                           G_PARAM_READABLE |
+                           G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceDisplayChannel::display-primary-create:
+     * @display: the #SpiceDisplayChannel that emitted the signal
+     * @format: %SPICE_SURFACE_FMT_32_xRGB or %SPICE_SURFACE_FMT_16_555;
+     * @width: width resolution
+     * @height: height resolution
+     * @stride: the buffer stride ("width" padding)
+     * @shmid: identifier of the shared memory segment associated with
+     * the @imgdata, or -1 if not shm
+     * @imgdata: pointer to surface buffer
+     *
+     * The #SpiceDisplayChannel::display-primary-create signal
+     * provides main display buffer data.
+     **/
+    signals[SPICE_DISPLAY_PRIMARY_CREATE] =
+        g_signal_new("display-primary-create",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_FIRST,
+                     G_STRUCT_OFFSET(SpiceDisplayChannelClass,
+                                     display_primary_create),
+                     NULL, NULL,
+                     g_cclosure_user_marshal_VOID__INT_INT_INT_INT_INT_POINTER,
+                     G_TYPE_NONE,
+                     6,
+                     G_TYPE_INT, G_TYPE_INT, G_TYPE_INT,
+                     G_TYPE_INT, G_TYPE_INT, G_TYPE_POINTER);
+
+    /**
+     * SpiceDisplayChannel::display-primary-destroy:
+     * @display: the #SpiceDisplayChannel that emitted the signal
+     *
+     * The #SpiceDisplayChannel::display-primary-destroy signal is
+     * emitted when the primary surface is freed and should not be
+     * accessed anymore.
+     **/
+    signals[SPICE_DISPLAY_PRIMARY_DESTROY] =
+        g_signal_new("display-primary-destroy",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_FIRST,
+                     G_STRUCT_OFFSET(SpiceDisplayChannelClass,
+                                     display_primary_destroy),
+                     NULL, NULL,
+                     g_cclosure_marshal_VOID__VOID,
+                     G_TYPE_NONE,
+                     0);
+
+    /**
+     * SpiceDisplayChannel::display-invalidate:
+     * @display: the #SpiceDisplayChannel that emitted the signal
+     * @x: x position
+     * @y: y position
+     * @width: width
+     * @height: height
+     *
+     * The #SpiceDisplayChannel::display-invalidate signal is emitted
+     * when the rectangular region x/y/w/h of the primary buffer is
+     * updated.
+     **/
+    signals[SPICE_DISPLAY_INVALIDATE] =
+        g_signal_new("display-invalidate",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_FIRST,
+                     G_STRUCT_OFFSET(SpiceDisplayChannelClass,
+                                     display_invalidate),
+                     NULL, NULL,
+                     g_cclosure_user_marshal_VOID__INT_INT_INT_INT,
+                     G_TYPE_NONE,
+                     4,
+                     G_TYPE_INT, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT);
+
+    /**
+     * SpiceDisplayChannel::display-mark:
+     * @display: the #SpiceDisplayChannel that emitted the signal
+     * @mark: %TRUE when the display mark has been received
+     *
+     * The #SpiceDisplayChannel::display-mark signal is emitted when
+     * the %RED_DISPLAY_MARK command is received, and the display
+     * should be exposed.
+     **/
+    signals[SPICE_DISPLAY_MARK] =
+        g_signal_new("display-mark",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_FIRST,
+                     G_STRUCT_OFFSET(SpiceDisplayChannelClass,
+                                     display_mark),
+                     NULL, NULL,
+                     g_cclosure_marshal_VOID__INT,
+                     G_TYPE_NONE,
+                     1,
+                     G_TYPE_INT);
+
+    g_type_class_add_private(klass, sizeof(SpiceDisplayChannelPrivate));
+
+    sw_canvas_init();
+    quic_init();
+    rop3_init();
+    channel_set_handlers(SPICE_CHANNEL_CLASS(klass));
+}
+
+/**
+ * spice_display_get_primary:
+ * @channel:
+ * @surface_id:
+ * @primary:
+ *
+ * Retrieve primary display surface @surface_id.
+ *
+ * Returns: %TRUE if the primary surface was found and its details
+ * collected in @primary.
+ */
+gboolean spice_display_get_primary(SpiceChannel *channel, guint32 surface_id,
+                                   SpiceDisplayPrimary *primary)
+{
+    g_return_val_if_fail(SPICE_IS_DISPLAY_CHANNEL(channel), FALSE);
+    g_return_val_if_fail(primary != NULL, FALSE);
+
+    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
+    display_surface *surface = find_surface(c, surface_id);
+
+    if (surface == NULL)
+        return FALSE;
+
+    g_return_val_if_fail(surface->primary, FALSE);
+
+    primary->format = surface->format;
+    primary->width = surface->width;
+    primary->height = surface->height;
+    primary->stride = surface->stride;
+    primary->shmid = surface->shmid;
+    primary->data = surface->data;
+    primary->marked = c->mark;
+    CHANNEL_DEBUG(channel, "get primary %p", primary->data);
+
+    return TRUE;
+}
+
+/* ------------------------------------------------------------------ */
+
+static void image_put(SpiceImageCache *cache, uint64_t id, pixman_image_t *image)
+{
+    SpiceDisplayChannelPrivate *c =
+        SPICE_CONTAINEROF(cache, SpiceDisplayChannelPrivate, image_cache);
+
+    cache_add(c->images, id, pixman_image_ref(image));
+}
+
+typedef struct _WaitImageData
+{
+    gboolean lossy;
+    SpiceImageCache *cache;
+    uint64_t id;
+    pixman_image_t *image;
+} WaitImageData;
+
+static gboolean wait_image(gpointer data)
+{
+    gboolean lossy;
+    WaitImageData *wait = data;
+    SpiceDisplayChannelPrivate *c =
+        SPICE_CONTAINEROF(wait->cache, SpiceDisplayChannelPrivate, image_cache);
+    pixman_image_t *image = cache_find_lossy(c->images, wait->id, &lossy);
+
+    if (!image || (lossy && !wait->lossy))
+        return FALSE;
+
+    wait->image = pixman_image_ref(image);
+
+    return TRUE;
+}
+
+static pixman_image_t *image_get(SpiceImageCache *cache, uint64_t id)
+{
+    WaitImageData wait = {
+        .lossy = TRUE,
+        .cache = cache,
+        .id = id,
+        .image = NULL
+    };
+    if (!g_coroutine_condition_wait(g_coroutine_self(), wait_image, &wait))
+        SPICE_DEBUG("wait image got cancelled");
+
+    return wait.image;
+}
+
+static void palette_put(SpicePaletteCache *cache, SpicePalette *palette)
+{
+    SpiceDisplayChannelPrivate *c =
+        SPICE_CONTAINEROF(cache, SpiceDisplayChannelPrivate, palette_cache);
+
+    cache_add(c->palettes, palette->unique,
+              g_memdup(palette, sizeof(SpicePalette) +
+                       palette->num_ents * sizeof(palette->ents[0])));
+}
+
+static SpicePalette *palette_get(SpicePaletteCache *cache, uint64_t id)
+{
+    SpiceDisplayChannelPrivate *c =
+        SPICE_CONTAINEROF(cache, SpiceDisplayChannelPrivate, palette_cache);
+
+    /* here the returned pointer is weak, no ref given to caller.  it
+     * seems spice canvas usage is exclusively temporary, so it's ok.
+     * palette_release is a noop. */
+    return cache_find(c->palettes, id);
+}
+
+static void palette_remove(SpicePaletteCache *cache, uint64_t id)
+{
+    SpiceDisplayChannelPrivate *c =
+        SPICE_CONTAINEROF(cache, SpiceDisplayChannelPrivate, palette_cache);
+
+    cache_remove(c->palettes, id);
+}
+
+static void palette_release(SpicePaletteCache *cache, SpicePalette *palette)
+{
+    /* there is no refcount of palette, see palette_get() */
+}
+
+static void image_put_lossy(SpiceImageCache *cache, uint64_t id,
+                            pixman_image_t *surface)
+{
+    SpiceDisplayChannelPrivate *c =
+        SPICE_CONTAINEROF(cache, SpiceDisplayChannelPrivate, image_cache);
+
+#ifndef NDEBUG
+    g_warn_if_fail(cache_find(c->images, id) == NULL);
+#endif
+
+    cache_add_lossy(c->images, id, pixman_image_ref(surface), TRUE);
+}
+
+static void image_replace_lossy(SpiceImageCache *cache, uint64_t id,
+                                pixman_image_t *surface)
+{
+    image_put(cache, id, surface);
+}
+
+static pixman_image_t* image_get_lossless(SpiceImageCache *cache, uint64_t id)
+{
+    WaitImageData wait = {
+        .lossy = FALSE,
+        .cache = cache,
+        .id = id,
+        .image = NULL
+    };
+    if (!g_coroutine_condition_wait(g_coroutine_self(), wait_image, &wait))
+        SPICE_DEBUG("wait lossless got cancelled");
+
+    return wait.image;
+}
+
+static SpiceCanvas *surfaces_get(SpiceImageSurfaces *surfaces,
+                                 uint32_t surface_id)
+{
+    SpiceDisplayChannelPrivate *c =
+        SPICE_CONTAINEROF(surfaces, SpiceDisplayChannelPrivate, image_surfaces);
+
+    display_surface *s =
+        find_surface(c, surface_id);
+
+    return s ? s->canvas : NULL;
+}
+
+static SpiceImageCacheOps image_cache_ops = {
+    .put = image_put,
+    .get = image_get,
+
+    .put_lossy = image_put_lossy,
+    .replace_lossy = image_replace_lossy,
+    .get_lossless = image_get_lossless,
+};
+
+static SpicePaletteCacheOps palette_cache_ops = {
+    .put     = palette_put,
+    .get     = palette_get,
+    .release = palette_release,
+};
+
+static SpiceImageSurfacesOps image_surfaces_ops = {
+    .get = surfaces_get
+};
+
+#if defined(G_OS_WIN32)
+static HDC create_compatible_dc(void)
+{
+    HDC dc = CreateCompatibleDC(NULL);
+    if (!dc) {
+        g_warning("create compatible DC failed");
+    }
+    return dc;
+}
+#endif
+
+static void spice_display_channel_reset_capabilities(SpiceChannel *channel)
+{
+    spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_DISPLAY_CAP_SIZED_STREAM);
+    spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_DISPLAY_CAP_MONITORS_CONFIG);
+    spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_DISPLAY_CAP_COMPOSITE);
+    spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_DISPLAY_CAP_A8_SURFACE);
+#ifdef USE_LZ4
+    spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_DISPLAY_CAP_LZ4_COMPRESSION);
+#endif
+    if (SPICE_DISPLAY_CHANNEL(channel)->priv->enable_adaptive_streaming) {
+        spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_DISPLAY_CAP_STREAM_REPORT);
+    }
+}
+
+static void destroy_surface(gpointer data)
+{
+    display_surface *surface = data;
+
+    destroy_canvas(surface);
+    g_slice_free(display_surface, surface);
+}
+
+static void spice_display_channel_init(SpiceDisplayChannel *channel)
+{
+    SpiceDisplayChannelPrivate *c;
+
+    c = channel->priv = SPICE_DISPLAY_CHANNEL_GET_PRIVATE(channel);
+
+    c->surfaces = g_hash_table_new_full(NULL, NULL, NULL, destroy_surface);
+    c->image_cache.ops = &image_cache_ops;
+    c->palette_cache.ops = &palette_cache_ops;
+    c->image_surfaces.ops = &image_surfaces_ops;
+#if defined(G_OS_WIN32)
+    c->dc = create_compatible_dc();
+#endif
+    c->monitors_max = 1;
+
+    if (g_getenv("SPICE_DISABLE_ADAPTIVE_STREAMING")) {
+        SPICE_DEBUG("adaptive video disabled");
+        c->enable_adaptive_streaming = FALSE;
+    } else {
+        c->enable_adaptive_streaming = TRUE;
+    }
+    spice_display_channel_reset_capabilities(SPICE_CHANNEL(channel));
+}
+
+/* ------------------------------------------------------------------ */
+
+static int create_canvas(SpiceChannel *channel, display_surface *surface)
+{
+    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
+
+    if (surface->primary) {
+        if (c->primary) {
+            if (c->primary->width == surface->width &&
+                c->primary->height == surface->height) {
+                CHANNEL_DEBUG(channel, "Reusing existing primary surface");
+                return 0;
+            }
+
+            g_coroutine_signal_emit(channel, signals[SPICE_DISPLAY_PRIMARY_DESTROY], 0);
+
+            g_hash_table_remove(c->surfaces, GINT_TO_POINTER(c->primary->surface_id));
+        }
+
+        CHANNEL_DEBUG(channel, "Create primary canvas");
+#if defined(WITH_X11) && defined(HAVE_SYS_SHM_H)
+        surface->shmid = shmget(IPC_PRIVATE, surface->size, IPC_CREAT | 0777);
+        if (surface->shmid >= 0) {
+            surface->data = shmat(surface->shmid, 0, 0);
+            if (surface->data == NULL) {
+                shmctl(surface->shmid, IPC_RMID, 0);
+                surface->shmid = -1;
+            }
+        }
+#else
+        surface->shmid = -1;
+#endif
+    } else {
+        surface->shmid = -1;
+    }
+
+    if (surface->shmid == -1)
+        surface->data = g_malloc0(surface->size);
+
+    g_return_val_if_fail(c->glz_window, 0);
+
+    g_warn_if_fail(surface->canvas == NULL);
+    g_warn_if_fail(surface->glz_decoder == NULL);
+    g_warn_if_fail(surface->zlib_decoder == NULL);
+    g_warn_if_fail(surface->jpeg_decoder == NULL);
+
+    surface->glz_decoder = glz_decoder_new(c->glz_window);
+    surface->zlib_decoder = zlib_decoder_new();
+    surface->jpeg_decoder = jpeg_decoder_new();
+
+    surface->canvas = canvas_create_for_data(surface->width,
+                                             surface->height,
+                                             surface->format,
+                                             surface->data,
+                                             surface->stride,
+                                             &c->image_cache,
+                                             &c->palette_cache,
+                                             &c->image_surfaces,
+                                             surface->glz_decoder,
+                                             surface->jpeg_decoder,
+                                             surface->zlib_decoder);
+
+    g_return_val_if_fail(surface->canvas != NULL, 0);
+    g_hash_table_insert(c->surfaces, GINT_TO_POINTER(surface->surface_id), surface);
+
+    if (surface->primary) {
+        g_warn_if_fail(c->primary == NULL);
+        c->primary = surface;
+        g_coroutine_signal_emit(channel, signals[SPICE_DISPLAY_PRIMARY_CREATE], 0,
+                                surface->format, surface->width, surface->height,
+                                surface->stride, surface->shmid, surface->data);
+
+        if (!spice_channel_test_capability(channel, SPICE_DISPLAY_CAP_MONITORS_CONFIG)) {
+            g_array_set_size(c->monitors, 1);
+            SpiceDisplayMonitorConfig *config = &g_array_index(c->monitors, SpiceDisplayMonitorConfig, 0);
+            config->x = config->y = 0;
+            config->width = surface->width;
+            config->height = surface->height;
+            g_coroutine_object_notify(G_OBJECT(channel), "monitors");
+        }
+    }
+
+    return 0;
+}
+
+static void destroy_canvas(display_surface *surface)
+{
+    if (surface == NULL)
+        return;
+
+    glz_decoder_destroy(surface->glz_decoder);
+    zlib_decoder_destroy(surface->zlib_decoder);
+    jpeg_decoder_destroy(surface->jpeg_decoder);
+
+    if (surface->shmid == -1) {
+        g_free(surface->data);
+    }
+#ifdef HAVE_SYS_SHM_H
+    else {
+        shmdt(surface->data);
+        shmctl(surface->shmid, IPC_RMID, 0);
+    }
+#endif
+    surface->shmid = -1;
+    surface->data = NULL;
+
+    surface->canvas->ops->destroy(surface->canvas);
+    surface->canvas = NULL;
+}
+
+static display_surface *find_surface(SpiceDisplayChannelPrivate *c, guint32 surface_id)
+{
+    if (c->primary && c->primary->surface_id == surface_id)
+        return c->primary;
+
+    return g_hash_table_lookup(c->surfaces, GINT_TO_POINTER(surface_id));
+}
+
+/* main or coroutine context */
+static void clear_surfaces(SpiceChannel *channel, gboolean keep_primary)
+{
+    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
+    GHashTableIter iter;
+    display_surface *surface;
+
+    if (!keep_primary) {
+        c->primary = NULL;
+        g_coroutine_signal_emit(channel, signals[SPICE_DISPLAY_PRIMARY_DESTROY], 0);
+    }
+
+    g_hash_table_iter_init(&iter, c->surfaces);
+    while (g_hash_table_iter_next(&iter, NULL, (gpointer*)&surface)) {
+
+        if (keep_primary && surface->primary) {
+            CHANNEL_DEBUG(channel, "keeping existing primary surface, migration or reset");
+            continue;
+        }
+
+        g_hash_table_iter_remove(&iter);
+    }
+}
+
+/* coroutine context */
+static void emit_invalidate(SpiceChannel *channel, SpiceRect *bbox)
+{
+    g_coroutine_signal_emit(channel, signals[SPICE_DISPLAY_INVALIDATE], 0,
+                            bbox->left, bbox->top,
+                            bbox->right - bbox->left,
+                            bbox->bottom - bbox->top);
+}
+
+/* ------------------------------------------------------------------ */
+
+/* coroutine context */
+static void spice_display_channel_up(SpiceChannel *channel)
+{
+    SpiceMsgOut *out;
+    SpiceSession *s = spice_channel_get_session(channel);
+    SpiceMsgcDisplayInit init;
+    int cache_size;
+    int glz_window_size;
+
+    g_object_get(s,
+                 "cache-size", &cache_size,
+                 "glz-window-size", &glz_window_size,
+                 NULL);
+    CHANNEL_DEBUG(channel, "%s: cache_size %d, glz_window_size %d (bytes)", __FUNCTION__,
+                  cache_size, glz_window_size);
+    init.pixmap_cache_id = 1;
+    init.glz_dictionary_id = 1;
+    init.pixmap_cache_size = cache_size / 4; /* pixels */
+    init.glz_dictionary_window_size = glz_window_size / 4; /* pixels */
+    out = spice_msg_out_new(channel, SPICE_MSGC_DISPLAY_INIT);
+    out->marshallers->msgc_display_init(out->marshaller, &init);
+    spice_msg_out_send_internal(out);
+
+    /* if we are not using monitors config, notify of existence of
+       this monitor */
+    if (channel->priv->channel_id != 0)
+        g_coroutine_object_notify(G_OBJECT(channel), "monitors");
+}
+
+#define DRAW(type) {                                                    \
+        display_surface *surface =                                      \
+            find_surface(SPICE_DISPLAY_CHANNEL(channel)->priv,          \
+                op->base.surface_id);                                   \
+        g_return_if_fail(surface != NULL);                              \
+        surface->canvas->ops->draw_##type(surface->canvas, &op->base.box, \
+                                          &op->base.clip, &op->data);   \
+        if (surface->primary) {                                         \
+            emit_invalidate(channel, &op->base.box);                    \
+        }                                                               \
+}
+
+/* coroutine context */
+static void display_handle_mode(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
+    SpiceMsgDisplayMode *mode = spice_msg_in_parsed(in);
+    display_surface *surface;
+
+    g_warn_if_fail(c->mark == FALSE);
+
+    surface = g_slice_new0(display_surface);
+    surface->format  = mode->bits == 32 ?
+        SPICE_SURFACE_FMT_32_xRGB : SPICE_SURFACE_FMT_16_555;
+    surface->width   = mode->x_res;
+    surface->height  = mode->y_res;
+    surface->stride  = surface->width * 4;
+    surface->size    = surface->height * surface->stride;
+    surface->primary = true;
+    create_canvas(channel, surface);
+}
+
+/* coroutine context */
+static void display_handle_mark(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
+
+    CHANNEL_DEBUG(channel, "%s", __FUNCTION__);
+    g_return_if_fail(c->primary != NULL);
+#ifdef EXTRA_CHECKS
+    g_warn_if_fail(c->mark == FALSE);
+#endif
+
+    c->mark = TRUE;
+    g_coroutine_signal_emit(channel, signals[SPICE_DISPLAY_MARK], 0, TRUE);
+}
+
+/* coroutine context */
+static void display_handle_reset(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
+    display_surface *surface = c->primary;
+
+    CHANNEL_DEBUG(channel, "%s: TODO detach_from_screen", __FUNCTION__);
+
+    if (surface != NULL)
+        surface->canvas->ops->clear(surface->canvas);
+
+    cache_clear(c->palettes);
+
+    c->mark = FALSE;
+    g_coroutine_signal_emit(channel, signals[SPICE_DISPLAY_MARK], 0, FALSE);
+}
+
+/* coroutine context */
+static void display_handle_copy_bits(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceMsgDisplayCopyBits *op = spice_msg_in_parsed(in);
+    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
+    display_surface *surface = find_surface(c, op->base.surface_id);
+
+    g_return_if_fail(surface != NULL);
+    surface->canvas->ops->copy_bits(surface->canvas, &op->base.box,
+                                    &op->base.clip, &op->src_pos);
+    if (surface->primary) {
+        emit_invalidate(channel, &op->base.box);
+    }
+}
+
+/* coroutine context */
+static void display_handle_inv_list(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
+    SpiceResourceList *list = spice_msg_in_parsed(in);
+    int i;
+
+    for (i = 0; i < list->count; i++) {
+        guint64 id = list->resources[i].id;
+
+        switch (list->resources[i].type) {
+        case SPICE_RES_TYPE_PIXMAP:
+            if (!cache_remove(c->images, id))
+                SPICE_DEBUG("fail to remove image %" G_GUINT64_FORMAT, id);
+            break;
+        default:
+            g_return_if_reached();
+            break;
+        }
+    }
+}
+
+/* coroutine context */
+static void display_handle_inv_pixmap_all(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
+
+    spice_channel_handle_wait_for_channels(channel, in);
+    cache_clear(c->images);
+}
+
+/* coroutine context */
+static void display_handle_inv_palette(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
+    SpiceMsgDisplayInvalOne* op = spice_msg_in_parsed(in);
+
+    palette_remove(&c->palette_cache, op->id);
+}
+
+/* coroutine context */
+static void display_handle_inv_palette_all(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
+
+    cache_clear(c->palettes);
+}
+
+/* ------------------------------------------------------------------ */
+
+static void display_update_stream_region(display_stream *st)
+{
+    int i;
+
+    switch (st->clip->type) {
+    case SPICE_CLIP_TYPE_RECTS:
+        region_clear(&st->region);
+        for (i = 0; i < st->clip->rects->num_rects; i++) {
+            region_add(&st->region, &st->clip->rects->rects[i]);
+        }
+        st->have_region = true;
+        break;
+    case SPICE_CLIP_TYPE_NONE:
+    default:
+        st->have_region = false;
+        break;
+    }
+}
+
+/* coroutine context */
+static void display_handle_stream_create(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
+    SpiceMsgDisplayStreamCreate *op = spice_msg_in_parsed(in);
+    display_stream *st;
+
+    CHANNEL_DEBUG(channel, "%s: id %d", __FUNCTION__, op->id);
+
+    if (op->id >= c->nstreams) {
+        int n = c->nstreams;
+        if (!c->nstreams) {
+            c->nstreams = 1;
+        }
+        while (op->id >= c->nstreams) {
+            c->nstreams *= 2;
+        }
+        c->streams = realloc(c->streams, c->nstreams * sizeof(c->streams[0]));
+        memset(c->streams + n, 0, (c->nstreams - n) * sizeof(c->streams[0]));
+    }
+    g_return_if_fail(c->streams[op->id] == NULL);
+    c->streams[op->id] = g_new0(display_stream, 1);
+    st = c->streams[op->id];
+
+    st->msg_create = in;
+    spice_msg_in_ref(in);
+    st->clip = &op->clip;
+    st->codec = op->codec_type;
+    st->surface = find_surface(c, op->surface_id);
+    st->msgq = g_queue_new();
+    st->channel = channel;
+    st->drops_seqs_stats_arr = g_array_new(FALSE, FALSE, sizeof(drops_sequence_stats));
+
+    region_init(&st->region);
+    display_update_stream_region(st);
+
+    switch (st->codec) {
+    case SPICE_VIDEO_CODEC_TYPE_MJPEG:
+        stream_mjpeg_init(st);
+        break;
+    }
+}
+
+/* coroutine or main context */
+static gboolean display_stream_schedule(display_stream *st)
+{
+    SpiceSession *session = spice_channel_get_session(st->channel);
+    guint32 time, d;
+    SpiceStreamDataHeader *op;
+    SpiceMsgIn *in;
+
+    SPICE_DEBUG("%s", __FUNCTION__);
+    if (st->timeout || !session)
+        return TRUE;
+
+    time = spice_session_get_mm_time(session);
+    in = g_queue_peek_head(st->msgq);
+
+    if (in == NULL) {
+        return TRUE;
+    }
+
+    op = spice_msg_in_parsed(in);
+    if (time < op->multi_media_time) {
+        d = op->multi_media_time - time;
+        SPICE_DEBUG("scheduling next stream render in %u ms", d);
+        st->timeout = g_timeout_add(d, (GSourceFunc)display_stream_render, st);
+        return TRUE;
+    } else {
+        SPICE_DEBUG("%s: rendering too late by %u ms (ts: %u, mmtime: %u), dropping ",
+                    __FUNCTION__, time - op->multi_media_time,
+                    op->multi_media_time, time);
+        in = g_queue_pop_head(st->msgq);
+        spice_msg_in_unref(in);
+        st->num_drops_on_playback++;
+        if (g_queue_get_length(st->msgq) == 0)
+            return TRUE;
+    }
+
+    return FALSE;
+}
+
+static SpiceRect *stream_get_dest(display_stream *st)
+{
+    if (st->msg_data == NULL ||
+        spice_msg_in_type(st->msg_data) != SPICE_MSG_DISPLAY_STREAM_DATA_SIZED) {
+        SpiceMsgDisplayStreamCreate *info = spice_msg_in_parsed(st->msg_create);
+
+        return &info->dest;
+    } else {
+        SpiceMsgDisplayStreamDataSized *op = spice_msg_in_parsed(st->msg_data);
+
+        return &op->dest;
+   }
+
+}
+
+static uint32_t stream_get_flags(display_stream *st)
+{
+    SpiceMsgDisplayStreamCreate *info = spice_msg_in_parsed(st->msg_create);
+
+    return info->flags;
+}
+
+G_GNUC_INTERNAL
+uint32_t stream_get_current_frame(display_stream *st, uint8_t **data)
+{
+    if (st->msg_data == NULL) {
+        *data = NULL;
+        return 0;
+    }
+
+    if (spice_msg_in_type(st->msg_data) == SPICE_MSG_DISPLAY_STREAM_DATA) {
+        SpiceMsgDisplayStreamData *op = spice_msg_in_parsed(st->msg_data);
+
+        *data = op->data;
+        return op->data_size;
+    } else {
+        SpiceMsgDisplayStreamDataSized *op = spice_msg_in_parsed(st->msg_data);
+
+        g_return_val_if_fail(spice_msg_in_type(st->msg_data) ==
+                             SPICE_MSG_DISPLAY_STREAM_DATA_SIZED, 0);
+        *data = op->data;
+        return op->data_size;
+   }
+
+}
+
+G_GNUC_INTERNAL
+void stream_get_dimensions(display_stream *st, int *width, int *height)
+{
+    g_return_if_fail(width != NULL);
+    g_return_if_fail(height != NULL);
+
+    if (st->msg_data == NULL ||
+        spice_msg_in_type(st->msg_data) != SPICE_MSG_DISPLAY_STREAM_DATA_SIZED) {
+        SpiceMsgDisplayStreamCreate *info = spice_msg_in_parsed(st->msg_create);
+
+        *width = info->stream_width;
+        *height = info->stream_height;
+    } else {
+        SpiceMsgDisplayStreamDataSized *op = spice_msg_in_parsed(st->msg_data);
+
+        *width = op->width;
+        *height = op->height;
+   }
+}
+
+/* main context */
+static gboolean display_stream_render(display_stream *st)
+{
+    SpiceMsgIn *in;
+
+    st->timeout = 0;
+    do {
+        in = g_queue_pop_head(st->msgq);
+
+        g_return_val_if_fail(in != NULL, FALSE);
+
+        st->msg_data = in;
+        switch (st->codec) {
+        case SPICE_VIDEO_CODEC_TYPE_MJPEG:
+            stream_mjpeg_data(st);
+            break;
+        }
+
+        if (st->out_frame) {
+            int width;
+            int height;
+            SpiceRect *dest;
+            uint8_t *data;
+            int stride;
+
+            stream_get_dimensions(st, &width, &height);
+            dest = stream_get_dest(st);
+
+            data = st->out_frame;
+            stride = width * sizeof(uint32_t);
+            if (!(stream_get_flags(st) & SPICE_STREAM_FLAGS_TOP_DOWN)) {
+                data += stride * (height - 1);
+                stride = -stride;
+            }
+
+            st->surface->canvas->ops->put_image(
+                st->surface->canvas,
+#ifdef G_OS_WIN32
+                SPICE_DISPLAY_CHANNEL(st->channel)->priv->dc,
+#endif
+                dest, data,
+                width, height, stride,
+                st->have_region ? &st->region : NULL);
+
+            if (st->surface->primary)
+                g_signal_emit(st->channel, signals[SPICE_DISPLAY_INVALIDATE], 0,
+                    dest->left, dest->top,
+                    dest->right - dest->left,
+                    dest->bottom - dest->top);
+        }
+
+        st->msg_data = NULL;
+        spice_msg_in_unref(in);
+
+        in = g_queue_peek_head(st->msgq);
+        if (in == NULL)
+            break;
+
+        if (display_stream_schedule(st))
+            return FALSE;
+    } while (1);
+
+    return FALSE;
+}
+/* after a sequence of 3 drops, push a report to the server, even
+ * if the report window is bigger */
+#define STREAM_REPORT_DROP_SEQ_LEN_LIMIT 3
+
+static void display_update_stream_report(SpiceDisplayChannel *channel, uint32_t stream_id,
+                                         uint32_t frame_time, int32_t latency)
+{
+    display_stream *st = channel->priv->streams[stream_id];
+    guint64 now;
+
+    if (!st->report_is_active) {
+        return;
+    }
+    now = g_get_monotonic_time();
+
+    if (st->report_num_frames == 0) {
+        st->report_start_frame_time = frame_time;
+        st->report_start_time = now;
+    }
+    st->report_num_frames++;
+
+    if (latency < 0) { // drop
+        st->report_num_drops++;
+        st->report_drops_seq_len++;
+    } else {
+        st->report_drops_seq_len = 0;
+    }
+
+    if (st->report_num_frames >= st->report_max_window ||
+        now - st->report_start_time >= st->report_timeout ||
+        st->report_drops_seq_len >= STREAM_REPORT_DROP_SEQ_LEN_LIMIT) {
+        SpiceMsgcDisplayStreamReport report;
+        SpiceSession *session = spice_channel_get_session(SPICE_CHANNEL(channel));
+        SpiceMsgOut *msg;
+
+        report.stream_id = stream_id;
+        report.unique_id = st->report_id;
+        report.start_frame_mm_time = st->report_start_frame_time;
+        report.end_frame_mm_time = frame_time;
+        report.num_frames = st->report_num_frames;
+        report.num_drops = st-> report_num_drops;
+        report.last_frame_delay = latency;
+        if (spice_session_is_playback_active(session)) {
+            report.audio_delay = spice_session_get_playback_latency(session);
+        } else {
+            report.audio_delay = UINT_MAX;
+        }
+
+        msg = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_DISPLAY_STREAM_REPORT);
+        msg->marshallers->msgc_display_stream_report(msg->marshaller, &report);
+        spice_msg_out_send(msg);
+
+        st->report_start_time = 0;
+        st->report_start_frame_time = 0;
+        st->report_num_frames = 0;
+        st->report_num_drops = 0;
+        st->report_drops_seq_len = 0;
+    }
+}
+
+static void display_stream_reset_rendering_timer(display_stream *st)
+{
+    SPICE_DEBUG("%s", __FUNCTION__);
+    if (st->timeout != 0) {
+        g_source_remove(st->timeout);
+        st->timeout = 0;
+    }
+    while (!display_stream_schedule(st)) {
+    }
+}
+
+/*
+ * Migration can occur between 2 spice-servers with different mm-times.
+ * Then, the following cases can happen after migration completes:
+ * (We refer to src/dst-time as the mm-times on the src/dst servers):
+ *
+ * (case 1) Frames with time ~= dst-time arrive to the client before the
+ *          playback-channel updates the session's mm-time (i.e., the mm_time
+ *          of the session is still based on the src-time).
+ *     (a) If src-time < dst-time:
+ *         display_stream_schedule schedules the next rendering to
+ *         ~(dst-time - src-time) milliseconds from now.
+ *         Since we assume monotonic mm_time, display_stream_schedule,
+ *         returns immediately when a rendering timeout
+ *         has already been set, and doesn't update the timeout,
+ *         even after the mm_time is updated.
+ *         When src-time << dst-time, a significant video frames loss will occur.
+ *     (b) If src-time > dst-time
+ *         Frames will be dropped till the mm-time will be updated.
+ * (case 2) mm-time is synced with dst-time, but frames that were in the command
+ *         ring during migration still arrive (such frames hold src-time).
+ *    (a) If src-time < dst-time
+ *        The frames that hold src-time will be dropped, since their
+ *        mm_time < session-mm_time. But all the new frames that are generated in
+ *        the driver after migration, will be rendered appropriately.
+ *    (b) If src-time > dst-time
+ *        Similar consequences as in 1 (a)
+ * case 2 is less likely, since at takes at least 20 frames till the dst-server re-identifies
+ * the video stream and starts sending stream data
+ *
+ * display_session_mm_time_reset_cb handles case 1.a, and
+ * display_stream_test_frames_mm_time_reset handles case 2.b
+ */
+
+/* main context */
+static void display_session_mm_time_reset_cb(SpiceSession *session, gpointer data)
+{
+    SpiceChannel *channel = data;
+    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
+    guint i;
+
+    CHANNEL_DEBUG(channel, "%s", __FUNCTION__);
+
+    for (i = 0; i < c->nstreams; i++) {
+        display_stream *st;
+
+        if (c->streams[i] == NULL) {
+            continue;
+        }
+        SPICE_DEBUG("%s: stream-id %d", __FUNCTION__, i);
+        st = c->streams[i];
+        display_stream_reset_rendering_timer(st);
+    }
+}
+
+/* coroutine context */
+static void display_stream_test_frames_mm_time_reset(display_stream *st,
+                                                     SpiceMsgIn *new_frame_msg,
+                                                     guint32 mm_time)
+{
+    SpiceStreamDataHeader *tail_op, *new_op;
+    SpiceMsgIn *tail_msg;
+
+    SPICE_DEBUG("%s", __FUNCTION__);
+    g_return_if_fail(new_frame_msg != NULL);
+    tail_msg = g_queue_peek_tail(st->msgq);
+    if (!tail_msg) {
+        return;
+    }
+    tail_op = spice_msg_in_parsed(tail_msg);
+    new_op = spice_msg_in_parsed(new_frame_msg);
+
+    if (new_op->multi_media_time < tail_op->multi_media_time) {
+        SPICE_DEBUG("new-frame-time < tail-frame-time (%u < %u):"
+                    " reseting stream, id %d",
+                    new_op->multi_media_time,
+                    tail_op->multi_media_time,
+                    new_op->id);
+        g_queue_foreach(st->msgq, _msg_in_unref_func, NULL);
+        g_queue_clear(st->msgq);
+        display_stream_reset_rendering_timer(st);
+    }
+}
+
+#define STREAM_PLAYBACK_SYNC_DROP_SEQ_LEN_LIMIT 5
+
+/* coroutine context */
+static void display_handle_stream_data(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
+    SpiceStreamDataHeader *op = spice_msg_in_parsed(in);
+    display_stream *st;
+    guint32 mmtime;
+    int32_t latency;
+
+    g_return_if_fail(c != NULL);
+    g_return_if_fail(c->streams != NULL);
+    g_return_if_fail(c->nstreams > op->id);
+
+    st =  c->streams[op->id];
+    mmtime = spice_session_get_mm_time(spice_channel_get_session(channel));
+
+    if (spice_msg_in_type(in) == SPICE_MSG_DISPLAY_STREAM_DATA_SIZED) {
+        CHANNEL_DEBUG(channel, "stream %d contains sized data", op->id);
+    }
+
+    if (op->multi_media_time == 0) {
+        g_critical("Received frame with invalid 0 timestamp! perhaps wrong graphic driver?");
+        op->multi_media_time = mmtime + 100; /* workaround... */
+    }
+
+    if (!st->num_input_frames) {
+        st->first_frame_mm_time = op->multi_media_time;
+    }
+    st->num_input_frames++;
+
+    latency = op->multi_media_time - mmtime;
+    if (latency < 0) {
+        CHANNEL_DEBUG(channel, "stream data too late by %u ms (ts: %u, mmtime: %u), dropping",
+                      mmtime - op->multi_media_time, op->multi_media_time, mmtime);
+        st->arrive_late_time += mmtime - op->multi_media_time;
+        st->num_drops_on_receive++;
+
+        if (!st->cur_drops_seq_stats.len) {
+            st->cur_drops_seq_stats.start_mm_time = op->multi_media_time;
+        }
+        st->cur_drops_seq_stats.len++;
+        st->playback_sync_drops_seq_len++;
+    } else {
+        CHANNEL_DEBUG(channel, "video latency: %d", latency);
+        spice_msg_in_ref(in);
+        display_stream_test_frames_mm_time_reset(st, in, mmtime);
+        g_queue_push_tail(st->msgq, in);
+        while (!display_stream_schedule(st)) {
+        }
+        if (st->cur_drops_seq_stats.len) {
+            st->cur_drops_seq_stats.duration = op->multi_media_time -
+                                               st->cur_drops_seq_stats.start_mm_time;
+            g_array_append_val(st->drops_seqs_stats_arr, st->cur_drops_seq_stats);
+            memset(&st->cur_drops_seq_stats, 0, sizeof(st->cur_drops_seq_stats));
+            st->num_drops_seqs++;
+        }
+        st->playback_sync_drops_seq_len = 0;
+    }
+    if (c->enable_adaptive_streaming) {
+        display_update_stream_report(SPICE_DISPLAY_CHANNEL(channel), op->id,
+                                     op->multi_media_time, latency);
+        if (st->playback_sync_drops_seq_len >= STREAM_PLAYBACK_SYNC_DROP_SEQ_LEN_LIMIT) {
+            spice_session_sync_playback_latency(spice_channel_get_session(channel));
+            st->playback_sync_drops_seq_len = 0;
+        }
+    }
+}
+
+/* coroutine context */
+static void display_handle_stream_clip(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
+    SpiceMsgDisplayStreamClip *op = spice_msg_in_parsed(in);
+    display_stream *st;
+
+    g_return_if_fail(c != NULL);
+    g_return_if_fail(c->streams != NULL);
+    g_return_if_fail(c->nstreams > op->id);
+
+    st = c->streams[op->id];
+
+    if (st->msg_clip) {
+        spice_msg_in_unref(st->msg_clip);
+    }
+    spice_msg_in_ref(in);
+    st->msg_clip = in;
+    st->clip = &op->clip;
+    display_update_stream_region(st);
+}
+
+static void _msg_in_unref_func(gpointer data, gpointer user_data)
+{
+    spice_msg_in_unref(data);
+}
+
+static void destroy_stream(SpiceChannel *channel, int id)
+{
+    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
+    display_stream *st;
+    guint64 drops_duration_total = 0;
+    guint32 num_out_frames;
+    int i;
+
+    g_return_if_fail(c != NULL);
+    g_return_if_fail(c->streams != NULL);
+    g_return_if_fail(c->nstreams > id);
+
+    st = c->streams[id];
+    if (!st)
+        return;
+
+    num_out_frames = st->num_input_frames - st->num_drops_on_receive - st->num_drops_on_playback;
+    CHANNEL_DEBUG(channel, "%s: id=%d #in-frames=%d out/in=%.2f "
+        "#drops-on-receive=%d avg-late-time(ms)=%.2f "
+        "#drops-on-playback=%d", __FUNCTION__,
+        id,
+        st->num_input_frames,
+        num_out_frames / (double)st->num_input_frames,
+        st->num_drops_on_receive,
+        st->num_drops_on_receive ? st->arrive_late_time / ((double)st->num_drops_on_receive): 0,
+        st->num_drops_on_playback);
+    if (st->num_drops_seqs) {
+        CHANNEL_DEBUG(channel, "%s: #drops-sequences=%u ==>", __FUNCTION__, st->num_drops_seqs);
+    }
+    for (i = 0; i < st->num_drops_seqs; i++) {
+            drops_sequence_stats *stats = &g_array_index(st->drops_seqs_stats_arr,
+                                                         drops_sequence_stats,
+                                                         i);
+            drops_duration_total += stats->duration;
+            CHANNEL_DEBUG(channel, "%s: \t len=%u start-ms=%u duration-ms=%u", __FUNCTION__,
+                                   stats->len,
+                                   stats->start_mm_time - st->first_frame_mm_time,
+                                   stats->duration);
+    }
+    if (st->num_drops_seqs) {
+        CHANNEL_DEBUG(channel, "%s: drops-total-duration=%"G_GUINT64_FORMAT" ==>", __FUNCTION__, drops_duration_total);
+    }
+
+    g_array_free(st->drops_seqs_stats_arr, TRUE);
+
+    switch (st->codec) {
+    case SPICE_VIDEO_CODEC_TYPE_MJPEG:
+        stream_mjpeg_cleanup(st);
+        break;
+    }
+
+    if (st->msg_clip)
+        spice_msg_in_unref(st->msg_clip);
+    spice_msg_in_unref(st->msg_create);
+
+    g_queue_foreach(st->msgq, _msg_in_unref_func, NULL);
+    g_queue_free(st->msgq);
+    if (st->timeout != 0)
+        g_source_remove(st->timeout);
+    g_free(st);
+    c->streams[id] = NULL;
+}
+
+static void clear_streams(SpiceChannel *channel)
+{
+    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
+    int i;
+
+    for (i = 0; i < c->nstreams; i++) {
+        destroy_stream(channel, i);
+    }
+    g_free(c->streams);
+    c->streams = NULL;
+    c->nstreams = 0;
+}
+
+/* coroutine context */
+static void display_handle_stream_destroy(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceMsgDisplayStreamDestroy *op = spice_msg_in_parsed(in);
+
+    g_return_if_fail(op != NULL);
+    CHANNEL_DEBUG(channel, "%s: id %d", __FUNCTION__, op->id);
+    destroy_stream(channel, op->id);
+}
+
+/* coroutine context */
+static void display_handle_stream_destroy_all(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    clear_streams(channel);
+}
+
+/* coroutine context */
+static void display_handle_stream_activate_report(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
+    SpiceMsgDisplayStreamActivateReport *op = spice_msg_in_parsed(in);
+    display_stream *st;
+
+    g_return_if_fail(c != NULL);
+    g_return_if_fail(c->streams != NULL);
+    g_return_if_fail(c->nstreams > op->stream_id);
+
+    st = c->streams[op->stream_id];
+    g_return_if_fail(st != NULL);
+
+    st->report_is_active = TRUE;
+    st->report_id = op->unique_id;
+    st->report_max_window = op->max_window_size;
+    st->report_timeout = op->timeout_ms * 1000;
+    st->report_start_time = 0;
+    st->report_start_frame_time = 0;
+    st->report_num_frames = 0;
+    st->report_num_drops = 0;
+    st->report_drops_seq_len = 0;
+}
+
+/* ------------------------------------------------------------------ */
+
+/* coroutine context */
+static void display_handle_draw_fill(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceMsgDisplayDrawFill *op = spice_msg_in_parsed(in);
+    DRAW(fill);
+}
+
+/* coroutine context */
+static void display_handle_draw_opaque(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceMsgDisplayDrawOpaque *op = spice_msg_in_parsed(in);
+    DRAW(opaque);
+}
+
+/* coroutine context */
+static void display_handle_draw_copy(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceMsgDisplayDrawCopy *op = spice_msg_in_parsed(in);
+    DRAW(copy);
+}
+
+/* coroutine context */
+static void display_handle_draw_blend(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceMsgDisplayDrawBlend *op = spice_msg_in_parsed(in);
+    DRAW(blend);
+}
+
+/* coroutine context */
+static void display_handle_draw_blackness(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceMsgDisplayDrawBlackness *op = spice_msg_in_parsed(in);
+    DRAW(blackness);
+}
+
+static void display_handle_draw_whiteness(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceMsgDisplayDrawWhiteness *op = spice_msg_in_parsed(in);
+    DRAW(whiteness);
+}
+
+/* coroutine context */
+static void display_handle_draw_invers(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceMsgDisplayDrawInvers *op = spice_msg_in_parsed(in);
+    DRAW(invers);
+}
+
+/* coroutine context */
+static void display_handle_draw_rop3(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceMsgDisplayDrawRop3 *op = spice_msg_in_parsed(in);
+    DRAW(rop3);
+}
+
+/* coroutine context */
+static void display_handle_draw_stroke(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceMsgDisplayDrawStroke *op = spice_msg_in_parsed(in);
+    DRAW(stroke);
+}
+
+/* coroutine context */
+static void display_handle_draw_text(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceMsgDisplayDrawText *op = spice_msg_in_parsed(in);
+    DRAW(text);
+}
+
+/* coroutine context */
+static void display_handle_draw_transparent(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceMsgDisplayDrawTransparent *op = spice_msg_in_parsed(in);
+    DRAW(transparent);
+}
+
+/* coroutine context */
+static void display_handle_draw_alpha_blend(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceMsgDisplayDrawAlphaBlend *op = spice_msg_in_parsed(in);
+    DRAW(alpha_blend);
+}
+
+/* coroutine context */
+static void display_handle_draw_composite(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceMsgDisplayDrawComposite *op = spice_msg_in_parsed(in);
+    DRAW(composite);
+}
+
+/* coroutine context */
+static void display_handle_surface_create(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
+    SpiceMsgSurfaceCreate *create = spice_msg_in_parsed(in);
+    display_surface *surface = g_slice_new0(display_surface);
+
+    surface->surface_id = create->surface_id;
+    surface->format = create->format;
+    surface->width  = create->width;
+    surface->height = create->height;
+    surface->stride = create->width * 4;
+    surface->size   = surface->height * surface->stride;
+
+    if (create->flags & SPICE_SURFACE_FLAGS_PRIMARY) {
+        SPICE_DEBUG("primary flags: %d", create->flags);
+        surface->primary = true;
+        create_canvas(channel, surface);
+        if (c->mark_false_event_id != 0) {
+            g_source_remove(c->mark_false_event_id);
+            c->mark_false_event_id = FALSE;
+        }
+    } else {
+        surface->primary = false;
+        create_canvas(channel, surface);
+    }
+}
+
+static gboolean display_mark_false(gpointer data)
+{
+    SpiceChannel *channel = data;
+    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
+
+    c->mark = FALSE;
+    g_signal_emit(channel, signals[SPICE_DISPLAY_MARK], 0, FALSE);
+
+    c->mark_false_event_id = 0;
+    return FALSE;
+}
+
+/* coroutine context */
+static void display_handle_surface_destroy(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceMsgSurfaceDestroy *destroy = spice_msg_in_parsed(in);
+    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
+    display_surface *surface;
+
+    g_return_if_fail(destroy != NULL);
+
+    surface = find_surface(c, destroy->surface_id);
+    if (surface == NULL) {
+        /* this is not a problem in spicec, it happens as well and returns.. */
+        /* g_warn_if_reached(); */
+        return;
+    }
+    if (surface->primary) {
+        int id = spice_channel_get_channel_id(channel);
+        CHANNEL_DEBUG(channel, "%d: FIXME primary destroy, but is display really disabled?", id);
+        /* this is done with a timeout in spicec as well, it's *ugly* */
+        if (id != 0 && c->mark_false_event_id == 0) {
+            c->mark_false_event_id = g_timeout_add_seconds(1, display_mark_false, channel);
+        }
+        c->primary = NULL;
+        g_coroutine_signal_emit(channel, signals[SPICE_DISPLAY_PRIMARY_DESTROY], 0);
+    }
+
+    g_hash_table_remove(c->surfaces, GINT_TO_POINTER(surface->surface_id));
+}
+
+#define CLAMP_CHECK(x, low, high)  (((x) > (high)) ? TRUE : (((x) < (low)) ? TRUE : FALSE))
+
+/* coroutine context */
+static void display_handle_monitors_config(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceMsgDisplayMonitorsConfig *config = spice_msg_in_parsed(in);
+    SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
+    guint i;
+
+    g_return_if_fail(config != NULL);
+    g_return_if_fail(config->count > 0);
+
+    CHANNEL_DEBUG(channel, "monitors config: n: %d/%d", config->count, config->max_allowed);
+
+    c->monitors_max = config->max_allowed;
+    if (CLAMP_CHECK(c->monitors_max, 1, MONITORS_MAX)) {
+        g_warning("MonitorConfig max_allowed is not within permitted range, clamping");
+        c->monitors_max = CLAMP(c->monitors_max, 1, MONITORS_MAX);
+    }
+
+    if (CLAMP_CHECK(config->count, 1, c->monitors_max)) {
+        g_warning("MonitorConfig count is not within permitted range, clamping");
+        config->count = CLAMP(config->count, 1, c->monitors_max);
+    }
+
+    c->monitors = g_array_set_size(c->monitors, config->count);
+
+    for (i = 0; i < config->count; i++) {
+        SpiceDisplayMonitorConfig *mc = &g_array_index(c->monitors, SpiceDisplayMonitorConfig, i);
+        SpiceHead *head = &config->heads[i];
+        CHANNEL_DEBUG(channel, "monitor id: %u, surface id: %u, +%u+%u-%ux%u",
+                    head->id, head->surface_id,
+                    head->x, head->y, head->width, head->height);
+        mc->id = head->id;
+        mc->surface_id = head->surface_id;
+        mc->x = head->x;
+        mc->y = head->y;
+        mc->width = head->width;
+        mc->height = head->height;
+    }
+
+    g_coroutine_object_notify(G_OBJECT(channel), "monitors");
+}
+
+static void channel_set_handlers(SpiceChannelClass *klass)
+{
+    static const spice_msg_handler handlers[] = {
+        [ SPICE_MSG_DISPLAY_MODE ]               = display_handle_mode,
+        [ SPICE_MSG_DISPLAY_MARK ]               = display_handle_mark,
+        [ SPICE_MSG_DISPLAY_RESET ]              = display_handle_reset,
+        [ SPICE_MSG_DISPLAY_COPY_BITS ]          = display_handle_copy_bits,
+        [ SPICE_MSG_DISPLAY_INVAL_LIST ]         = display_handle_inv_list,
+        [ SPICE_MSG_DISPLAY_INVAL_ALL_PIXMAPS ]  = display_handle_inv_pixmap_all,
+        [ SPICE_MSG_DISPLAY_INVAL_PALETTE ]      = display_handle_inv_palette,
+        [ SPICE_MSG_DISPLAY_INVAL_ALL_PALETTES ] = display_handle_inv_palette_all,
+
+        [ SPICE_MSG_DISPLAY_STREAM_CREATE ]      = display_handle_stream_create,
+        [ SPICE_MSG_DISPLAY_STREAM_DATA ]        = display_handle_stream_data,
+        [ SPICE_MSG_DISPLAY_STREAM_CLIP ]        = display_handle_stream_clip,
+        [ SPICE_MSG_DISPLAY_STREAM_DESTROY ]     = display_handle_stream_destroy,
+        [ SPICE_MSG_DISPLAY_STREAM_DESTROY_ALL ] = display_handle_stream_destroy_all,
+        [ SPICE_MSG_DISPLAY_STREAM_DATA_SIZED ]  = display_handle_stream_data,
+        [ SPICE_MSG_DISPLAY_STREAM_ACTIVATE_REPORT ] = display_handle_stream_activate_report,
+
+        [ SPICE_MSG_DISPLAY_DRAW_FILL ]          = display_handle_draw_fill,
+        [ SPICE_MSG_DISPLAY_DRAW_OPAQUE ]        = display_handle_draw_opaque,
+        [ SPICE_MSG_DISPLAY_DRAW_COPY ]          = display_handle_draw_copy,
+        [ SPICE_MSG_DISPLAY_DRAW_BLEND ]         = display_handle_draw_blend,
+        [ SPICE_MSG_DISPLAY_DRAW_BLACKNESS ]     = display_handle_draw_blackness,
+        [ SPICE_MSG_DISPLAY_DRAW_WHITENESS ]     = display_handle_draw_whiteness,
+        [ SPICE_MSG_DISPLAY_DRAW_INVERS ]        = display_handle_draw_invers,
+        [ SPICE_MSG_DISPLAY_DRAW_ROP3 ]          = display_handle_draw_rop3,
+        [ SPICE_MSG_DISPLAY_DRAW_STROKE ]        = display_handle_draw_stroke,
+        [ SPICE_MSG_DISPLAY_DRAW_TEXT ]          = display_handle_draw_text,
+        [ SPICE_MSG_DISPLAY_DRAW_TRANSPARENT ]   = display_handle_draw_transparent,
+        [ SPICE_MSG_DISPLAY_DRAW_ALPHA_BLEND ]   = display_handle_draw_alpha_blend,
+        [ SPICE_MSG_DISPLAY_DRAW_COMPOSITE ]     = display_handle_draw_composite,
+
+        [ SPICE_MSG_DISPLAY_SURFACE_CREATE ]     = display_handle_surface_create,
+        [ SPICE_MSG_DISPLAY_SURFACE_DESTROY ]    = display_handle_surface_destroy,
+
+        [ SPICE_MSG_DISPLAY_MONITORS_CONFIG ]    = display_handle_monitors_config,
+    };
+
+    spice_channel_set_handlers(klass, handlers, G_N_ELEMENTS(handlers));
+}
diff --git a/src/channel-display.h b/src/channel-display.h
new file mode 100644
index 0000000..88e60d9
--- /dev/null
+++ b/src/channel-display.h
@@ -0,0 +1,102 @@
+/* -*- 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_CLIENT_DISPLAY_CHANNEL_H__
+#define __SPICE_CLIENT_DISPLAY_CHANNEL_H__
+
+#include "spice-client.h"
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_DISPLAY_CHANNEL            (spice_display_channel_get_type())
+#define SPICE_DISPLAY_CHANNEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), SPICE_TYPE_DISPLAY_CHANNEL, SpiceDisplayChannel))
+#define SPICE_DISPLAY_CHANNEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), SPICE_TYPE_DISPLAY_CHANNEL, SpiceDisplayChannelClass))
+#define SPICE_IS_DISPLAY_CHANNEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), SPICE_TYPE_DISPLAY_CHANNEL))
+#define SPICE_IS_DISPLAY_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SPICE_TYPE_DISPLAY_CHANNEL))
+#define SPICE_DISPLAY_CHANNEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), SPICE_TYPE_DISPLAY_CHANNEL, SpiceDisplayChannelClass))
+
+typedef struct _SpiceDisplayChannel SpiceDisplayChannel;
+typedef struct _SpiceDisplayChannelClass SpiceDisplayChannelClass;
+typedef struct _SpiceDisplayChannelPrivate SpiceDisplayChannelPrivate;
+
+typedef struct _SpiceDisplayMonitorConfig SpiceDisplayMonitorConfig;
+struct _SpiceDisplayMonitorConfig {
+    guint id;
+    guint surface_id;
+    guint x;
+    guint y;
+    guint width;
+    guint height;
+};
+
+typedef struct _SpiceDisplayPrimary SpiceDisplayPrimary;
+struct _SpiceDisplayPrimary {
+    enum SpiceSurfaceFmt format;
+    gint width;
+    gint height;
+    gint stride;
+    gint shmid;
+    guint8 *data;
+    gboolean marked;
+};
+
+/**
+ * SpiceDisplayChannel:
+ *
+ * The #SpiceDisplayChannel struct is opaque and should not be accessed directly.
+ */
+struct _SpiceDisplayChannel {
+    SpiceChannel parent;
+
+    /*< private >*/
+    SpiceDisplayChannelPrivate *priv;
+    /* Do not add fields to this struct */
+};
+
+/**
+ * SpiceDisplayChannelClass:
+ * @parent_class: Parent class.
+ * @display_primary_create: Signal class handler for the #SpiceDisplayChannel::display-primary-create signal.
+ * @display_primary_destroy: Signal class handler for the #SpiceDisplayChannel::display-primary-destroy signal.
+ * @display_invalidate: Signal class handler for the #SpiceDisplayChannel::display-invalidate signal.
+ * @display_mark: Signal class handler for the #SpiceDisplayChannel::display-mark signal.
+ *
+ * Class structure for #SpiceDisplayChannel.
+ */
+struct _SpiceDisplayChannelClass {
+    SpiceChannelClass parent_class;
+
+    /* signals */
+    void (*display_primary_create)(SpiceChannel *channel, gint format,
+                                   gint width, gint height, gint stride,
+                                   gint shmid, gpointer data);
+    void (*display_primary_destroy)(SpiceChannel *channel);
+    void (*display_invalidate)(SpiceChannel *channel,
+                               gint x, gint y, gint w, gint h);
+    void (*display_mark)(SpiceChannel *channel,
+                         gboolean mark);
+
+    /*< private >*/
+};
+
+GType	        spice_display_channel_get_type(void);
+gboolean        spice_display_get_primary(SpiceChannel *channel, guint32 surface_id,
+                                          SpiceDisplayPrimary *primary);
+
+G_END_DECLS
+
+#endif /* __SPICE_CLIENT_DISPLAY_CHANNEL_H__ */
diff --git a/src/channel-inputs.c b/src/channel-inputs.c
new file mode 100644
index 0000000..df1ffe1
--- /dev/null
+++ b/src/channel-inputs.c
@@ -0,0 +1,603 @@
+/* -*- 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-channel-priv.h"
+
+/**
+ * SECTION:channel-inputs
+ * @short_description: control the server mouse and keyboard
+ * @title: Inputs Channel
+ * @section_id:
+ * @see_also: #SpiceChannel, and the GTK widget #SpiceDisplay
+ * @stability: Stable
+ * @include: channel-inputs.h
+ *
+ * Spice supports sending keyboard key events and keyboard leds
+ * synchronization. The key events are sent using
+ * spice_inputs_key_press() and spice_inputs_key_release() using
+ * a modified variant of PC XT scancodes.
+ *
+ * Guest keyboard leds state can be manipulated with
+ * spice_inputs_set_key_locks(). When key lock change, a notification
+ * is emitted with #SpiceInputsChannel::inputs-modifiers signal.
+ */
+
+#define SPICE_INPUTS_CHANNEL_GET_PRIVATE(obj)                                  \
+    (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_INPUTS_CHANNEL, SpiceInputsChannelPrivate))
+
+struct _SpiceInputsChannelPrivate {
+    int                         bs;
+    int                         dx, dy;
+    unsigned int                x, y, dpy;
+    int                         motion_count;
+    int                         modifiers;
+    guint32                     locks;
+};
+
+G_DEFINE_TYPE(SpiceInputsChannel, spice_inputs_channel, SPICE_TYPE_CHANNEL)
+
+/* Properties */
+enum {
+    PROP_0,
+    PROP_KEY_MODIFIERS,
+};
+
+/* Signals */
+enum {
+    SPICE_INPUTS_MODIFIERS,
+
+    SPICE_INPUTS_LAST_SIGNAL,
+};
+
+static guint signals[SPICE_INPUTS_LAST_SIGNAL];
+
+static void spice_inputs_channel_up(SpiceChannel *channel);
+static void spice_inputs_channel_reset(SpiceChannel *channel, gboolean migrating);
+static void channel_set_handlers(SpiceChannelClass *klass);
+
+/* ------------------------------------------------------------------ */
+
+static void spice_inputs_channel_init(SpiceInputsChannel *channel)
+{
+    channel->priv = SPICE_INPUTS_CHANNEL_GET_PRIVATE(channel);
+}
+
+static void spice_inputs_get_property(GObject    *object,
+                                      guint       prop_id,
+                                      GValue     *value,
+                                      GParamSpec *pspec)
+{
+    SpiceInputsChannelPrivate *c = SPICE_INPUTS_CHANNEL(object)->priv;
+
+    switch (prop_id) {
+    case PROP_KEY_MODIFIERS:
+        g_value_set_int(value, c->modifiers);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void spice_inputs_channel_finalize(GObject *obj)
+{
+    if (G_OBJECT_CLASS(spice_inputs_channel_parent_class)->finalize)
+        G_OBJECT_CLASS(spice_inputs_channel_parent_class)->finalize(obj);
+}
+
+static void spice_inputs_channel_class_init(SpiceInputsChannelClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+    SpiceChannelClass *channel_class = SPICE_CHANNEL_CLASS(klass);
+
+    gobject_class->finalize     = spice_inputs_channel_finalize;
+    gobject_class->get_property = spice_inputs_get_property;
+    channel_class->channel_up   = spice_inputs_channel_up;
+    channel_class->channel_reset = spice_inputs_channel_reset;
+
+    g_object_class_install_property
+        (gobject_class, PROP_KEY_MODIFIERS,
+         g_param_spec_int("key-modifiers",
+                          "Key modifiers",
+                          "Guest keyboard lock/led state",
+                          0, INT_MAX, 0,
+                          G_PARAM_READABLE |
+                          G_PARAM_STATIC_NAME |
+                          G_PARAM_STATIC_NICK |
+                          G_PARAM_STATIC_BLURB));
+
+    /**
+     * SpiceInputsChannel::inputs-modifier:
+     * @display: the #SpiceInputsChannel that emitted the signal
+     *
+     * The #SpiceInputsChannel::inputs-modifier signal is emitted when
+     * the guest keyboard locks are changed. You can read the current
+     * state from #SpiceInputsChannel:key-modifiers property.
+     **/
+    /* TODO: use notify instead? */
+    signals[SPICE_INPUTS_MODIFIERS] =
+        g_signal_new("inputs-modifiers",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_FIRST,
+                     G_STRUCT_OFFSET(SpiceInputsChannelClass, inputs_modifiers),
+                     NULL, NULL,
+                     g_cclosure_marshal_VOID__VOID,
+                     G_TYPE_NONE,
+                     0);
+
+    g_type_class_add_private(klass, sizeof(SpiceInputsChannelPrivate));
+    channel_set_handlers(SPICE_CHANNEL_CLASS(klass));
+}
+
+/* ------------------------------------------------------------------ */
+
+static SpiceMsgOut* mouse_motion(SpiceInputsChannel *channel)
+{
+    SpiceInputsChannelPrivate *c = channel->priv;
+    SpiceMsgcMouseMotion motion;
+    SpiceMsgOut *msg;
+
+    if (!c->dx && !c->dy)
+        return NULL;
+
+    motion.buttons_state = c->bs;
+    motion.dx            = c->dx;
+    motion.dy            = c->dy;
+    msg = spice_msg_out_new(SPICE_CHANNEL(channel),
+                            SPICE_MSGC_INPUTS_MOUSE_MOTION);
+    msg->marshallers->msgc_inputs_mouse_motion(msg->marshaller, &motion);
+
+    c->motion_count++;
+    c->dx = 0;
+    c->dy = 0;
+
+    return msg;
+}
+
+static SpiceMsgOut* mouse_position(SpiceInputsChannel *channel)
+{
+    SpiceInputsChannelPrivate *c = channel->priv;
+    SpiceMsgcMousePosition position;
+    SpiceMsgOut *msg;
+
+    if (c->dpy == -1)
+        return NULL;
+
+    /* CHANNEL_DEBUG(channel, "%s: +%d+%d", __FUNCTION__, c->x, c->y); */
+    position.buttons_state = c->bs;
+    position.x             = c->x;
+    position.y             = c->y;
+    position.display_id    = c->dpy;
+    msg = spice_msg_out_new(SPICE_CHANNEL(channel),
+                            SPICE_MSGC_INPUTS_MOUSE_POSITION);
+    msg->marshallers->msgc_inputs_mouse_position(msg->marshaller, &position);
+
+    c->motion_count++;
+    c->dpy = -1;
+
+    return msg;
+}
+
+/* main context */
+static void send_position(SpiceInputsChannel *channel)
+{
+    SpiceMsgOut *msg;
+
+    if (spice_channel_get_read_only(SPICE_CHANNEL(channel)))
+        return;
+
+    msg = mouse_position(channel);
+    if (!msg) /* if no motion */
+        return;
+
+    spice_msg_out_send(msg);
+}
+
+/* main context */
+static void send_motion(SpiceInputsChannel *channel)
+{
+    SpiceMsgOut *msg;
+
+    if (spice_channel_get_read_only(SPICE_CHANNEL(channel)))
+        return;
+
+    msg = mouse_motion(channel);
+    if (!msg) /* if no motion */
+        return;
+
+    spice_msg_out_send(msg);
+}
+
+/* coroutine context */
+static void inputs_handle_init(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceInputsChannelPrivate *c = SPICE_INPUTS_CHANNEL(channel)->priv;
+    SpiceMsgInputsInit *init = spice_msg_in_parsed(in);
+
+    c->modifiers = init->keyboard_modifiers;
+    g_coroutine_signal_emit(channel, signals[SPICE_INPUTS_MODIFIERS], 0);
+}
+
+/* coroutine context */
+static void inputs_handle_modifiers(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceInputsChannelPrivate *c = SPICE_INPUTS_CHANNEL(channel)->priv;
+    SpiceMsgInputsKeyModifiers *modifiers = spice_msg_in_parsed(in);
+
+    c->modifiers = modifiers->modifiers;
+    g_coroutine_signal_emit(channel, signals[SPICE_INPUTS_MODIFIERS], 0);
+}
+
+/* coroutine context */
+static void inputs_handle_ack(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceInputsChannelPrivate *c = SPICE_INPUTS_CHANNEL(channel)->priv;
+    SpiceMsgOut *msg;
+
+    c->motion_count -= SPICE_INPUT_MOTION_ACK_BUNCH;
+
+    msg = mouse_motion(SPICE_INPUTS_CHANNEL(channel));
+    if (msg) { /* if no motion, msg == NULL */
+        spice_msg_out_send_internal(msg);
+    }
+
+    msg = mouse_position(SPICE_INPUTS_CHANNEL(channel));
+    if (msg) {
+        spice_msg_out_send_internal(msg);
+    }
+}
+
+static void channel_set_handlers(SpiceChannelClass *klass)
+{
+    static const spice_msg_handler handlers[] = {
+        [ SPICE_MSG_INPUTS_INIT ]              = inputs_handle_init,
+        [ SPICE_MSG_INPUTS_KEY_MODIFIERS ]     = inputs_handle_modifiers,
+        [ SPICE_MSG_INPUTS_MOUSE_MOTION_ACK ]  = inputs_handle_ack,
+    };
+
+    spice_channel_set_handlers(klass, handlers, G_N_ELEMENTS(handlers));
+}
+
+/**
+ * spice_inputs_motion:
+ * @channel:
+ * @dx: delta X mouse coordinates
+ * @dy: delta Y mouse coordinates
+ * @button_state: SPICE_MOUSE_BUTTON_MASK flags
+ *
+ * Change mouse position (used in SPICE_MOUSE_MODE_CLIENT).
+ **/
+void spice_inputs_motion(SpiceInputsChannel *channel, gint dx, gint dy,
+                         gint button_state)
+{
+    SpiceInputsChannelPrivate *c;
+
+    g_return_if_fail(channel != NULL);
+    g_return_if_fail(SPICE_CHANNEL(channel)->priv->state != SPICE_CHANNEL_STATE_UNCONNECTED);
+    if (SPICE_CHANNEL(channel)->priv->state != SPICE_CHANNEL_STATE_READY)
+        return;
+
+    if (dx == 0 && dy == 0)
+        return;
+
+    c = channel->priv;
+    c->bs  = button_state;
+    c->dx += dx;
+    c->dy += dy;
+
+    if (c->motion_count < SPICE_INPUT_MOTION_ACK_BUNCH * 2) {
+        send_motion(channel);
+    }
+}
+
+/**
+ * spice_inputs_position:
+ * @channel:
+ * @x: X mouse coordinates
+ * @y: Y mouse coordinates
+ * @display: display channel id
+ * @button_state: SPICE_MOUSE_BUTTON_MASK flags
+ *
+ * Change mouse position (used in SPICE_MOUSE_MODE_CLIENT).
+ **/
+void spice_inputs_position(SpiceInputsChannel *channel, gint x, gint y,
+                           gint display, gint button_state)
+{
+    SpiceInputsChannelPrivate *c;
+
+    g_return_if_fail(channel != NULL);
+
+    if (SPICE_CHANNEL(channel)->priv->state != SPICE_CHANNEL_STATE_READY)
+        return;
+
+    c = channel->priv;
+    c->bs  = button_state;
+    c->x   = x;
+    c->y   = y;
+    c->dpy = display;
+
+    if (c->motion_count < SPICE_INPUT_MOTION_ACK_BUNCH * 2) {
+        send_position(channel);
+    } else {
+        CHANNEL_DEBUG(channel, "over SPICE_INPUT_MOTION_ACK_BUNCH * 2, dropping");
+    }
+}
+
+/**
+ * spice_inputs_button_press:
+ * @channel:
+ * @button: a SPICE_MOUSE_BUTTON
+ * @button_state: SPICE_MOUSE_BUTTON_MASK flags
+ *
+ * Press a mouse button.
+ **/
+void spice_inputs_button_press(SpiceInputsChannel *channel, gint button,
+                               gint button_state)
+{
+    SpiceInputsChannelPrivate *c;
+    SpiceMsgcMousePress press;
+    SpiceMsgOut *msg;
+
+    g_return_if_fail(channel != NULL);
+
+    if (SPICE_CHANNEL(channel)->priv->state != SPICE_CHANNEL_STATE_READY)
+        return;
+    if (spice_channel_get_read_only(SPICE_CHANNEL(channel)))
+        return;
+
+    c = channel->priv;
+    switch (button) {
+    case SPICE_MOUSE_BUTTON_LEFT:
+        button_state |= SPICE_MOUSE_BUTTON_MASK_LEFT;
+        break;
+    case SPICE_MOUSE_BUTTON_MIDDLE:
+        button_state |= SPICE_MOUSE_BUTTON_MASK_MIDDLE;
+        break;
+    case SPICE_MOUSE_BUTTON_RIGHT:
+        button_state |= SPICE_MOUSE_BUTTON_MASK_RIGHT;
+        break;
+    }
+
+    c->bs  = button_state;
+    send_motion(channel);
+    send_position(channel);
+
+    msg = spice_msg_out_new(SPICE_CHANNEL(channel),
+                            SPICE_MSGC_INPUTS_MOUSE_PRESS);
+    press.button = button;
+    press.buttons_state = button_state;
+    msg->marshallers->msgc_inputs_mouse_press(msg->marshaller, &press);
+    spice_msg_out_send(msg);
+}
+
+/**
+ * spice_inputs_button_release:
+ * @channel:
+ * @button: a SPICE_MOUSE_BUTTON
+ * @button_state: SPICE_MOUSE_BUTTON_MASK flags
+ *
+ * Release a button.
+ **/
+void spice_inputs_button_release(SpiceInputsChannel *channel, gint button,
+                                 gint button_state)
+{
+    SpiceInputsChannelPrivate *c;
+    SpiceMsgcMouseRelease release;
+    SpiceMsgOut *msg;
+
+    g_return_if_fail(channel != NULL);
+
+    if (SPICE_CHANNEL(channel)->priv->state != SPICE_CHANNEL_STATE_READY)
+        return;
+    if (spice_channel_get_read_only(SPICE_CHANNEL(channel)))
+        return;
+
+    c = channel->priv;
+    switch (button) {
+    case SPICE_MOUSE_BUTTON_LEFT:
+        button_state &= ~SPICE_MOUSE_BUTTON_MASK_LEFT;
+        break;
+    case SPICE_MOUSE_BUTTON_MIDDLE:
+        button_state &= ~SPICE_MOUSE_BUTTON_MASK_MIDDLE;
+        break;
+    case SPICE_MOUSE_BUTTON_RIGHT:
+        button_state &= ~SPICE_MOUSE_BUTTON_MASK_RIGHT;
+        break;
+    }
+
+    c->bs = button_state;
+    send_motion(channel);
+    send_position(channel);
+
+    msg = spice_msg_out_new(SPICE_CHANNEL(channel),
+                            SPICE_MSGC_INPUTS_MOUSE_RELEASE);
+    release.button = button;
+    release.buttons_state = button_state;
+    msg->marshallers->msgc_inputs_mouse_release(msg->marshaller, &release);
+    spice_msg_out_send(msg);
+}
+
+/**
+ * spice_inputs_key_press:
+ * @channel:
+ * @scancode: a PC XT (set 1) key scancode.  For scancodes with an %0xe0
+ *            prefix, drop the prefix and OR the scancode with %0x100.
+ *
+ * Press a key.
+ **/
+void spice_inputs_key_press(SpiceInputsChannel *channel, guint scancode)
+{
+    SpiceMsgcKeyDown down;
+    SpiceMsgOut *msg;
+
+    g_return_if_fail(channel != NULL);
+    g_return_if_fail(SPICE_CHANNEL(channel)->priv->state != SPICE_CHANNEL_STATE_UNCONNECTED);
+    if (SPICE_CHANNEL(channel)->priv->state != SPICE_CHANNEL_STATE_READY)
+        return;
+    if (spice_channel_get_read_only(SPICE_CHANNEL(channel)))
+        return;
+
+    down.code = spice_make_scancode(scancode, FALSE);
+    msg = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_INPUTS_KEY_DOWN);
+    msg->marshallers->msgc_inputs_key_down(msg->marshaller, &down);
+    spice_msg_out_send(msg);
+}
+
+/**
+ * spice_inputs_key_release:
+ * @channel:
+ * @scancode: a PC XT (set 1) key scancode.  For scancodes with an %0xe0
+ *            prefix, drop the prefix and OR the scancode with %0x100.
+ *
+ * Release a key.
+ **/
+void spice_inputs_key_release(SpiceInputsChannel *channel, guint scancode)
+{
+    SpiceMsgcKeyUp up;
+    SpiceMsgOut *msg;
+
+    g_return_if_fail(channel != NULL);
+    g_return_if_fail(SPICE_CHANNEL(channel)->priv->state != SPICE_CHANNEL_STATE_UNCONNECTED);
+    if (SPICE_CHANNEL(channel)->priv->state != SPICE_CHANNEL_STATE_READY)
+        return;
+    if (spice_channel_get_read_only(SPICE_CHANNEL(channel)))
+        return;
+
+    up.code = spice_make_scancode(scancode, TRUE);
+    msg = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_INPUTS_KEY_UP);
+    msg->marshallers->msgc_inputs_key_up(msg->marshaller, &up);
+    spice_msg_out_send(msg);
+}
+
+/**
+ * spice_inputs_key_press_and_release:
+ * @channel:
+ * @scancode: a PC XT (set 1) key scancode.  For scancodes with an %0xe0
+ *            prefix, drop the prefix and OR the scancode with %0x100.
+ *
+ * Press and release a key event atomically (in the same message).
+ *
+ * Since: 0.13
+ **/
+void spice_inputs_key_press_and_release(SpiceInputsChannel *input_channel, guint scancode)
+{
+    SpiceChannel *channel = SPICE_CHANNEL(input_channel);
+
+    g_return_if_fail(channel != NULL);
+    g_return_if_fail(channel->priv->state != SPICE_CHANNEL_STATE_UNCONNECTED);
+
+    if (channel->priv->state != SPICE_CHANNEL_STATE_READY)
+        return;
+    if (spice_channel_get_read_only(channel))
+        return;
+
+    if (spice_channel_test_capability(channel, SPICE_INPUTS_CAP_KEY_SCANCODE)) {
+        SpiceMsgOut *msg;
+        guint16 code;
+        guint8 *buf;
+
+        msg = spice_msg_out_new(channel, SPICE_MSGC_INPUTS_KEY_SCANCODE);
+        if (scancode < 0x100) {
+            buf = (guint8*)spice_marshaller_reserve_space(msg->marshaller, 2);
+            buf[0] = spice_make_scancode(scancode, FALSE);
+            buf[1] = spice_make_scancode(scancode, TRUE);
+        } else {
+            buf = (guint8*)spice_marshaller_reserve_space(msg->marshaller, 4);
+            code = spice_make_scancode(scancode, FALSE);
+            buf[0] = code & 0xff;
+            buf[1] = code >> 8;
+            code = spice_make_scancode(scancode, TRUE);
+            buf[2] = code & 0xff;
+            buf[3] = code >> 8;
+        }
+        spice_msg_out_send(msg);
+    } else {
+        CHANNEL_DEBUG(channel, "The server doesn't support atomic press and release");
+        spice_inputs_key_press(input_channel, scancode);
+        spice_inputs_key_release(input_channel, scancode);
+    }
+}
+
+/* main or coroutine context */
+static SpiceMsgOut* set_key_locks(SpiceInputsChannel *channel, guint locks)
+{
+    SpiceMsgcKeyModifiers modifiers;
+    SpiceMsgOut *msg;
+    SpiceInputsChannelPrivate *ic;
+    SpiceChannelPrivate *c;
+
+    g_return_val_if_fail(SPICE_IS_INPUTS_CHANNEL(channel), NULL);
+
+    ic = channel->priv;
+    c = SPICE_CHANNEL(channel)->priv;
+
+    ic->locks = locks;
+    if (c->state != SPICE_CHANNEL_STATE_READY)
+        return NULL;
+
+    msg = spice_msg_out_new(SPICE_CHANNEL(channel),
+                            SPICE_MSGC_INPUTS_KEY_MODIFIERS);
+    modifiers.modifiers = locks;
+    msg->marshallers->msgc_inputs_key_modifiers(msg->marshaller, &modifiers);
+    return msg;
+}
+
+/**
+ * spice_inputs_set_key_locks:
+ * @channel:
+ * @locks: #SpiceInputsLock modifiers flags
+ *
+ * Set the keyboard locks on the guest (Caps, Num, Scroll..)
+ **/
+void spice_inputs_set_key_locks(SpiceInputsChannel *channel, guint locks)
+{
+    SpiceMsgOut *msg;
+
+    if (spice_channel_get_read_only(SPICE_CHANNEL(channel)))
+        return;
+
+    msg = set_key_locks(channel, locks);
+    if (!msg) /* you can set_key_locks() even if the channel is not ready */
+        return;
+
+    spice_msg_out_send(msg); /* main -> coroutine */
+}
+
+/* coroutine context */
+static void spice_inputs_channel_up(SpiceChannel *channel)
+{
+    SpiceInputsChannelPrivate *c = SPICE_INPUTS_CHANNEL(channel)->priv;
+    SpiceMsgOut *msg;
+
+    if (spice_channel_get_read_only(channel))
+        return;
+
+    msg = set_key_locks(SPICE_INPUTS_CHANNEL(channel), c->locks);
+    spice_msg_out_send_internal(msg);
+}
+
+static void spice_inputs_channel_reset(SpiceChannel *channel, gboolean migrating)
+{
+    SpiceInputsChannelPrivate *c = SPICE_INPUTS_CHANNEL(channel)->priv;
+    c->motion_count = 0;
+
+    SPICE_CHANNEL_CLASS(spice_inputs_channel_parent_class)->channel_reset(channel, migrating);
+}
diff --git a/src/channel-inputs.h b/src/channel-inputs.h
new file mode 100644
index 0000000..3179a76
--- /dev/null
+++ b/src/channel-inputs.h
@@ -0,0 +1,89 @@
+/* -*- 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_CLIENT_INPUTS_CHANNEL_H__
+#define __SPICE_CLIENT_INPUTS_CHANNEL_H__
+
+#include "spice-client.h"
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_INPUTS_CHANNEL            (spice_inputs_channel_get_type())
+#define SPICE_INPUTS_CHANNEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), SPICE_TYPE_INPUTS_CHANNEL, SpiceInputsChannel))
+#define SPICE_INPUTS_CHANNEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), SPICE_TYPE_INPUTS_CHANNEL, SpiceInputsChannelClass))
+#define SPICE_IS_INPUTS_CHANNEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), SPICE_TYPE_INPUTS_CHANNEL))
+#define SPICE_IS_INPUTS_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SPICE_TYPE_INPUTS_CHANNEL))
+#define SPICE_INPUTS_CHANNEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), SPICE_TYPE_INPUTS_CHANNEL, SpiceInputsChannelClass))
+
+typedef struct _SpiceInputsChannel SpiceInputsChannel;
+typedef struct _SpiceInputsChannelClass SpiceInputsChannelClass;
+typedef struct _SpiceInputsChannelPrivate SpiceInputsChannelPrivate;
+
+typedef enum {
+    SPICE_INPUTS_SCROLL_LOCK = (1 << 0),
+    SPICE_INPUTS_NUM_LOCK    = (1 << 1),
+    SPICE_INPUTS_CAPS_LOCK   = (1 << 2)
+} SpiceInputsLock;
+
+/**
+ * SpiceInputsChannel:
+ *
+ * The #SpiceInputsChannel struct is opaque and should not be accessed directly.
+ */
+struct _SpiceInputsChannel {
+    SpiceChannel parent;
+
+    /*< private >*/
+    SpiceInputsChannelPrivate *priv;
+    /* Do not add fields to this struct */
+};
+
+/**
+ * SpiceInputsChannelClass:
+ * @parent_class: Parent class.
+ * @inputs_modifiers: Signal class handler for the #SpiceInputsChannel::inputs-modifiers signal.
+ *
+ * Class structure for #SpiceInputsChannel.
+ */
+struct _SpiceInputsChannelClass {
+    SpiceChannelClass parent_class;
+
+    /* signals */
+    void (*inputs_modifiers)(SpiceChannel *channel);
+
+    /*< private >*/
+    /* Do not add fields to this struct */
+};
+
+GType spice_inputs_channel_get_type(void);
+
+void spice_inputs_motion(SpiceInputsChannel *channel, gint dx, gint dy,
+                         gint button_state);
+void spice_inputs_position(SpiceInputsChannel *channel, gint x, gint y,
+                           gint display, gint button_state);
+void spice_inputs_button_press(SpiceInputsChannel *channel, gint button,
+                               gint button_state);
+void spice_inputs_button_release(SpiceInputsChannel *channel, gint button,
+                                 gint button_state);
+void spice_inputs_key_press(SpiceInputsChannel *channel, guint scancode);
+void spice_inputs_key_release(SpiceInputsChannel *channel, guint scancode);
+void spice_inputs_set_key_locks(SpiceInputsChannel *channel, guint locks);
+void spice_inputs_key_press_and_release(SpiceInputsChannel *channel, guint scancode);
+
+G_END_DECLS
+
+#endif /* __SPICE_CLIENT_INPUTS_CHANNEL_H__ */
diff --git a/src/channel-main.c b/src/channel-main.c
new file mode 100644
index 0000000..c55d097
--- /dev/null
+++ b/src/channel-main.c
@@ -0,0 +1,2993 @@
+/* -*- 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 <math.h>
+#include <spice/vd_agent.h>
+#include <common/rect.h>
+#include <glib/gstdio.h>
+
+#include "glib-compat.h"
+#include "spice-client.h"
+#include "spice-common.h"
+#include "spice-marshal.h"
+
+#include "spice-util-priv.h"
+#include "spice-channel-priv.h"
+#include "spice-session-priv.h"
+#include "spice-audio-priv.h"
+
+/**
+ * SECTION:channel-main
+ * @short_description: the main Spice channel
+ * @title: Main Channel
+ * @section_id:
+ * @see_also: #SpiceChannel, and the GTK widget #SpiceDisplay
+ * @stability: Stable
+ * @include: channel-main.h
+ *
+ * The main channel is the Spice session control channel. It handles
+ * communication initialization (channels list), migrations, mouse
+ * modes, multimedia time, and agent communication.
+ *
+ *
+ */
+
+#define SPICE_MAIN_CHANNEL_GET_PRIVATE(obj)                             \
+    (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_MAIN_CHANNEL, SpiceMainChannelPrivate))
+
+#define MAX_DISPLAY 16 /* Note must fit in a guint32, see monitors_align */
+
+typedef struct spice_migrate spice_migrate;
+
+#define FILE_XFER_CHUNK_SIZE (VD_AGENT_MAX_DATA_SIZE * 32)
+typedef struct SpiceFileXferTask {
+    uint32_t                       id;
+    gboolean                       pending;
+    GFile                          *file;
+    SpiceMainChannel               *channel;
+    GFileInputStream               *file_stream;
+    GFileCopyFlags                 flags;
+    GCancellable                   *cancellable;
+    GFileProgressCallback          progress_callback;
+    gpointer                       progress_callback_data;
+    GAsyncReadyCallback            callback;
+    gpointer                       user_data;
+    char                           buffer[FILE_XFER_CHUNK_SIZE];
+    uint64_t                       read_bytes;
+    uint64_t                       file_size;
+    GError                         *error;
+} SpiceFileXferTask;
+
+struct _SpiceMainChannelPrivate  {
+    enum SpiceMouseMode         mouse_mode;
+    bool                        agent_connected;
+    bool                        agent_caps_received;
+
+    gboolean                    agent_display_config_sent;
+    guint8                      display_color_depth;
+    gboolean                    display_disable_wallpaper:1;
+    gboolean                    display_disable_font_smooth:1;
+    gboolean                    display_disable_animation:1;
+    gboolean                    disable_display_position:1;
+    gboolean                    disable_display_align:1;
+
+    int                         agent_tokens;
+    VDAgentMessage              agent_msg; /* partial msg reconstruction */
+    guint8                      *agent_msg_data;
+    guint                       agent_msg_pos;
+    uint8_t                     agent_msg_size;
+    uint32_t                    agent_caps[VD_AGENT_CAPS_SIZE];
+    struct {
+        int                     x;
+        int                     y;
+        int                     width;
+        int                     height;
+        gboolean                enabled;
+        gboolean                enabled_set;
+    } display[MAX_DISPLAY];
+    gint                        timer_id;
+    GQueue                      *agent_msg_queue;
+    GHashTable                  *file_xfer_tasks;
+    GSList                      *flushing;
+
+    guint                       switch_host_delayed_id;
+    guint                       migrate_delayed_id;
+    spice_migrate               *migrate_data;
+    int                         max_clipboard;
+
+    gboolean                    agent_volume_playback_sync;
+    gboolean                    agent_volume_record_sync;
+    GCancellable                *cancellable_volume_info;
+};
+
+struct spice_migrate {
+    struct coroutine *from;
+    SpiceMigrationDstInfo *info;
+    SpiceSession *session;
+    guint nchannels;
+    SpiceChannel *src_channel;
+    SpiceChannel *dst_channel;
+    bool do_seamless; /* used as input and output for the seamless migration handshake.
+                         input: whether to send to the dest SPICE_MSGC_MAIN_MIGRATE_DST_DO_SEAMLESS
+                         output: whether the dest approved seamless migration
+                         (SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_ACK/NACK)
+                       */
+    uint32_t src_mig_version;
+};
+
+G_DEFINE_TYPE(SpiceMainChannel, spice_main_channel, SPICE_TYPE_CHANNEL)
+
+/* Properties */
+enum {
+    PROP_0,
+    PROP_MOUSE_MODE,
+    PROP_AGENT_CONNECTED,
+    PROP_AGENT_CAPS_0,
+    PROP_DISPLAY_DISABLE_WALLPAPER,
+    PROP_DISPLAY_DISABLE_FONT_SMOOTH,
+    PROP_DISPLAY_DISABLE_ANIMATION,
+    PROP_DISPLAY_COLOR_DEPTH,
+    PROP_DISABLE_DISPLAY_POSITION,
+    PROP_DISABLE_DISPLAY_ALIGN,
+    PROP_MAX_CLIPBOARD,
+};
+
+/* Signals */
+enum {
+    SPICE_MAIN_MOUSE_UPDATE,
+    SPICE_MAIN_AGENT_UPDATE,
+    SPICE_MAIN_CLIPBOARD,
+    SPICE_MAIN_CLIPBOARD_GRAB,
+    SPICE_MAIN_CLIPBOARD_REQUEST,
+    SPICE_MAIN_CLIPBOARD_RELEASE,
+    SPICE_MAIN_CLIPBOARD_SELECTION,
+    SPICE_MAIN_CLIPBOARD_SELECTION_GRAB,
+    SPICE_MAIN_CLIPBOARD_SELECTION_REQUEST,
+    SPICE_MAIN_CLIPBOARD_SELECTION_RELEASE,
+    SPICE_MIGRATION_STARTED,
+    SPICE_MAIN_LAST_SIGNAL,
+};
+
+static guint signals[SPICE_MAIN_LAST_SIGNAL];
+
+static void spice_main_handle_msg(SpiceChannel *channel, SpiceMsgIn *msg);
+static void channel_set_handlers(SpiceChannelClass *klass);
+static void agent_send_msg_queue(SpiceMainChannel *channel);
+static void agent_free_msg_queue(SpiceMainChannel *channel);
+static void migrate_channel_event_cb(SpiceChannel *channel, SpiceChannelEvent event,
+                                     gpointer data);
+static gboolean main_migrate_handshake_done(gpointer data);
+static void spice_main_channel_send_migration_handshake(SpiceChannel *channel);
+static void file_xfer_continue_read(SpiceFileXferTask *task);
+static void file_xfer_completed(SpiceFileXferTask *task, GError *error);
+static void file_xfer_flushed(SpiceMainChannel *channel, gboolean success);
+static void spice_main_set_max_clipboard(SpiceMainChannel *self, gint max);
+static void set_agent_connected(SpiceMainChannel *channel, gboolean connected);
+
+/* ------------------------------------------------------------------ */
+
+static const char *agent_msg_types[] = {
+    [ VD_AGENT_MOUSE_STATE             ] = "mouse state",
+    [ VD_AGENT_MONITORS_CONFIG         ] = "monitors config",
+    [ VD_AGENT_REPLY                   ] = "reply",
+    [ VD_AGENT_CLIPBOARD               ] = "clipboard",
+    [ VD_AGENT_DISPLAY_CONFIG          ] = "display config",
+    [ VD_AGENT_ANNOUNCE_CAPABILITIES   ] = "announce caps",
+    [ VD_AGENT_CLIPBOARD_GRAB          ] = "clipboard grab",
+    [ VD_AGENT_CLIPBOARD_REQUEST       ] = "clipboard request",
+    [ VD_AGENT_CLIPBOARD_RELEASE       ] = "clipboard release",
+    [ VD_AGENT_AUDIO_VOLUME_SYNC       ] = "volume-sync",
+};
+
+static const char *agent_caps[] = {
+    [ VD_AGENT_CAP_MOUSE_STATE         ] = "mouse state",
+    [ VD_AGENT_CAP_MONITORS_CONFIG     ] = "monitors config",
+    [ VD_AGENT_CAP_REPLY               ] = "reply",
+    [ VD_AGENT_CAP_CLIPBOARD           ] = "clipboard (old)",
+    [ VD_AGENT_CAP_DISPLAY_CONFIG      ] = "display config",
+    [ VD_AGENT_CAP_CLIPBOARD_BY_DEMAND ] = "clipboard",
+    [ VD_AGENT_CAP_CLIPBOARD_SELECTION ] = "clipboard selection",
+    [ VD_AGENT_CAP_SPARSE_MONITORS_CONFIG ] = "sparse monitors",
+    [ VD_AGENT_CAP_GUEST_LINEEND_LF    ] = "line-end lf",
+    [ VD_AGENT_CAP_GUEST_LINEEND_CRLF  ] = "line-end crlf",
+    [ VD_AGENT_CAP_MAX_CLIPBOARD       ] = "max-clipboard",
+    [ VD_AGENT_CAP_AUDIO_VOLUME_SYNC   ] = "volume-sync",
+};
+#define NAME(_a, _i) ((_i) < SPICE_N_ELEMENTS(_a) ? (_a[(_i)] ?: "?") : "?")
+
+/* ------------------------------------------------------------------ */
+
+static gboolean test_agent_cap(SpiceMainChannel *channel, guint32 cap)
+{
+    SpiceMainChannelPrivate *c = channel->priv;
+
+    if (!c->agent_caps_received)
+        return FALSE;
+
+    return VD_AGENT_HAS_CAPABILITY(c->agent_caps, G_N_ELEMENTS(c->agent_caps), cap);
+}
+
+static void spice_main_channel_reset_capabilties(SpiceChannel *channel)
+{
+    spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_MAIN_CAP_SEMI_SEAMLESS_MIGRATE);
+    spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_MAIN_CAP_NAME_AND_UUID);
+    spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_MAIN_CAP_AGENT_CONNECTED_TOKENS);
+    spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_MAIN_CAP_SEAMLESS_MIGRATE);
+}
+
+static void spice_main_channel_init(SpiceMainChannel *channel)
+{
+    SpiceMainChannelPrivate *c;
+
+    c = channel->priv = SPICE_MAIN_CHANNEL_GET_PRIVATE(channel);
+    c->agent_msg_queue = g_queue_new();
+    c->file_xfer_tasks = g_hash_table_new(g_direct_hash, g_direct_equal);
+    c->cancellable_volume_info = g_cancellable_new();
+
+    spice_main_channel_reset_capabilties(SPICE_CHANNEL(channel));
+}
+
+static gint spice_main_get_max_clipboard(SpiceMainChannel *self)
+{
+    g_return_val_if_fail(SPICE_IS_MAIN_CHANNEL(self), 0);
+
+    if (g_getenv("SPICE_MAX_CLIPBOARD"))
+        return atoi(g_getenv("SPICE_MAX_CLIPBOARD"));
+
+    return self->priv->max_clipboard;
+}
+
+static void spice_main_get_property(GObject    *object,
+                                    guint       prop_id,
+                                    GValue     *value,
+                                    GParamSpec *pspec)
+{
+    SpiceMainChannel *self = SPICE_MAIN_CHANNEL(object);
+    SpiceMainChannelPrivate *c = self->priv;
+
+    switch (prop_id) {
+    case PROP_MOUSE_MODE:
+        g_value_set_int(value, c->mouse_mode);
+	break;
+    case PROP_AGENT_CONNECTED:
+        g_value_set_boolean(value, c->agent_connected);
+	break;
+    case PROP_AGENT_CAPS_0:
+        g_value_set_int(value, c->agent_caps[0]);
+	break;
+    case PROP_DISPLAY_DISABLE_WALLPAPER:
+        g_value_set_boolean(value, c->display_disable_wallpaper);
+        break;
+    case PROP_DISPLAY_DISABLE_FONT_SMOOTH:
+        g_value_set_boolean(value, c->display_disable_font_smooth);
+        break;
+    case PROP_DISPLAY_DISABLE_ANIMATION:
+        g_value_set_boolean(value, c->display_disable_animation);
+        break;
+    case PROP_DISPLAY_COLOR_DEPTH:
+        g_value_set_uint(value, c->display_color_depth);
+        break;
+    case PROP_DISABLE_DISPLAY_POSITION:
+        g_value_set_boolean(value, c->disable_display_position);
+        break;
+    case PROP_DISABLE_DISPLAY_ALIGN:
+        g_value_set_boolean(value, c->disable_display_align);
+        break;
+    case PROP_MAX_CLIPBOARD:
+        g_value_set_int(value, spice_main_get_max_clipboard(self));
+        break;
+    default:
+	G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+	break;
+    }
+}
+
+static void spice_main_set_property(GObject *gobject, guint prop_id,
+                                    const GValue *value, GParamSpec *pspec)
+{
+    SpiceMainChannel *self = SPICE_MAIN_CHANNEL(gobject);
+    SpiceMainChannelPrivate *c = self->priv;
+
+    switch (prop_id) {
+    case PROP_DISPLAY_DISABLE_WALLPAPER:
+        c->display_disable_wallpaper = g_value_get_boolean(value);
+        break;
+    case PROP_DISPLAY_DISABLE_FONT_SMOOTH:
+        c->display_disable_font_smooth = g_value_get_boolean(value);
+        break;
+    case PROP_DISPLAY_DISABLE_ANIMATION:
+        c->display_disable_animation = g_value_get_boolean(value);
+        break;
+    case PROP_DISPLAY_COLOR_DEPTH: {
+        guint color_depth = g_value_get_uint(value);
+        g_return_if_fail(color_depth % 8 == 0);
+        c->display_color_depth = color_depth;
+        break;
+    }
+    case PROP_DISABLE_DISPLAY_POSITION:
+        c->disable_display_position = g_value_get_boolean(value);
+        break;
+    case PROP_DISABLE_DISPLAY_ALIGN:
+        c->disable_display_align = g_value_get_boolean(value);
+        break;
+    case PROP_MAX_CLIPBOARD:
+        spice_main_set_max_clipboard(self, g_value_get_int(value));
+        break;
+    default:
+	G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
+	break;
+    }
+}
+
+static void spice_main_channel_dispose(GObject *obj)
+{
+    SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(obj)->priv;
+
+    if (c->timer_id) {
+        g_source_remove(c->timer_id);
+        c->timer_id = 0;
+    }
+
+    if (c->switch_host_delayed_id) {
+        g_source_remove(c->switch_host_delayed_id);
+        c->switch_host_delayed_id = 0;
+    }
+
+    if (c->migrate_delayed_id) {
+        g_source_remove(c->migrate_delayed_id);
+        c->migrate_delayed_id = 0;
+    }
+
+    g_cancellable_cancel(c->cancellable_volume_info);
+    g_clear_object(&c->cancellable_volume_info);
+
+    if (G_OBJECT_CLASS(spice_main_channel_parent_class)->dispose)
+        G_OBJECT_CLASS(spice_main_channel_parent_class)->dispose(obj);
+}
+
+static void spice_main_channel_finalize(GObject *obj)
+{
+    SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(obj)->priv;
+
+    g_free(c->agent_msg_data);
+    agent_free_msg_queue(SPICE_MAIN_CHANNEL(obj));
+    if (c->file_xfer_tasks)
+        g_hash_table_unref(c->file_xfer_tasks);
+
+    if (G_OBJECT_CLASS(spice_main_channel_parent_class)->finalize)
+        G_OBJECT_CLASS(spice_main_channel_parent_class)->finalize(obj);
+}
+
+/* coroutine context */
+static void spice_channel_iterate_write(SpiceChannel *channel)
+{
+    agent_send_msg_queue(SPICE_MAIN_CHANNEL(channel));
+
+    if (SPICE_CHANNEL_CLASS(spice_main_channel_parent_class)->iterate_write)
+        SPICE_CHANNEL_CLASS(spice_main_channel_parent_class)->iterate_write(channel);
+}
+
+/* main or coroutine context */
+static void spice_main_channel_reset_agent(SpiceMainChannel *channel)
+{
+    SpiceMainChannelPrivate *c = channel->priv;
+    GError *error;
+    GList *tasks;
+    GList *l;
+
+    c->agent_connected = FALSE;
+    c->agent_caps_received = FALSE;
+    c->agent_display_config_sent = FALSE;
+    c->agent_msg_pos = 0;
+    g_free(c->agent_msg_data);
+    c->agent_msg_data = NULL;
+    c->agent_msg_size = 0;
+
+    tasks = g_hash_table_get_values(c->file_xfer_tasks);
+    for (l = tasks; l != NULL; l = l->next) {
+        SpiceFileXferTask *task = (SpiceFileXferTask *)l->data;
+
+        error = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                            "Agent connection closed");
+        file_xfer_completed(task, error);
+    }
+    g_list_free(tasks);
+    file_xfer_flushed(channel, FALSE);
+}
+
+/* main or coroutine context */
+static void spice_main_channel_reset(SpiceChannel *channel, gboolean migrating)
+{
+    SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
+
+    /* This is not part of reset_agent, since the spice-server expects any
+       pending multi-chunk messages to be completed by the client, even after
+       it has send an agent-disconnected msg as that is what the original
+       spicec did. Also see the TODO in server/reds.c reds_reset_vdp() */
+    c->agent_tokens = 0;
+    agent_free_msg_queue(SPICE_MAIN_CHANNEL(channel));
+    c->agent_msg_queue = g_queue_new();
+
+    c->agent_volume_playback_sync = FALSE;
+    c->agent_volume_record_sync = FALSE;
+
+    set_agent_connected(SPICE_MAIN_CHANNEL(channel), FALSE);
+
+    SPICE_CHANNEL_CLASS(spice_main_channel_parent_class)->channel_reset(channel, migrating);
+}
+
+static void spice_main_constructed(GObject *object)
+{
+    SpiceMainChannel *self = SPICE_MAIN_CHANNEL(object);
+    SpiceMainChannelPrivate *c = self->priv;
+
+    /* update default value */
+    c->max_clipboard = spice_main_get_max_clipboard(self);
+
+    if (G_OBJECT_CLASS(spice_main_channel_parent_class)->constructed)
+        G_OBJECT_CLASS(spice_main_channel_parent_class)->constructed(object);
+}
+
+static void spice_main_channel_class_init(SpiceMainChannelClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+    SpiceChannelClass *channel_class = SPICE_CHANNEL_CLASS(klass);
+
+    gobject_class->dispose      = spice_main_channel_dispose;
+    gobject_class->finalize     = spice_main_channel_finalize;
+    gobject_class->get_property = spice_main_get_property;
+    gobject_class->set_property = spice_main_set_property;
+    gobject_class->constructed  = spice_main_constructed;
+
+    channel_class->handle_msg    = spice_main_handle_msg;
+    channel_class->iterate_write = spice_channel_iterate_write;
+    channel_class->channel_reset = spice_main_channel_reset;
+    channel_class->channel_reset_capabilities = spice_main_channel_reset_capabilties;
+    channel_class->channel_send_migration_handshake = spice_main_channel_send_migration_handshake;
+
+    /**
+     * SpiceMainChannel:mouse-mode:
+     *
+     * Spice protocol specifies two mouse modes, client mode and
+     * server mode. In client mode (%SPICE_MOUSE_MODE_CLIENT), the
+     * affective mouse is the client side mouse: the client sends
+     * mouse position within the display and the server sends mouse
+     * shape messages. In server mode (%SPICE_MOUSE_MODE_SERVER), the
+     * client sends relative mouse movements and the server sends
+     * position and shape commands.
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_MOUSE_MODE,
+         g_param_spec_int("mouse-mode",
+                          "Mouse mode",
+                          "Mouse mode",
+                          0, INT_MAX, 0,
+                          G_PARAM_READABLE |
+                          G_PARAM_STATIC_NAME |
+                          G_PARAM_STATIC_NICK |
+                          G_PARAM_STATIC_BLURB));
+
+    g_object_class_install_property
+        (gobject_class, PROP_AGENT_CONNECTED,
+         g_param_spec_boolean("agent-connected",
+                              "Agent connected",
+                              "Whether the agent is connected",
+                              FALSE,
+                              G_PARAM_READABLE |
+                              G_PARAM_STATIC_NAME |
+                              G_PARAM_STATIC_NICK |
+                              G_PARAM_STATIC_BLURB));
+
+    g_object_class_install_property
+        (gobject_class, PROP_AGENT_CAPS_0,
+         g_param_spec_int("agent-caps-0",
+                          "Agent caps 0",
+                          "Agent capability bits 0 -> 31",
+                          0, INT_MAX, 0,
+                          G_PARAM_READABLE |
+                          G_PARAM_STATIC_NAME |
+                          G_PARAM_STATIC_NICK |
+                          G_PARAM_STATIC_BLURB));
+
+    g_object_class_install_property
+        (gobject_class, PROP_DISPLAY_DISABLE_WALLPAPER,
+         g_param_spec_boolean("disable-wallpaper",
+                              "Disable guest wallpaper",
+                              "Disable guest wallpaper",
+                              FALSE,
+                              G_PARAM_READWRITE |
+                              G_PARAM_CONSTRUCT |
+                              G_PARAM_STATIC_STRINGS));
+
+    g_object_class_install_property
+        (gobject_class, PROP_DISPLAY_DISABLE_FONT_SMOOTH,
+         g_param_spec_boolean("disable-font-smooth",
+                              "Disable guest font smooth",
+                              "Disable guest font smoothing",
+                              FALSE,
+                              G_PARAM_READWRITE |
+                              G_PARAM_CONSTRUCT |
+                              G_PARAM_STATIC_STRINGS));
+
+    g_object_class_install_property
+        (gobject_class, PROP_DISPLAY_DISABLE_ANIMATION,
+         g_param_spec_boolean("disable-animation",
+                              "Disable guest animations",
+                              "Disable guest animations",
+                              FALSE,
+                              G_PARAM_READWRITE |
+                              G_PARAM_CONSTRUCT |
+                              G_PARAM_STATIC_STRINGS));
+
+    g_object_class_install_property
+        (gobject_class, PROP_DISABLE_DISPLAY_POSITION,
+         g_param_spec_boolean("disable-display-position",
+                              "Disable display position",
+                              "Disable using display position when setting monitor config",
+                              TRUE,
+                              G_PARAM_READWRITE |
+                              G_PARAM_CONSTRUCT |
+                              G_PARAM_STATIC_STRINGS));
+
+    g_object_class_install_property
+        (gobject_class, PROP_DISPLAY_COLOR_DEPTH,
+         g_param_spec_uint("color-depth",
+                           "Color depth",
+                           "Color depth", 0, 32, 0,
+                           G_PARAM_READWRITE |
+                           G_PARAM_CONSTRUCT |
+                           G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceMainChannel:disable-display-align:
+     *
+     * Disable automatic horizontal display position alignment.
+     *
+     * Since: 0.13
+     */
+    g_object_class_install_property
+        (gobject_class, PROP_DISABLE_DISPLAY_ALIGN,
+         g_param_spec_boolean("disable-display-align",
+                              "Disable display align",
+                              "Disable display position alignment",
+                              FALSE,
+                              G_PARAM_READWRITE |
+                              G_PARAM_CONSTRUCT |
+                              G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceMainChannel:max-clipboard:
+     *
+     * Maximum size of clipboard operations in bytes (default 100MB,
+     * -1 for unlimited size);
+     *
+     * Since: 0.22
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_MAX_CLIPBOARD,
+         g_param_spec_int("max-clipboard",
+                          "max clipboard",
+                          "Maximum clipboard data size",
+                          -1, G_MAXINT, 100 * 1024 * 1024,
+                          G_PARAM_READWRITE |
+                          G_PARAM_CONSTRUCT |
+                          G_PARAM_STATIC_STRINGS));
+
+    /* TODO use notify instead */
+    /**
+     * SpiceMainChannel::main-mouse-update:
+     * @main: the #SpiceMainChannel that emitted the signal
+     *
+     * Notify when the mouse mode has changed.
+     **/
+    signals[SPICE_MAIN_MOUSE_UPDATE] =
+        g_signal_new("main-mouse-update",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_FIRST,
+                     G_STRUCT_OFFSET(SpiceMainChannelClass, mouse_update),
+                     NULL, NULL,
+                     g_cclosure_marshal_VOID__VOID,
+                     G_TYPE_NONE,
+                     0);
+
+    /* TODO use notify instead */
+    /**
+     * SpiceMainChannel::main-agent-update:
+     * @main: the #SpiceMainChannel that emitted the signal
+     *
+     * Notify when the %SpiceMainChannel:agent-connected or
+     * %SpiceMainChannel:agent-caps-0 property change.
+     **/
+    signals[SPICE_MAIN_AGENT_UPDATE] =
+        g_signal_new("main-agent-update",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_FIRST,
+                     G_STRUCT_OFFSET(SpiceMainChannelClass, agent_update),
+                     NULL, NULL,
+                     g_cclosure_marshal_VOID__VOID,
+                     G_TYPE_NONE,
+                     0);
+    /**
+     * SpiceMainChannel::main-clipboard:
+     * @main: the #SpiceMainChannel that emitted the signal
+     * @type: the VD_AGENT_CLIPBOARD data type
+     * @data: clipboard data
+     * @size: size of @data in bytes
+     *
+     * Provides guest clipboard data requested by spice_main_clipboard_request().
+     *
+     * Deprecated: 0.6: use SpiceMainChannel::main-clipboard-selection instead.
+     **/
+    signals[SPICE_MAIN_CLIPBOARD] =
+        g_signal_new("main-clipboard",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_LAST | G_SIGNAL_DEPRECATED,
+                     0,
+                     NULL, NULL,
+                     g_cclosure_user_marshal_VOID__UINT_POINTER_UINT,
+                     G_TYPE_NONE,
+                     3,
+                     G_TYPE_UINT, G_TYPE_POINTER, G_TYPE_UINT);
+
+    /**
+     * SpiceMainChannel::main-clipboard-selection:
+     * @main: the #SpiceMainChannel that emitted the signal
+     * @selection: a VD_AGENT_CLIPBOARD_SELECTION clipboard
+     * @type: the VD_AGENT_CLIPBOARD data type
+     * @data: clipboard data
+     * @size: size of @data in bytes
+     *
+     * Since: 0.6
+     **/
+    signals[SPICE_MAIN_CLIPBOARD_SELECTION] =
+        g_signal_new("main-clipboard-selection",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_LAST,
+                     0,
+                     NULL, NULL,
+                     g_cclosure_user_marshal_VOID__UINT_UINT_POINTER_UINT,
+                     G_TYPE_NONE,
+                     4,
+                     G_TYPE_UINT, G_TYPE_UINT, G_TYPE_POINTER, G_TYPE_UINT);
+
+    /**
+     * SpiceMainChannel::main-clipboard-grab:
+     * @main: the #SpiceMainChannel that emitted the signal
+     * @types: the VD_AGENT_CLIPBOARD data types
+     * @ntypes: the number of @types
+     *
+     * Inform when clipboard data is available from the guest, and for
+     * which @types.
+     *
+     * Deprecated: 0.6: use SpiceMainChannel::main-clipboard-selection-grab instead.
+     **/
+    signals[SPICE_MAIN_CLIPBOARD_GRAB] =
+        g_signal_new("main-clipboard-grab",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_LAST | G_SIGNAL_DEPRECATED,
+                     0,
+                     NULL, NULL,
+                     g_cclosure_user_marshal_BOOLEAN__POINTER_UINT,
+                     G_TYPE_BOOLEAN,
+                     2,
+                     G_TYPE_POINTER, G_TYPE_UINT);
+
+    /**
+     * SpiceMainChannel::main-clipboard-selection-grab:
+     * @main: the #SpiceMainChannel that emitted the signal
+     * @selection: a VD_AGENT_CLIPBOARD_SELECTION clipboard
+     * @types: the VD_AGENT_CLIPBOARD data types
+     * @ntypes: the number of @types
+     *
+     * Inform when clipboard data is available from the guest, and for
+     * which @types.
+     *
+     * Since: 0.6
+     **/
+    signals[SPICE_MAIN_CLIPBOARD_SELECTION_GRAB] =
+        g_signal_new("main-clipboard-selection-grab",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_LAST,
+                     0,
+                     NULL, NULL,
+                     g_cclosure_user_marshal_BOOLEAN__UINT_POINTER_UINT,
+                     G_TYPE_BOOLEAN,
+                     3,
+                     G_TYPE_UINT, G_TYPE_POINTER, G_TYPE_UINT);
+
+    /**
+     * SpiceMainChannel::main-clipboard-request:
+     * @main: the #SpiceMainChannel that emitted the signal
+     * @types: the VD_AGENT_CLIPBOARD request type
+     *
+     * Return value: %TRUE if the request is successful
+     *
+     * Request clipbard data from the client.
+     *
+     * Deprecated: 0.6: use SpiceMainChannel::main-clipboard-selection-request instead.
+     **/
+    signals[SPICE_MAIN_CLIPBOARD_REQUEST] =
+        g_signal_new("main-clipboard-request",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_LAST | G_SIGNAL_DEPRECATED,
+                     0,
+                     NULL, NULL,
+                     g_cclosure_user_marshal_BOOLEAN__UINT,
+                     G_TYPE_BOOLEAN,
+                     1,
+                     G_TYPE_UINT);
+
+    /**
+     * SpiceMainChannel::main-clipboard-selection-request:
+     * @main: the #SpiceMainChannel that emitted the signal
+     * @selection: a VD_AGENT_CLIPBOARD_SELECTION clipboard
+     * @types: the VD_AGENT_CLIPBOARD request type
+     *
+     * Return value: %TRUE if the request is successful
+     *
+     * Request clipbard data from the client.
+     *
+     * Since: 0.6
+     **/
+    signals[SPICE_MAIN_CLIPBOARD_SELECTION_REQUEST] =
+        g_signal_new("main-clipboard-selection-request",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_LAST,
+                     0,
+                     NULL, NULL,
+                     g_cclosure_user_marshal_BOOLEAN__UINT_UINT,
+                     G_TYPE_BOOLEAN,
+                     2,
+                     G_TYPE_UINT, G_TYPE_UINT);
+
+    /**
+     * SpiceMainChannel::main-clipboard-release:
+     * @main: the #SpiceMainChannel that emitted the signal
+     *
+     * Inform when the clipboard is released from the guest, when no
+     * clipboard data is available from the guest.
+     *
+     * Deprecated: 0.6: use SpiceMainChannel::main-clipboard-selection-release instead.
+     **/
+    signals[SPICE_MAIN_CLIPBOARD_RELEASE] =
+        g_signal_new("main-clipboard-release",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_LAST | G_SIGNAL_DEPRECATED,
+                     0,
+                     NULL, NULL,
+                     g_cclosure_marshal_VOID__VOID,
+                     G_TYPE_NONE,
+                     0);
+
+    /**
+     * SpiceMainChannel::main-clipboard-selection-release:
+     * @main: the #SpiceMainChannel that emitted the signal
+     * @selection: a VD_AGENT_CLIPBOARD_SELECTION clipboard
+     *
+     * Inform when the clipboard is released from the guest, when no
+     * clipboard data is available from the guest.
+     *
+     * Since: 0.6
+     **/
+    signals[SPICE_MAIN_CLIPBOARD_SELECTION_RELEASE] =
+        g_signal_new("main-clipboard-selection-release",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_LAST,
+                     0,
+                     NULL, NULL,
+                     g_cclosure_marshal_VOID__UINT,
+                     G_TYPE_NONE,
+                     1,
+                     G_TYPE_UINT);
+
+    /**
+     * SpiceMainChannel::migration-started:
+     * @main: the #SpiceMainChannel that emitted the signal
+     * @session: a migration #SpiceSession
+     *
+     * Inform when migration is starting. Application wishing to make
+     * connections themself can set the #SpiceSession:client-sockets
+     * to @TRUE, then follow #SpiceSession::channel-new creation, and
+     * use spice_channel_open_fd() once the socket is created.
+     *
+     **/
+    signals[SPICE_MIGRATION_STARTED] =
+        g_signal_new("migration-started",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_LAST,
+                     0,
+                     NULL, NULL,
+                     g_cclosure_marshal_VOID__OBJECT,
+                     G_TYPE_NONE,
+                     1,
+                     G_TYPE_OBJECT);
+
+    g_type_class_add_private(klass, sizeof(SpiceMainChannelPrivate));
+    channel_set_handlers(SPICE_CHANNEL_CLASS(klass));
+}
+
+/* ------------------------------------------------------------------ */
+
+
+static void agent_free_msg_queue(SpiceMainChannel *channel)
+{
+    SpiceMainChannelPrivate *c = channel->priv;
+    SpiceMsgOut *out;
+
+    if (!c->agent_msg_queue)
+        return;
+
+    while (!g_queue_is_empty(c->agent_msg_queue)) {
+        out = g_queue_pop_head(c->agent_msg_queue);
+        spice_msg_out_unref(out);
+    }
+
+    g_queue_free(c->agent_msg_queue);
+    c->agent_msg_queue = NULL;
+}
+
+/* Here, flushing algorithm is stolen from spice-channel.c */
+static void file_xfer_flushed(SpiceMainChannel *channel, gboolean success)
+{
+    SpiceMainChannelPrivate *c = channel->priv;
+    GSList *l;
+
+    for (l = c->flushing; l != NULL; l = l->next) {
+        GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT(l->data);
+        g_simple_async_result_set_op_res_gboolean(result, success);
+        g_simple_async_result_complete_in_idle(result);
+    }
+
+    g_slist_free_full(c->flushing, g_object_unref);
+    c->flushing = NULL;
+}
+
+static void file_xfer_flush_async(SpiceMainChannel *channel, GCancellable *cancellable,
+                                  GAsyncReadyCallback callback, gpointer user_data)
+{
+    GSimpleAsyncResult *simple;
+    SpiceMainChannelPrivate *c = channel->priv;
+    gboolean was_empty;
+
+    simple = g_simple_async_result_new(G_OBJECT(channel), callback, user_data,
+                                       file_xfer_flush_async);
+
+    was_empty = g_queue_is_empty(c->agent_msg_queue);
+    if (was_empty) {
+        g_simple_async_result_set_op_res_gboolean(simple, TRUE);
+        g_simple_async_result_complete_in_idle(simple);
+        g_object_unref(simple);
+        return;
+    }
+
+    c->flushing = g_slist_append(c->flushing, simple);
+}
+
+static gboolean file_xfer_flush_finish(SpiceMainChannel *channel, GAsyncResult *result,
+                                       GError **error)
+{
+    GSimpleAsyncResult *simple = (GSimpleAsyncResult *)result;
+
+    g_return_val_if_fail(g_simple_async_result_is_valid(result,
+        G_OBJECT(channel), file_xfer_flush_async), FALSE);
+
+    if (g_simple_async_result_propagate_error(simple, error)) {
+        return FALSE;
+    }
+
+    CHANNEL_DEBUG(channel, "flushed finished!");
+    return g_simple_async_result_get_op_res_gboolean(simple);
+}
+
+/* coroutine context */
+static void agent_send_msg_queue(SpiceMainChannel *channel)
+{
+    SpiceMainChannelPrivate *c = channel->priv;
+    SpiceMsgOut *out;
+
+    while (c->agent_tokens > 0 &&
+           !g_queue_is_empty(c->agent_msg_queue)) {
+        c->agent_tokens--;
+        out = g_queue_pop_head(c->agent_msg_queue);
+        spice_msg_out_send_internal(out);
+    }
+    if (g_queue_is_empty(c->agent_msg_queue) && c->flushing != NULL) {
+        file_xfer_flushed(channel, TRUE);
+    }
+}
+
+/* any context: the message is not flushed immediately,
+   you can wakeup() the channel coroutine or send_msg_queue()
+
+   expected arguments, pair of data/data_size to send terminated with NULL:
+   agent_msg_queue_many(main, VD_AGENT_...,
+                        &foo, sizeof(Foo),
+                        data, data_size, NULL);
+*/
+G_GNUC_NULL_TERMINATED
+static void agent_msg_queue_many(SpiceMainChannel *channel, int type, const void *data, ...)
+{
+    va_list args;
+    SpiceMainChannelPrivate *c = channel->priv;
+    SpiceMsgOut *out;
+    VDAgentMessage msg;
+    guint8 *payload;
+    gsize paysize, s, mins, size = 0;
+    const guint8 *d;
+
+    G_STATIC_ASSERT(VD_AGENT_MAX_DATA_SIZE > sizeof(VDAgentMessage));
+
+    va_start(args, data);
+    for (d = data; d != NULL; d = va_arg(args, void*)) {
+        size += va_arg(args, gsize);
+    }
+    va_end(args);
+
+    msg.protocol = VD_AGENT_PROTOCOL;
+    msg.type = type;
+    msg.opaque = 0;
+    msg.size = size;
+
+    paysize = MIN(VD_AGENT_MAX_DATA_SIZE, size + sizeof(VDAgentMessage));
+    out = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_MAIN_AGENT_DATA);
+    payload = spice_marshaller_reserve_space(out->marshaller, paysize);
+    memcpy(payload, &msg, sizeof(VDAgentMessage));
+    payload += sizeof(VDAgentMessage);
+    paysize -= sizeof(VDAgentMessage);
+    if (paysize == 0) {
+        g_queue_push_tail(c->agent_msg_queue, out);
+        out = NULL;
+    }
+
+    va_start(args, data);
+    for (d = data; size > 0; d = va_arg(args, void*)) {
+        s = va_arg(args, gsize);
+        while (s > 0) {
+            if (out == NULL) {
+                paysize = MIN(VD_AGENT_MAX_DATA_SIZE, size);
+                out = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_MAIN_AGENT_DATA);
+                payload = spice_marshaller_reserve_space(out->marshaller, paysize);
+            }
+            mins = MIN(paysize, s);
+            memcpy(payload, d, mins);
+            d += mins;
+            payload += mins;
+            s -= mins;
+            size -= mins;
+            paysize -= mins;
+            if (paysize == 0) {
+                g_queue_push_tail(c->agent_msg_queue, out);
+                out = NULL;
+            }
+        }
+    }
+    va_end(args);
+    g_warn_if_fail(out == NULL);
+}
+
+static int monitors_cmp(const void *p1, const void *p2, gpointer user_data)
+{
+    const VDAgentMonConfig *m1 = p1;
+    const VDAgentMonConfig *m2 = p2;
+    double d1 = sqrt(m1->x * m1->x + m1->y * m1->y);
+    double d2 = sqrt(m2->x * m2->x + m2->y * m2->y);
+    int diff = d1 - d2;
+
+    return diff == 0 ? (char*)p1 - (char*)p2 : diff;
+}
+
+static void monitors_align(VDAgentMonConfig *monitors, int nmonitors)
+{
+    gint i, j, x = 0;
+    guint32 used = 0;
+    VDAgentMonConfig *sorted_monitors;
+
+    if (nmonitors == 0)
+        return;
+
+    /* sort by distance from origin */
+    sorted_monitors = g_memdup(monitors, nmonitors * sizeof(VDAgentMonConfig));
+    g_qsort_with_data(sorted_monitors, nmonitors, sizeof(VDAgentMonConfig), monitors_cmp, NULL);
+
+    /* super-KISS ltr alignment, feel free to improve */
+    for (i = 0; i < nmonitors; i++) {
+        /* Find where this monitor is in the sorted order */
+        for (j = 0; j < nmonitors; j++) {
+            /* Avoid using the same entry twice, this happens with older
+               virt-viewer versions which always set x and y to 0 */
+            if (used & (1 << j))
+                continue;
+            if (memcmp(&monitors[j], &sorted_monitors[i],
+                       sizeof(VDAgentMonConfig)) == 0)
+                break;
+        }
+        used |= 1 << j;
+        monitors[j].x = x;
+        monitors[j].y = 0;
+        x += monitors[j].width;
+        if (monitors[j].width || monitors[j].height)
+            SPICE_DEBUG("#%d +%d+%d-%dx%d", j, monitors[j].x, monitors[j].y,
+                        monitors[j].width, monitors[j].height);
+    }
+    g_free(sorted_monitors);
+}
+
+
+#define agent_msg_queue(Channel, Type, Size, Data) \
+    agent_msg_queue_many((Channel), (Type), (Data), (Size), NULL)
+
+/**
+ * spice_main_send_monitor_config:
+ * @channel:
+ *
+ * Send monitors configuration previously set with
+ * spice_main_set_display() and spice_main_set_display_enabled()
+ *
+ * Returns: %TRUE on success.
+ **/
+gboolean spice_main_send_monitor_config(SpiceMainChannel *channel)
+{
+    SpiceMainChannelPrivate *c;
+    VDAgentMonitorsConfig *mon;
+    int i, j, monitors;
+    size_t size;
+
+    g_return_val_if_fail(SPICE_IS_MAIN_CHANNEL(channel), FALSE);
+    c = channel->priv;
+    g_return_val_if_fail(c->agent_connected, FALSE);
+
+    if (spice_main_agent_test_capability(channel,
+                                     VD_AGENT_CAP_SPARSE_MONITORS_CONFIG)) {
+        monitors = SPICE_N_ELEMENTS(c->display);
+    } else {
+        monitors = 0;
+        for (i = 0; i < SPICE_N_ELEMENTS(c->display); i++) {
+            if (c->display[i].enabled)
+                monitors += 1;
+        }
+    }
+
+    size = sizeof(VDAgentMonitorsConfig) + sizeof(VDAgentMonConfig) * monitors;
+    mon = g_malloc0(size);
+
+    mon->num_of_monitors = monitors;
+    if (c->disable_display_position == FALSE ||
+        c->disable_display_align == FALSE)
+        mon->flags |= VD_AGENT_CONFIG_MONITORS_FLAG_USE_POS;
+
+    j = 0;
+    for (i = 0; i < SPICE_N_ELEMENTS(c->display); i++) {
+        if (!c->display[i].enabled) {
+            if (spice_main_agent_test_capability(channel,
+                                     VD_AGENT_CAP_SPARSE_MONITORS_CONFIG))
+                j++;
+            continue;
+        }
+        mon->monitors[j].depth  = c->display_color_depth ? c->display_color_depth : 32;
+        mon->monitors[j].width  = c->display[i].width;
+        mon->monitors[j].height = c->display[i].height;
+        mon->monitors[j].x = c->display[i].x;
+        mon->monitors[j].y = c->display[i].y;
+        CHANNEL_DEBUG(channel, "monitor config: #%d %dx%d+%d+%d @ %d bpp", j,
+                      mon->monitors[j].width, mon->monitors[j].height,
+                      mon->monitors[j].x, mon->monitors[j].y,
+                      mon->monitors[j].depth);
+        j++;
+    }
+
+    if (c->disable_display_align == FALSE)
+        monitors_align(mon->monitors, mon->num_of_monitors);
+
+    agent_msg_queue(channel, VD_AGENT_MONITORS_CONFIG, size, mon);
+    g_free(mon);
+
+    spice_channel_wakeup(SPICE_CHANNEL(channel), FALSE);
+    if (c->timer_id != 0) {
+        g_source_remove(c->timer_id);
+        c->timer_id = 0;
+    }
+
+    return TRUE;
+}
+
+static void audio_playback_volume_info_cb(GObject *object, GAsyncResult *res, gpointer user_data)
+{
+    SpiceMainChannel *main_channel = user_data;
+    SpiceSession *session = spice_channel_get_session(SPICE_CHANNEL(main_channel));
+    SpiceAudio *audio = spice_audio_get(session, NULL);
+    VDAgentAudioVolumeSync *avs;
+    guint16 *volume;
+    guint8 nchannels;
+    gboolean mute, ret;
+    gsize array_size;
+    GError *error = NULL;
+
+    ret = spice_audio_get_playback_volume_info_finish(audio, res, &mute, &nchannels,
+                                                      &volume, &error);
+    if (ret == FALSE || volume == NULL || nchannels == 0) {
+        if (error != NULL) {
+            spice_warning("Failed to get playback async volume info: %s", error->message);
+            g_error_free (error);
+        } else {
+            SPICE_DEBUG("Failed to get playback async volume info");
+        }
+        main_channel->priv->agent_volume_playback_sync = FALSE;
+        return;
+    }
+
+    array_size = sizeof(uint16_t) * nchannels;
+    avs = g_malloc0(sizeof(VDAgentAudioVolumeSync) + array_size);
+    avs->is_playback = TRUE;
+    avs->mute = mute;
+    avs->nchannels = nchannels;
+    memcpy(avs->volume, volume, array_size);
+
+    SPICE_DEBUG("%s mute=%s nchannels=%u volume[0]=%u",
+                __func__, spice_yes_no(mute), nchannels, volume[0]);
+    g_free(volume);
+    agent_msg_queue(main_channel, VD_AGENT_AUDIO_VOLUME_SYNC,
+                    sizeof(VDAgentAudioVolumeSync) + array_size, avs);
+}
+
+static void agent_sync_audio_playback(SpiceMainChannel *main_channel)
+{
+    SpiceSession *session = spice_channel_get_session(SPICE_CHANNEL(main_channel));
+    SpiceAudio *audio = spice_audio_get(session, NULL);
+    SpiceMainChannelPrivate *c = main_channel->priv;
+
+    if (!test_agent_cap(main_channel, VD_AGENT_CAP_AUDIO_VOLUME_SYNC) ||
+        c->agent_volume_playback_sync == TRUE) {
+        SPICE_DEBUG("%s - is not going to sync audio with guest", __func__);
+        return;
+    }
+    /* only one per connection */
+    g_cancellable_reset(c->cancellable_volume_info);
+    c->agent_volume_playback_sync = TRUE;
+    spice_audio_get_playback_volume_info_async(audio, c->cancellable_volume_info, main_channel,
+                                               audio_playback_volume_info_cb, main_channel);
+}
+
+static void audio_record_volume_info_cb(GObject *object, GAsyncResult *res, gpointer user_data)
+{
+    SpiceMainChannel *main_channel = user_data;
+    SpiceSession *session = spice_channel_get_session(SPICE_CHANNEL(main_channel));
+    SpiceAudio *audio = spice_audio_get(session, NULL);
+    VDAgentAudioVolumeSync *avs;
+    guint16 *volume;
+    guint8 nchannels;
+    gboolean ret, mute;
+    gsize array_size;
+    GError *error = NULL;
+
+    ret = spice_audio_get_record_volume_info_finish(audio, res, &mute, &nchannels, &volume, &error);
+    if (ret == FALSE || volume == NULL || nchannels == 0) {
+        if (error != NULL) {
+            spice_warning ("Failed to get record async volume info: %s", error->message);
+            g_error_free (error);
+        } else {
+            SPICE_DEBUG("Failed to get record async volume info");
+        }
+        main_channel->priv->agent_volume_record_sync = FALSE;
+        return;
+    }
+
+    array_size = sizeof(uint16_t) * nchannels;
+    avs = g_malloc0(sizeof(VDAgentAudioVolumeSync) + array_size);
+    avs->is_playback = FALSE;
+    avs->mute = mute;
+    avs->nchannels = nchannels;
+    memcpy(avs->volume, volume, array_size);
+
+    SPICE_DEBUG("%s mute=%s nchannels=%u volume[0]=%u",
+                __func__, spice_yes_no(mute), nchannels, volume[0]);
+    g_free(volume);
+    agent_msg_queue(main_channel, VD_AGENT_AUDIO_VOLUME_SYNC,
+                    sizeof(VDAgentAudioVolumeSync) + array_size, avs);
+}
+
+static void agent_sync_audio_record(SpiceMainChannel *main_channel)
+{
+    SpiceSession *session = spice_channel_get_session(SPICE_CHANNEL(main_channel));
+    SpiceAudio *audio = spice_audio_get(session, NULL);
+    SpiceMainChannelPrivate *c = main_channel->priv;
+
+    if (!test_agent_cap(main_channel, VD_AGENT_CAP_AUDIO_VOLUME_SYNC) ||
+        c->agent_volume_record_sync == TRUE) {
+        SPICE_DEBUG("%s - is not going to sync audio with guest", __func__);
+        return;
+    }
+    /* only one per connection */
+    g_cancellable_reset(c->cancellable_volume_info);
+    c->agent_volume_record_sync = TRUE;
+    spice_audio_get_record_volume_info_async(audio, c->cancellable_volume_info, main_channel,
+                                             audio_record_volume_info_cb, main_channel);
+}
+
+/* any context: the message is not flushed immediately,
+   you can wakeup() the channel coroutine or send_msg_queue() */
+static void agent_display_config(SpiceMainChannel *channel)
+{
+    SpiceMainChannelPrivate *c = channel->priv;
+    VDAgentDisplayConfig config = { 0, };
+
+    if (c->display_disable_wallpaper) {
+        config.flags |= VD_AGENT_DISPLAY_CONFIG_FLAG_DISABLE_WALLPAPER;
+    }
+
+    if (c->display_disable_font_smooth) {
+        config.flags |= VD_AGENT_DISPLAY_CONFIG_FLAG_DISABLE_FONT_SMOOTH;
+    }
+
+    if (c->display_disable_animation) {
+        config.flags |= VD_AGENT_DISPLAY_CONFIG_FLAG_DISABLE_ANIMATION;
+    }
+
+    if (c->display_color_depth != 0) {
+        config.flags |= VD_AGENT_DISPLAY_CONFIG_FLAG_SET_COLOR_DEPTH;
+        config.depth = c->display_color_depth;
+    }
+
+    CHANNEL_DEBUG(channel, "display_config: flags: %u, depth: %u", config.flags, config.depth);
+
+    agent_msg_queue(channel, VD_AGENT_DISPLAY_CONFIG, sizeof(VDAgentDisplayConfig), &config);
+}
+
+/* any context: the message is not flushed immediately,
+   you can wakeup() the channel coroutine or send_msg_queue() */
+static void agent_announce_caps(SpiceMainChannel *channel)
+{
+    SpiceMainChannelPrivate *c = channel->priv;
+    VDAgentAnnounceCapabilities *caps;
+    size_t size;
+
+    if (!c->agent_connected)
+        return;
+
+    size = sizeof(VDAgentAnnounceCapabilities) + VD_AGENT_CAPS_BYTES;
+    caps = g_malloc0(size);
+    if (!c->agent_caps_received)
+        caps->request = 1;
+    VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_MOUSE_STATE);
+    VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_MONITORS_CONFIG);
+    VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_REPLY);
+    VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_DISPLAY_CONFIG);
+    VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_CLIPBOARD_BY_DEMAND);
+    VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_CLIPBOARD_SELECTION);
+
+    agent_msg_queue(channel, VD_AGENT_ANNOUNCE_CAPABILITIES, size, caps);
+    g_free(caps);
+}
+
+/* any context: the message is not flushed immediately,
+   you can wakeup() the channel coroutine or send_msg_queue() */
+static void agent_clipboard_grab(SpiceMainChannel *channel, guint selection,
+                                 guint32 *types, int ntypes)
+{
+    SpiceMainChannelPrivate *c = channel->priv;
+    guint8 *msg;
+    VDAgentClipboardGrab *grab;
+    size_t size;
+    int i;
+
+    if (!c->agent_connected)
+        return;
+
+    g_return_if_fail(test_agent_cap(channel, VD_AGENT_CAP_CLIPBOARD_BY_DEMAND));
+
+    size = sizeof(VDAgentClipboardGrab) + sizeof(uint32_t) * ntypes;
+    if (test_agent_cap(channel, VD_AGENT_CAP_CLIPBOARD_SELECTION)) {
+        size += 4;
+    } else if (selection != VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD) {
+        CHANNEL_DEBUG(channel, "Ignoring clipboard grab");
+        return;
+    }
+
+    msg = g_alloca(size);
+    memset(msg, 0, size);
+
+    grab = (VDAgentClipboardGrab *)msg;
+
+    if (test_agent_cap(channel, VD_AGENT_CAP_CLIPBOARD_SELECTION)) {
+        msg[0] = selection;
+        grab = (VDAgentClipboardGrab *)(msg + 4);
+    }
+
+    for (i = 0; i < ntypes; i++) {
+        grab->types[i] = types[i];
+    }
+
+    agent_msg_queue(channel, VD_AGENT_CLIPBOARD_GRAB, size, msg);
+}
+
+/* any context: the message is not flushed immediately,
+   you can wakeup() the channel coroutine or send_msg_queue() */
+static void agent_clipboard_notify(SpiceMainChannel *self, guint selection,
+                                   guint32 type, const guchar *data, size_t size)
+{
+    SpiceMainChannelPrivate *c = self->priv;
+    VDAgentClipboard *cb;
+    guint8 *msg;
+    size_t msgsize;
+    gint max_clipboard = spice_main_get_max_clipboard(self);
+
+    g_return_if_fail(c->agent_connected);
+    g_return_if_fail(test_agent_cap(self, VD_AGENT_CAP_CLIPBOARD_BY_DEMAND));
+    g_return_if_fail(max_clipboard == -1 || size < max_clipboard);
+
+    msgsize = sizeof(VDAgentClipboard);
+    if (test_agent_cap(self, VD_AGENT_CAP_CLIPBOARD_SELECTION)) {
+        msgsize += 4;
+    } else if (selection != VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD) {
+        CHANNEL_DEBUG(self, "Ignoring clipboard notify");
+        return;
+    }
+
+    msg = g_alloca(msgsize);
+    memset(msg, 0, msgsize);
+
+    cb = (VDAgentClipboard *)msg;
+
+    if (test_agent_cap(self, VD_AGENT_CAP_CLIPBOARD_SELECTION)) {
+        msg[0] = selection;
+        cb = (VDAgentClipboard *)(msg + 4);
+    }
+
+    cb->type = type;
+    agent_msg_queue_many(self, VD_AGENT_CLIPBOARD, msg, msgsize, data, size, NULL);
+}
+
+/* any context: the message is not flushed immediately,
+   you can wakeup() the channel coroutine or send_msg_queue() */
+static void agent_clipboard_request(SpiceMainChannel *channel, guint selection, guint32 type)
+{
+    SpiceMainChannelPrivate *c = channel->priv;
+    VDAgentClipboardRequest *request;
+    guint8 *msg;
+    size_t msgsize;
+
+    g_return_if_fail(c->agent_connected);
+    g_return_if_fail(test_agent_cap(channel, VD_AGENT_CAP_CLIPBOARD_BY_DEMAND));
+
+    msgsize = sizeof(VDAgentClipboardRequest);
+    if (test_agent_cap(channel, VD_AGENT_CAP_CLIPBOARD_SELECTION)) {
+        msgsize += 4;
+    } else if (selection != VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD) {
+        SPICE_DEBUG("Ignoring clipboard request");
+        return;
+    }
+
+    msg = g_alloca(msgsize);
+    memset(msg, 0, msgsize);
+
+    request = (VDAgentClipboardRequest *)msg;
+
+    if (test_agent_cap(channel, VD_AGENT_CAP_CLIPBOARD_SELECTION)) {
+        msg[0] = selection;
+        request = (VDAgentClipboardRequest *)(msg + 4);
+    }
+
+    request->type = type;
+
+    agent_msg_queue(channel, VD_AGENT_CLIPBOARD_REQUEST, msgsize, msg);
+}
+
+/* any context: the message is not flushed immediately,
+   you can wakeup() the channel coroutine or send_msg_queue() */
+static void agent_clipboard_release(SpiceMainChannel *channel, guint selection)
+{
+    SpiceMainChannelPrivate *c = channel->priv;
+    guint8 msg[4] = { 0, };
+    guint8 msgsize = 0;
+
+    g_return_if_fail(c->agent_connected);
+    g_return_if_fail(test_agent_cap(channel, VD_AGENT_CAP_CLIPBOARD_BY_DEMAND));
+
+    if (test_agent_cap(channel, VD_AGENT_CAP_CLIPBOARD_SELECTION)) {
+        msg[0] = selection;
+        msgsize += 4;
+    } else if (selection != VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD) {
+        SPICE_DEBUG("Ignoring clipboard release");
+        return;
+    }
+
+    agent_msg_queue(channel, VD_AGENT_CLIPBOARD_RELEASE, msgsize, msg);
+}
+
+/* main context*/
+static gboolean timer_set_display(gpointer data)
+{
+    SpiceMainChannel *channel = data;
+    SpiceMainChannelPrivate *c = channel->priv;
+    SpiceSession *session;
+    gint i;
+
+    c->timer_id = 0;
+    if (!c->agent_connected)
+        return FALSE;
+
+    session = spice_channel_get_session(SPICE_CHANNEL(channel));
+
+    /* ensure we have an explicit monitor configuration at least for
+       number of display channels */
+    for (i = 0; i < spice_session_get_n_display_channels(session); i++)
+        if (!c->display[i].enabled_set) {
+            SPICE_DEBUG("Not sending monitors config, missing monitors");
+            return FALSE;
+        }
+
+    spice_main_send_monitor_config(channel);
+
+    return FALSE;
+}
+
+/* any context  */
+static void update_display_timer(SpiceMainChannel *channel, guint seconds)
+{
+    SpiceMainChannelPrivate *c = channel->priv;
+
+    if (c->timer_id)
+        g_source_remove(c->timer_id);
+
+    c->timer_id = g_timeout_add_seconds(seconds, timer_set_display, channel);
+}
+
+/* coroutine context  */
+static void set_agent_connected(SpiceMainChannel *channel, gboolean connected)
+{
+    SpiceMainChannelPrivate *c = channel->priv;
+
+    SPICE_DEBUG("agent connected: %s", spice_yes_no(connected));
+    if (connected != c->agent_connected) {
+        c->agent_connected = connected;
+        g_coroutine_object_notify(G_OBJECT(channel), "agent-connected");
+    }
+    if (!connected)
+        spice_main_channel_reset_agent(SPICE_MAIN_CHANNEL(channel));
+
+    g_coroutine_signal_emit(channel, signals[SPICE_MAIN_AGENT_UPDATE], 0);
+}
+
+/* coroutine context  */
+static void agent_start(SpiceMainChannel *channel)
+{
+    SpiceMainChannelPrivate *c = channel->priv;
+    SpiceMsgcMainAgentStart agent_start = {
+        .num_tokens = ~0,
+    };
+    SpiceMsgOut *out;
+
+    c->agent_volume_playback_sync = FALSE;
+    c->agent_volume_record_sync = FALSE;
+    c->agent_caps_received = false;
+    set_agent_connected(channel, TRUE);
+
+    out = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_MAIN_AGENT_START);
+    out->marshallers->msgc_main_agent_start(out->marshaller, &agent_start);
+    spice_msg_out_send_internal(out);
+
+    if (c->agent_connected) {
+        agent_announce_caps(channel);
+        agent_send_msg_queue(channel);
+    }
+}
+
+/* coroutine context  */
+static void agent_stopped(SpiceMainChannel *channel)
+{
+    set_agent_connected(channel, FALSE);
+}
+
+/* coroutine context */
+static void set_mouse_mode(SpiceMainChannel *channel, uint32_t supported, uint32_t current)
+{
+    SpiceMainChannelPrivate *c = channel->priv;
+
+    if (c->mouse_mode != current) {
+        c->mouse_mode = current;
+        g_coroutine_signal_emit(channel, signals[SPICE_MAIN_MOUSE_UPDATE], 0);
+        g_coroutine_object_notify(G_OBJECT(channel), "mouse-mode");
+    }
+
+    /* switch to client mode if possible */
+    if (!spice_channel_get_read_only(SPICE_CHANNEL(channel)) &&
+        supported & SPICE_MOUSE_MODE_CLIENT &&
+        current != SPICE_MOUSE_MODE_CLIENT) {
+        SpiceMsgcMainMouseModeRequest req = {
+            .mode = SPICE_MOUSE_MODE_CLIENT,
+        };
+        SpiceMsgOut *out;
+
+        out = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_MAIN_MOUSE_MODE_REQUEST);
+        out->marshallers->msgc_main_mouse_mode_request(out->marshaller, &req);
+        spice_msg_out_send_internal(out);
+    }
+}
+
+/* coroutine context */
+static void main_handle_init(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
+    SpiceMsgMainInit *init = spice_msg_in_parsed(in);
+    SpiceSession *session;
+    SpiceMsgOut *out;
+
+    session = spice_channel_get_session(channel);
+    spice_session_set_connection_id(session, init->session_id);
+
+    set_mouse_mode(SPICE_MAIN_CHANNEL(channel), init->supported_mouse_modes,
+                   init->current_mouse_mode);
+
+    spice_session_set_mm_time(session, init->multi_media_time);
+    spice_session_set_caches_hints(session, init->ram_hint, init->display_channels_hint);
+
+    c->agent_tokens = init->agent_tokens;
+    if (init->agent_connected)
+        agent_start(SPICE_MAIN_CHANNEL(channel));
+
+    if (spice_session_migrate_after_main_init(session))
+        return;
+
+    out = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_MAIN_ATTACH_CHANNELS);
+    spice_msg_out_send_internal(out);
+}
+
+/* coroutine context */
+static void main_handle_name(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceMsgMainName *name = spice_msg_in_parsed(in);
+    SpiceSession *session = spice_channel_get_session(channel);
+
+    SPICE_DEBUG("server name: %s", name->name);
+    spice_session_set_name(session, (const gchar *)name->name);
+}
+
+/* coroutine context */
+static void main_handle_uuid(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceMsgMainUuid *uuid = spice_msg_in_parsed(in);
+    SpiceSession *session = spice_channel_get_session(channel);
+    gchar *uuid_str = spice_uuid_to_string(uuid->uuid);
+
+    SPICE_DEBUG("server uuid: %s", uuid_str);
+    spice_session_set_uuid(session, uuid->uuid);
+
+    g_free(uuid_str);
+}
+
+/* coroutine context */
+static void main_handle_mm_time(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceSession *session;
+    SpiceMsgMainMultiMediaTime *msg = spice_msg_in_parsed(in);
+
+    session = spice_channel_get_session(channel);
+    spice_session_set_mm_time(session, msg->time);
+}
+
+typedef struct channel_new {
+    SpiceSession *session;
+    int type;
+    int id;
+} channel_new_t;
+
+/* main context */
+static gboolean _channel_new(channel_new_t *c)
+{
+    g_return_val_if_fail(c != NULL, FALSE);
+
+    spice_channel_new(c->session, c->type, c->id);
+
+    g_object_unref(c->session);
+    g_free(c);
+
+    return FALSE;
+}
+
+/* coroutine context */
+static void main_handle_channels_list(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceMsgChannels *msg = spice_msg_in_parsed(in);
+    SpiceSession *session;
+    int i;
+
+    session = spice_channel_get_session(channel);
+
+    /* guarantee that uuid is notified before setting up the channels, even if
+     * the server is older and doesn't actually send the uuid */
+    g_coroutine_object_notify(G_OBJECT(session), "uuid");
+
+    for (i = 0; i < msg->num_of_channels; i++) {
+        channel_new_t *c;
+
+        c = g_new(channel_new_t, 1);
+        c->session = g_object_ref(session);
+        c->type = msg->channels[i].type;
+        c->id = msg->channels[i].id;
+        /* no need to explicitely switch to main context, since
+           synchronous call is not needed. */
+        /* no need to track idle, session is refed */
+        g_idle_add((GSourceFunc)_channel_new, c);
+    }
+}
+
+/* coroutine context */
+static void main_handle_mouse_mode(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceMsgMainMouseMode *msg = spice_msg_in_parsed(in);
+    set_mouse_mode(SPICE_MAIN_CHANNEL(channel), msg->supported_modes, msg->current_mode);
+}
+
+/* coroutine context */
+static void main_handle_agent_connected(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    agent_start(SPICE_MAIN_CHANNEL(channel));
+}
+
+/* coroutine context */
+static void main_handle_agent_connected_tokens(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
+    SpiceMsgMainAgentConnectedTokens *msg = spice_msg_in_parsed(in);
+
+    c->agent_tokens = msg->num_tokens;
+    agent_start(SPICE_MAIN_CHANNEL(channel));
+}
+
+/* coroutine context */
+static void main_handle_agent_disconnected(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    agent_stopped(SPICE_MAIN_CHANNEL(channel));
+}
+
+static void file_xfer_task_free(SpiceFileXferTask *task)
+{
+    SpiceMainChannelPrivate *c;
+
+    g_return_if_fail(task != NULL);
+
+    c = task->channel->priv;
+    g_hash_table_remove(c->file_xfer_tasks, GUINT_TO_POINTER(task->id));
+
+    g_clear_object(&task->channel);
+    g_clear_object(&task->file);
+    g_clear_object(&task->file_stream);
+    g_free(task);
+}
+
+/* main context */
+static void file_xfer_close_cb(GObject      *object,
+                               GAsyncResult *close_res,
+                               gpointer      user_data)
+{
+    GSimpleAsyncResult *res;
+    SpiceFileXferTask *task;
+    GError *error = NULL;
+
+    task = user_data;
+
+    if (object) {
+        GInputStream *stream = G_INPUT_STREAM(object);
+        g_input_stream_close_finish(stream, close_res, &error);
+        if (error) {
+            /* This error dont need to report to user, just print a log */
+            SPICE_DEBUG("close file error: %s", error->message);
+            g_clear_error(&error);
+        }
+    }
+
+    /* Notify to user that files have been transferred or something error
+       happened. */
+    res = g_simple_async_result_new(G_OBJECT(task->channel),
+                                    task->callback,
+                                    task->user_data,
+                                    spice_main_file_copy_async);
+    if (task->error) {
+        g_simple_async_result_take_error(res, task->error);
+        g_simple_async_result_set_op_res_gboolean(res, FALSE);
+    } else {
+        g_simple_async_result_set_op_res_gboolean(res, TRUE);
+    }
+    g_simple_async_result_complete_in_idle(res);
+    g_object_unref(res);
+
+    file_xfer_task_free(task);
+}
+
+static void file_xfer_data_flushed_cb(GObject *source_object,
+                                      GAsyncResult *res,
+                                      gpointer user_data)
+{
+    SpiceFileXferTask *task = user_data;
+    SpiceMainChannel *channel = (SpiceMainChannel *)source_object;
+    GError *error = NULL;
+
+    task->pending = FALSE;
+    file_xfer_flush_finish(channel, res, &error);
+    if (error || task->error) {
+        file_xfer_completed(task, error);
+        return;
+    }
+
+    if (task->progress_callback)
+        task->progress_callback(task->read_bytes, task->file_size,
+                                task->progress_callback_data);
+
+    /* Read more data */
+    file_xfer_continue_read(task);
+}
+
+static void file_xfer_queue(SpiceFileXferTask *task, int data_size)
+{
+    VDAgentFileXferDataMessage msg;
+    SpiceMainChannel *channel = SPICE_MAIN_CHANNEL(task->channel);
+
+    msg.id = task->id;
+    msg.size = data_size;
+    agent_msg_queue_many(channel, VD_AGENT_FILE_XFER_DATA,
+                         &msg, sizeof(msg),
+                         task->buffer, data_size, NULL);
+    spice_channel_wakeup(SPICE_CHANNEL(channel), FALSE);
+}
+
+/* main context */
+static void file_xfer_read_cb(GObject *source_object,
+                              GAsyncResult *res,
+                              gpointer user_data)
+{
+    SpiceFileXferTask *task = user_data;
+    SpiceMainChannel *channel = task->channel;
+    gssize count;
+    GError *error = NULL;
+
+    task->pending = FALSE;
+    count = g_input_stream_read_finish(G_INPUT_STREAM(task->file_stream),
+                                       res, &error);
+    /* Check for pending earlier errors */
+    if (task->error) {
+        file_xfer_completed(task, error);
+        return;
+    }
+
+    if (count > 0 || task->file_size == 0) {
+        task->read_bytes += count;
+        file_xfer_queue(task, count);
+        if (count == 0)
+            return;
+        file_xfer_flush_async(channel, task->cancellable,
+                              file_xfer_data_flushed_cb, task);
+        task->pending = TRUE;
+    } else if (error) {
+        VDAgentFileXferStatusMessage msg = {
+            .id = task->id,
+            .result = VD_AGENT_FILE_XFER_STATUS_ERROR,
+        };
+        agent_msg_queue_many(task->channel, VD_AGENT_FILE_XFER_STATUS,
+                             &msg, sizeof(msg), NULL);
+        spice_channel_wakeup(SPICE_CHANNEL(task->channel), FALSE);
+        file_xfer_completed(task, error);
+    }
+    /* else EOF, do nothing (wait for VD_AGENT_FILE_XFER_STATUS from agent) */
+}
+
+/* coroutine context */
+static void file_xfer_continue_read(SpiceFileXferTask *task)
+{
+    g_input_stream_read_async(G_INPUT_STREAM(task->file_stream),
+                              task->buffer,
+                              FILE_XFER_CHUNK_SIZE,
+                              G_PRIORITY_DEFAULT,
+                              task->cancellable,
+                              file_xfer_read_cb,
+                              task);
+    task->pending = TRUE;
+}
+
+/* coroutine context */
+static void file_xfer_handle_status(SpiceMainChannel *channel,
+                                    VDAgentFileXferStatusMessage *msg)
+{
+    SpiceMainChannelPrivate *c = channel->priv;
+    SpiceFileXferTask *task;
+    GError *error = NULL;
+
+
+    task = g_hash_table_lookup(c->file_xfer_tasks, GUINT_TO_POINTER(msg->id));
+    if (task == NULL) {
+        SPICE_DEBUG("cannot find task %d", msg->id);
+        return;
+    }
+
+    SPICE_DEBUG("task %d received response %d", msg->id, msg->result);
+
+    switch (msg->result) {
+    case VD_AGENT_FILE_XFER_STATUS_CAN_SEND_DATA:
+        if (task->pending) {
+            error = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                           "transfer received CAN_SEND_DATA in pending state");
+            break;
+        }
+        file_xfer_continue_read(task);
+        return;
+    case VD_AGENT_FILE_XFER_STATUS_CANCELLED:
+        error = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                            "transfer is cancelled by spice agent");
+        break;
+    case VD_AGENT_FILE_XFER_STATUS_ERROR:
+        error = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                            "some errors occurred in the spice agent");
+        break;
+    case VD_AGENT_FILE_XFER_STATUS_SUCCESS:
+        if (task->pending)
+            error = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                                "transfer received success in pending state");
+        break;
+    default:
+        g_warn_if_reached();
+        error = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                            "unhandled status type: %u", msg->result);
+        break;
+    }
+
+    file_xfer_completed(task, error);
+}
+
+/* any context: the message is not flushed immediately,
+   you can wakeup() the channel coroutine or send_msg_queue() */
+static void agent_max_clipboard(SpiceMainChannel *self)
+{
+    VDAgentMaxClipboard msg = { .max = spice_main_get_max_clipboard(self) };
+
+    if (!test_agent_cap(self, VD_AGENT_CAP_MAX_CLIPBOARD))
+        return;
+
+    agent_msg_queue(self, VD_AGENT_MAX_CLIPBOARD, sizeof(VDAgentMaxClipboard), &msg);
+}
+
+static void spice_main_set_max_clipboard(SpiceMainChannel *self, gint max)
+{
+    SpiceMainChannelPrivate *c;
+
+    g_return_if_fail(SPICE_IS_MAIN_CHANNEL(self));
+    g_return_if_fail(max >= -1);
+
+    c = self->priv;
+    if (max == spice_main_get_max_clipboard(self))
+        return;
+
+    c->max_clipboard = max;
+    agent_max_clipboard(self);
+    spice_channel_wakeup(SPICE_CHANNEL(self), FALSE);
+}
+
+/* coroutine context */
+static void main_agent_handle_msg(SpiceChannel *channel,
+                                  VDAgentMessage *msg, gpointer payload)
+{
+    SpiceMainChannel *self = SPICE_MAIN_CHANNEL(channel);
+    SpiceMainChannelPrivate *c = self->priv;
+    guint8 selection = VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD;
+
+    g_return_if_fail(msg->protocol == VD_AGENT_PROTOCOL);
+
+    switch (msg->type) {
+    case VD_AGENT_CLIPBOARD_RELEASE:
+    case VD_AGENT_CLIPBOARD_REQUEST:
+    case VD_AGENT_CLIPBOARD_GRAB:
+    case VD_AGENT_CLIPBOARD:
+        if (test_agent_cap(self, VD_AGENT_CAP_CLIPBOARD_SELECTION)) {
+            selection = *((guint8*)payload);
+            payload = ((guint8*)payload) + 4;
+            msg->size -= 4;
+        }
+        break;
+    default:
+        break;
+    }
+
+    switch (msg->type) {
+    case VD_AGENT_ANNOUNCE_CAPABILITIES:
+    {
+        VDAgentAnnounceCapabilities *caps = payload;
+        int i, size;
+
+        size = VD_AGENT_CAPS_SIZE_FROM_MSG_SIZE(msg->size);
+        if (size > VD_AGENT_CAPS_SIZE)
+            size = VD_AGENT_CAPS_SIZE;
+        memset(c->agent_caps, 0, sizeof(c->agent_caps));
+        for (i = 0; i < size * 32; i++) {
+            if (!VD_AGENT_HAS_CAPABILITY(caps->caps, size, i))
+                continue;
+            SPICE_DEBUG("%s: cap: %d (%s)", __FUNCTION__,
+                        i, NAME(agent_caps, i));
+            VD_AGENT_SET_CAPABILITY(c->agent_caps, i);
+        }
+        c->agent_caps_received = true;
+        g_coroutine_signal_emit(self, signals[SPICE_MAIN_AGENT_UPDATE], 0);
+        update_display_timer(SPICE_MAIN_CHANNEL(channel), 0);
+
+        if (caps->request)
+            agent_announce_caps(self);
+
+        if (test_agent_cap(self, VD_AGENT_CAP_DISPLAY_CONFIG) &&
+            !c->agent_display_config_sent) {
+            agent_display_config(self);
+            c->agent_display_config_sent = true;
+        }
+
+        agent_sync_audio_playback(self);
+        agent_sync_audio_record(self);
+
+        agent_max_clipboard(self);
+
+        agent_send_msg_queue(self);
+
+        break;
+    }
+    case VD_AGENT_CLIPBOARD:
+    {
+        VDAgentClipboard *cb = payload;
+        g_coroutine_signal_emit(self, signals[SPICE_MAIN_CLIPBOARD_SELECTION], 0, selection,
+                                cb->type, cb->data, msg->size - sizeof(VDAgentClipboard));
+
+       if (selection == VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD)
+           g_coroutine_signal_emit(self, signals[SPICE_MAIN_CLIPBOARD], 0,
+                              cb->type, cb->data, msg->size - sizeof(VDAgentClipboard));
+        break;
+    }
+    case VD_AGENT_CLIPBOARD_GRAB:
+    {
+        gboolean ret;
+        g_coroutine_signal_emit(self, signals[SPICE_MAIN_CLIPBOARD_SELECTION_GRAB], 0, selection,
+                          (guint8*)payload, msg->size / sizeof(uint32_t), &ret);
+        if (selection == VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD)
+            g_coroutine_signal_emit(self, signals[SPICE_MAIN_CLIPBOARD_GRAB], 0,
+                              payload, msg->size / sizeof(uint32_t), &ret);
+        break;
+    }
+    case VD_AGENT_CLIPBOARD_REQUEST:
+    {
+        gboolean ret;
+        VDAgentClipboardRequest *req = payload;
+        g_coroutine_signal_emit(self, signals[SPICE_MAIN_CLIPBOARD_SELECTION_REQUEST], 0, selection,
+                          req->type, &ret);
+
+        if (selection == VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD)
+            g_coroutine_signal_emit(self, signals[SPICE_MAIN_CLIPBOARD_REQUEST], 0,
+                              req->type, &ret);
+        break;
+    }
+    case VD_AGENT_CLIPBOARD_RELEASE:
+    {
+        g_coroutine_signal_emit(self, signals[SPICE_MAIN_CLIPBOARD_SELECTION_RELEASE], 0, selection);
+
+        if (selection == VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD)
+            g_coroutine_signal_emit(self, signals[SPICE_MAIN_CLIPBOARD_RELEASE], 0);
+        break;
+    }
+    case VD_AGENT_REPLY:
+    {
+        VDAgentReply *reply = payload;
+        SPICE_DEBUG("%s: reply: type %d, %s", __FUNCTION__, reply->type,
+                    reply->error == VD_AGENT_SUCCESS ? "success" : "error");
+        break;
+    }
+    case VD_AGENT_FILE_XFER_STATUS:
+        file_xfer_handle_status(self, payload);
+        break;
+    default:
+        g_warning("unhandled agent message type: %u (%s), size %u",
+                  msg->type, NAME(agent_msg_types, msg->type), msg->size);
+    }
+}
+
+/* coroutine context */
+static void main_handle_agent_data_msg(SpiceChannel* channel, int* msg_size, guchar** msg_pos)
+{
+    SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
+    int n;
+
+    if (c->agent_msg_pos < sizeof(VDAgentMessage)) {
+        n = MIN(sizeof(VDAgentMessage) - c->agent_msg_pos, *msg_size);
+        memcpy((uint8_t*)&c->agent_msg + c->agent_msg_pos, *msg_pos, n);
+        c->agent_msg_pos += n;
+        *msg_size -= n;
+        *msg_pos += n;
+        if (c->agent_msg_pos == sizeof(VDAgentMessage)) {
+            SPICE_DEBUG("agent msg start: msg_size=%d, protocol=%d, type=%d",
+                        c->agent_msg.size, c->agent_msg.protocol, c->agent_msg.type);
+            g_return_if_fail(c->agent_msg_data == NULL);
+            c->agent_msg_data = g_malloc0(c->agent_msg.size);
+        }
+    }
+
+    if (c->agent_msg_pos >= sizeof(VDAgentMessage)) {
+        n = MIN(sizeof(VDAgentMessage) + c->agent_msg.size - c->agent_msg_pos, *msg_size);
+        memcpy(c->agent_msg_data + c->agent_msg_pos - sizeof(VDAgentMessage), *msg_pos, n);
+        c->agent_msg_pos += n;
+        *msg_size -= n;
+        *msg_pos += n;
+    }
+
+    if (c->agent_msg_pos == sizeof(VDAgentMessage) + c->agent_msg.size) {
+        main_agent_handle_msg(channel, &c->agent_msg, c->agent_msg_data);
+        g_free(c->agent_msg_data);
+        c->agent_msg_data = NULL;
+        c->agent_msg_pos = 0;
+    }
+}
+
+/* coroutine context */
+static void main_handle_agent_data(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
+    guint8 *data;
+    int len;
+
+    g_warn_if_fail(c->agent_connected);
+
+    /* shortcut to avoid extra message allocation & copy if possible */
+    if (c->agent_msg_pos == 0) {
+        VDAgentMessage *msg;
+        guint msg_size;
+
+        msg = spice_msg_in_raw(in, &len);
+        msg_size = msg->size;
+
+        if (msg_size + sizeof(VDAgentMessage) == len) {
+            main_agent_handle_msg(channel, msg, msg->data);
+            return;
+        }
+    }
+
+    data = spice_msg_in_raw(in, &len);
+    while (len > 0) {
+        main_handle_agent_data_msg(channel, &len, &data);
+    }
+}
+
+/* coroutine context */
+static void main_handle_agent_token(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceMsgMainAgentTokens *tokens = spice_msg_in_parsed(in);
+    SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
+
+    c->agent_tokens += tokens->num_tokens;
+
+    agent_send_msg_queue(SPICE_MAIN_CHANNEL(channel));
+}
+
+/* main context */
+static void migrate_channel_new_cb(SpiceSession *s, SpiceChannel *channel, gpointer data)
+{
+    g_signal_connect(channel, "channel-event",
+                     G_CALLBACK(migrate_channel_event_cb), data);
+}
+
+static SpiceChannel* migrate_channel_connect(spice_migrate *mig, int type, int id)
+{
+    SPICE_DEBUG("migrate_channel_connect %d:%d", type, id);
+
+    SpiceChannel *newc = spice_channel_new(mig->session, type, id);
+    spice_channel_connect(newc);
+    mig->nchannels++;
+
+    return newc;
+}
+
+/* coroutine context */
+static void spice_main_channel_send_migration_handshake(SpiceChannel *channel)
+{
+    SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
+
+    if (!spice_channel_test_capability(channel, SPICE_MAIN_CAP_SEAMLESS_MIGRATE)) {
+        c->migrate_data->do_seamless = false;
+        g_idle_add(main_migrate_handshake_done, c->migrate_data);
+    } else {
+        SpiceMsgcMainMigrateDstDoSeamless msg_data;
+        SpiceMsgOut *msg_out;
+
+        msg_data.src_version = c->migrate_data->src_mig_version;
+
+        msg_out = spice_msg_out_new(channel, SPICE_MSGC_MAIN_MIGRATE_DST_DO_SEAMLESS);
+        msg_out->marshallers->msgc_main_migrate_dst_do_seamless(msg_out->marshaller, &msg_data);
+        spice_msg_out_send_internal(msg_out);
+    }
+}
+
+/* main context */
+static void migrate_channel_event_cb(SpiceChannel *channel, SpiceChannelEvent event,
+                                     gpointer data)
+{
+    spice_migrate *mig = data;
+    SpiceChannelPrivate  *c = SPICE_CHANNEL(channel)->priv;
+    SpiceSession *session;
+
+    g_return_if_fail(mig->nchannels > 0);
+    g_signal_handlers_disconnect_by_func(channel, migrate_channel_event_cb, data);
+
+    session = spice_channel_get_session(mig->src_channel);
+
+    switch (event) {
+    case SPICE_CHANNEL_OPENED:
+
+        if (c->channel_type == SPICE_CHANNEL_MAIN) {
+            if (mig->do_seamless) {
+                SpiceMainChannelPrivate *main_priv = SPICE_MAIN_CHANNEL(channel)->priv;
+
+                c->state = SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE;
+                mig->dst_channel = channel;
+                main_priv->migrate_data = mig;
+            } else {
+                c->state = SPICE_CHANNEL_STATE_MIGRATING;
+                mig->nchannels--;
+            }
+            /* now connect the rest of the channels */
+            GList *channels, *l;
+            l = channels = spice_session_get_channels(session);
+            while (l != NULL) {
+                SpiceChannelPrivate  *curc = SPICE_CHANNEL(l->data)->priv;
+                l = l->next;
+                if (curc->channel_type == SPICE_CHANNEL_MAIN)
+                    continue;
+                migrate_channel_connect(mig, curc->channel_type, curc->channel_id);
+            }
+            g_list_free(channels);
+        } else {
+            c->state = SPICE_CHANNEL_STATE_MIGRATING;
+            mig->nchannels--;
+        }
+
+        SPICE_DEBUG("migration: channel opened chan:%p, left %d", channel, mig->nchannels);
+        if (mig->nchannels == 0)
+            coroutine_yieldto(mig->from, NULL);
+        break;
+    default:
+        SPICE_DEBUG("error or unhandled channel event during migration: %d", event);
+        /* go back to main channel to report error */
+        coroutine_yieldto(mig->from, NULL);
+    }
+}
+
+/* main context */
+static gboolean main_migrate_handshake_done(gpointer data)
+{
+    spice_migrate *mig = data;
+    SpiceChannelPrivate  *c = SPICE_CHANNEL(mig->dst_channel)->priv;
+
+    g_return_val_if_fail(c->channel_type == SPICE_CHANNEL_MAIN, FALSE);
+    g_return_val_if_fail(c->state == SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE, FALSE);
+
+    c->state = SPICE_CHANNEL_STATE_MIGRATING;
+    mig->nchannels--;
+    if (mig->nchannels == 0)
+        coroutine_yieldto(mig->from, NULL);
+    return FALSE;
+}
+
+#ifdef __GNUC__
+typedef struct __attribute__ ((__packed__)) OldRedMigrationBegin {
+#else
+typedef struct __declspec(align(1)) OldRedMigrationBegin {
+#endif
+    uint16_t port;
+    uint16_t sport;
+    char host[0];
+} OldRedMigrationBegin;
+
+/* main context */
+static gboolean migrate_connect(gpointer data)
+{
+    spice_migrate *mig = data;
+    SpiceChannelPrivate  *c;
+    int port, sport;
+    const char *host;
+
+    g_return_val_if_fail(mig != NULL, FALSE);
+    g_return_val_if_fail(mig->info != NULL, FALSE);
+    g_return_val_if_fail(mig->nchannels == 0, FALSE);
+    c = SPICE_CHANNEL(mig->src_channel)->priv;
+    g_return_val_if_fail(c != NULL, FALSE);
+    g_return_val_if_fail(mig->session != NULL, FALSE);
+
+    spice_session_set_migration_state(mig->session, SPICE_SESSION_MIGRATION_CONNECTING);
+
+    if ((c->peer_hdr.major_version == 1) &&
+        (c->peer_hdr.minor_version < 1)) {
+        OldRedMigrationBegin *info = (OldRedMigrationBegin *)mig->info;
+        SPICE_DEBUG("migrate_begin old %s %d %d",
+                    info->host, info->port, info->sport);
+        port = info->port;
+        sport = info->sport;
+        host = info->host;
+    } else {
+        SpiceMigrationDstInfo *info = mig->info;
+        SPICE_DEBUG("migrate_begin %d %s %d %d",
+                    info->host_size, info->host_data, info->port, info->sport);
+        port = info->port;
+        sport = info->sport;
+        host = (char*)info->host_data;
+
+        if ((c->peer_hdr.major_version == 1) ||
+            (c->peer_hdr.major_version == 2 && c->peer_hdr.minor_version < 1)) {
+            GByteArray *pubkey = g_byte_array_new();
+
+            g_byte_array_append(pubkey, info->pub_key_data, info->pub_key_size);
+            g_object_set(mig->session,
+                         "pubkey", pubkey,
+                         "verify", SPICE_SESSION_VERIFY_PUBKEY,
+                         NULL);
+            g_byte_array_unref(pubkey);
+        } else if (info->cert_subject_size == 0 ||
+                   strlen((const char*)info->cert_subject_data) == 0) {
+            /* only verify hostname if no cert subject */
+            g_object_set(mig->session, "verify", SPICE_SESSION_VERIFY_HOSTNAME, NULL);
+        } else {
+            gchar *subject = g_alloca(info->cert_subject_size + 1);
+            strncpy(subject, (const char*)info->cert_subject_data, info->cert_subject_size);
+            subject[info->cert_subject_size] = '\0';
+
+            // session data are already copied
+            g_object_set(mig->session,
+                         "cert-subject", subject,
+                         "verify", SPICE_SESSION_VERIFY_SUBJECT,
+                         NULL);
+        }
+    }
+
+    if (g_getenv("SPICE_MIG_HOST"))
+        host = g_getenv("SPICE_MIG_HOST");
+
+    g_object_set(mig->session, "host", host, NULL);
+    spice_session_set_port(mig->session, port, FALSE);
+    spice_session_set_port(mig->session, sport, TRUE);
+    g_signal_connect(mig->session, "channel-new",
+                     G_CALLBACK(migrate_channel_new_cb), mig);
+
+    g_signal_emit(mig->src_channel, signals[SPICE_MIGRATION_STARTED], 0,
+                  mig->session);
+
+    /* the migration process is in 2 steps, first the main channel and
+       then the rest of the channels */
+    migrate_channel_connect(mig, SPICE_CHANNEL_MAIN, 0);
+
+    return FALSE;
+}
+
+/* coroutine context */
+static void main_migrate_connect(SpiceChannel *channel,
+                                 SpiceMigrationDstInfo *dst_info, bool do_seamless,
+                                 uint32_t src_mig_version)
+{
+    SpiceMainChannelPrivate *main_priv = SPICE_MAIN_CHANNEL(channel)->priv;
+    int reply_type = SPICE_MSGC_MAIN_MIGRATE_CONNECT_ERROR;
+    spice_migrate mig = { 0, };
+    SpiceMsgOut *out;
+    SpiceSession *session;
+
+    mig.src_channel = channel;
+    mig.info = dst_info;
+    mig.from = coroutine_self();
+    mig.do_seamless = do_seamless;
+    mig.src_mig_version = src_mig_version;
+
+    CHANNEL_DEBUG(channel, "migrate connect");
+    session = spice_channel_get_session(channel);
+    mig.session = spice_session_new_from_session(session);
+    if (mig.session == NULL)
+        goto end;
+    if (!spice_session_set_migration_session(session, mig.session))
+        goto end;
+
+    main_priv->migrate_data = &mig;
+
+    /* no need to track idle, call is sync for this coroutine */
+    g_idle_add(migrate_connect, &mig);
+
+    /* switch to main loop and wait for connections */
+    coroutine_yield(NULL);
+
+    if (mig.nchannels != 0) {
+        CHANNEL_DEBUG(channel, "migrate failed: some channels failed to connect");
+        spice_session_abort_migration(session);
+    } else {
+        if (mig.do_seamless) {
+            SPICE_DEBUG("migration (seamless): connections all ok");
+            reply_type = SPICE_MSGC_MAIN_MIGRATE_CONNECTED_SEAMLESS;
+        } else {
+            SPICE_DEBUG("migration (semi-seamless): connections all ok");
+            reply_type = SPICE_MSGC_MAIN_MIGRATE_CONNECTED;
+        }
+        spice_session_start_migrating(spice_channel_get_session(channel),
+                                      mig.do_seamless);
+    }
+
+end:
+    CHANNEL_DEBUG(channel, "migrate connect reply %d", reply_type);
+    out = spice_msg_out_new(SPICE_CHANNEL(channel), reply_type);
+    spice_msg_out_send(out);
+}
+
+/* coroutine context */
+static void main_handle_migrate_begin(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceMsgMainMigrationBegin *msg = spice_msg_in_parsed(in);
+
+    main_migrate_connect(channel, &msg->dst_info, false, 0);
+}
+
+/* coroutine context */
+static void main_handle_migrate_begin_seamless(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceMsgMainMigrateBeginSeamless *msg = spice_msg_in_parsed(in);
+
+    main_migrate_connect(channel, &msg->dst_info, true, msg->src_mig_version);
+}
+
+static void main_handle_migrate_dst_seamless_ack(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceChannelPrivate  *c = SPICE_CHANNEL(channel)->priv;
+    SpiceMainChannelPrivate *main_priv = SPICE_MAIN_CHANNEL(channel)->priv;
+
+    g_return_if_fail(c->state == SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE);
+    main_priv->migrate_data->do_seamless = true;
+    g_idle_add(main_migrate_handshake_done, main_priv->migrate_data);
+}
+
+static void main_handle_migrate_dst_seamless_nack(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceChannelPrivate  *c = SPICE_CHANNEL(channel)->priv;
+    SpiceMainChannelPrivate *main_priv = SPICE_MAIN_CHANNEL(channel)->priv;
+
+    g_return_if_fail(c->state == SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE);
+    main_priv->migrate_data->do_seamless = false;
+    g_idle_add(main_migrate_handshake_done, main_priv->migrate_data);
+}
+
+/* main context */
+static gboolean migrate_delayed(gpointer data)
+{
+    SpiceChannel *channel = data;
+    SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
+
+    g_warn_if_fail(c->migrate_delayed_id != 0);
+    c->migrate_delayed_id = 0;
+
+    spice_session_migrate_end(channel->priv->session);
+
+    return FALSE;
+}
+
+/* coroutine context */
+static void main_handle_migrate_end(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
+
+    SPICE_DEBUG("migrate end");
+
+    g_return_if_fail(c->migrate_delayed_id == 0);
+    g_return_if_fail(spice_channel_test_capability(channel, SPICE_MAIN_CAP_SEMI_SEAMLESS_MIGRATE));
+
+    c->migrate_delayed_id = g_idle_add(migrate_delayed, channel);
+}
+
+/* main context */
+static gboolean switch_host_delayed(gpointer data)
+{
+    SpiceChannel *channel = data;
+    SpiceSession *session;
+    SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
+
+    g_warn_if_fail(c->switch_host_delayed_id != 0);
+    c->switch_host_delayed_id = 0;
+
+    session = spice_channel_get_session(channel);
+
+    spice_channel_disconnect(channel, SPICE_CHANNEL_SWITCHING);
+    spice_session_switching_disconnect(session);
+
+    return FALSE;
+}
+
+/* coroutine context */
+static void main_handle_migrate_switch_host(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceMsgMainMigrationSwitchHost *mig = spice_msg_in_parsed(in);
+    SpiceSession *session;
+    char *host = (char *)mig->host_data;
+    char *subject = NULL;
+    SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
+
+    g_return_if_fail(host[mig->host_size - 1] == '\0');
+
+    if (mig->cert_subject_size) {
+        subject = (char *)mig->cert_subject_data;
+        g_return_if_fail(subject[mig->cert_subject_size - 1] == '\0');
+    }
+
+    SPICE_DEBUG("migrate_switch %s %d %d %s",
+                host, mig->port, mig->sport, subject);
+
+    if (c->switch_host_delayed_id != 0) {
+        g_warning("Switching host already in progress, aborting it");
+        g_warn_if_fail(g_source_remove(c->switch_host_delayed_id));
+        c->switch_host_delayed_id = 0;
+    }
+
+    session = spice_channel_get_session(channel);
+    spice_session_set_migration_state(session, SPICE_SESSION_MIGRATION_SWITCHING);
+    g_object_set(session,
+                 "host", host,
+                 "cert-subject", subject,
+                 NULL);
+    spice_session_set_port(session, mig->port, FALSE);
+    spice_session_set_port(session, mig->sport, TRUE);
+
+    c->switch_host_delayed_id = g_idle_add(switch_host_delayed, channel);
+}
+
+/* coroutine context */
+static void main_handle_migrate_cancel(SpiceChannel *channel,
+                                       SpiceMsgIn *in G_GNUC_UNUSED)
+{
+    SpiceSession *session;
+
+    SPICE_DEBUG("migrate_cancel");
+    session = spice_channel_get_session(channel);
+    spice_session_abort_migration(session);
+}
+
+static void channel_set_handlers(SpiceChannelClass *klass)
+{
+    static const spice_msg_handler handlers[] = {
+        [ SPICE_MSG_MAIN_INIT ]                = main_handle_init,
+        [ SPICE_MSG_MAIN_NAME ]                = main_handle_name,
+        [ SPICE_MSG_MAIN_UUID ]                = main_handle_uuid,
+        [ SPICE_MSG_MAIN_CHANNELS_LIST ]       = main_handle_channels_list,
+        [ SPICE_MSG_MAIN_MOUSE_MODE ]          = main_handle_mouse_mode,
+        [ SPICE_MSG_MAIN_MULTI_MEDIA_TIME ]    = main_handle_mm_time,
+
+        [ SPICE_MSG_MAIN_AGENT_CONNECTED ]     = main_handle_agent_connected,
+        [ SPICE_MSG_MAIN_AGENT_DISCONNECTED ]  = main_handle_agent_disconnected,
+        [ SPICE_MSG_MAIN_AGENT_DATA ]          = main_handle_agent_data,
+        [ SPICE_MSG_MAIN_AGENT_TOKEN ]         = main_handle_agent_token,
+
+        [ SPICE_MSG_MAIN_MIGRATE_BEGIN ]       = main_handle_migrate_begin,
+        [ SPICE_MSG_MAIN_MIGRATE_END ]         = main_handle_migrate_end,
+        [ SPICE_MSG_MAIN_MIGRATE_CANCEL ]      = main_handle_migrate_cancel,
+        [ SPICE_MSG_MAIN_MIGRATE_SWITCH_HOST ] = main_handle_migrate_switch_host,
+        [ SPICE_MSG_MAIN_AGENT_CONNECTED_TOKENS ]   = main_handle_agent_connected_tokens,
+        [ SPICE_MSG_MAIN_MIGRATE_BEGIN_SEAMLESS ]   = main_handle_migrate_begin_seamless,
+        [ SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_ACK]  = main_handle_migrate_dst_seamless_ack,
+        [ SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_NACK] = main_handle_migrate_dst_seamless_nack,
+    };
+
+    spice_channel_set_handlers(klass, handlers, G_N_ELEMENTS(handlers));
+}
+
+/* coroutine context */
+static void spice_main_handle_msg(SpiceChannel *channel, SpiceMsgIn *msg)
+{
+    int type = spice_msg_in_type(msg);
+    SpiceChannelClass *parent_class;
+    SpiceChannelPrivate *c = SPICE_CHANNEL(channel)->priv;
+
+    parent_class = SPICE_CHANNEL_CLASS(spice_main_channel_parent_class);
+
+    if (c->state == SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE) {
+        if (type != SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_ACK &&
+            type != SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_NACK) {
+            g_critical("unexpected msg (%d)."
+                       "Only MIGRATE_DST_SEAMLESS_ACK/NACK are allowed", type);
+            return;
+        }
+    }
+
+    parent_class->handle_msg(channel, msg);
+}
+
+/**
+ * spice_main_agent_test_capability:
+ * @channel:
+ * @cap: an agent capability identifier
+ *
+ * Test capability of a remote agent.
+ *
+ * Returns: %TRUE if @cap (channel kind capability) is available.
+ **/
+gboolean spice_main_agent_test_capability(SpiceMainChannel *channel, guint32 cap)
+{
+    g_return_val_if_fail(SPICE_IS_MAIN_CHANNEL(channel), FALSE);
+
+    return test_agent_cap(channel, cap);
+}
+
+/**
+ * spice_main_update_display:
+ * @channel:
+ * @id: display ID
+ * @x: x position
+ * @y: y position
+ * @width: display width
+ * @height: display height
+ * @update: if %TRUE, update guest resolution after 1sec.
+ *
+ * Update the display @id resolution.
+ *
+ * If @update is %TRUE, the remote configuration will be updated too
+ * after 1 second without further changes. You can send when you want
+ * without delay the new configuration to the remote with
+ * spice_main_send_monitor_config()
+ **/
+void spice_main_update_display(SpiceMainChannel *channel, int id,
+                               int x, int y, int width, int height,
+                               gboolean update)
+{
+    SpiceMainChannelPrivate *c;
+
+    g_return_if_fail(channel != NULL);
+    g_return_if_fail(SPICE_IS_MAIN_CHANNEL(channel));
+    g_return_if_fail(x >= 0);
+    g_return_if_fail(y >= 0);
+    g_return_if_fail(width >= 0);
+    g_return_if_fail(height >= 0);
+
+    c = SPICE_MAIN_CHANNEL(channel)->priv;
+
+    g_return_if_fail(id < SPICE_N_ELEMENTS(c->display));
+
+    c->display[id].x      = x;
+    c->display[id].y      = y;
+    c->display[id].width  = width;
+    c->display[id].height = height;
+
+    if (update)
+        update_display_timer(channel, 1);
+}
+
+/**
+ * spice_main_set_display:
+ * @channel:
+ * @id: display ID
+ * @x: x position
+ * @y: y position
+ * @width: display width
+ * @height: display height
+ *
+ * Notify the guest of screen resolution change. The notification is
+ * sent 1 second later, if no further changes happen.
+ **/
+void spice_main_set_display(SpiceMainChannel *channel, int id,
+                            int x, int y, int width, int height)
+{
+    spice_main_update_display(channel, id, x, y, width, height, TRUE);
+}
+
+/**
+ * spice_main_clipboard_grab:
+ * @channel:
+ * @types: an array of #VD_AGENT_CLIPBOARD types available in the clipboard
+ * @ntypes: the number of @types
+ *
+ * Grab the guest clipboard, with #VD_AGENT_CLIPBOARD @types.
+ *
+ * Deprecated: 0.6: use spice_main_clipboard_selection_grab() instead.
+ **/
+void spice_main_clipboard_grab(SpiceMainChannel *channel, guint32 *types, int ntypes)
+{
+    spice_main_clipboard_selection_grab(channel, VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD, types, ntypes);
+}
+
+/**
+ * spice_main_clipboard_selection_grab:
+ * @channel:
+ * @selection: one of the clipboard #VD_AGENT_CLIPBOARD_SELECTION_*
+ * @types: an array of #VD_AGENT_CLIPBOARD types available in the clipboard
+ * @ntypes: the number of @types
+ *
+ * Grab the guest clipboard, with #VD_AGENT_CLIPBOARD @types.
+ *
+ * Since: 0.6
+ **/
+void spice_main_clipboard_selection_grab(SpiceMainChannel *channel, guint selection,
+                                         guint32 *types, int ntypes)
+{
+    g_return_if_fail(channel != NULL);
+    g_return_if_fail(SPICE_IS_MAIN_CHANNEL(channel));
+
+    agent_clipboard_grab(channel, selection, types, ntypes);
+    spice_channel_wakeup(SPICE_CHANNEL(channel), FALSE);
+}
+
+/**
+ * spice_main_clipboard_release:
+ * @channel:
+ *
+ * Release the clipboard (for example, when the client loses the
+ * clipboard grab): Inform the guest no clipboard data is available.
+ *
+ * Deprecated: 0.6: use spice_main_clipboard_selection_release() instead.
+ **/
+void spice_main_clipboard_release(SpiceMainChannel *channel)
+{
+    spice_main_clipboard_selection_release(channel, VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD);
+}
+
+/**
+ * spice_main_clipboard_selection_release:
+ * @channel:
+ * @selection: one of the clipboard #VD_AGENT_CLIPBOARD_SELECTION_*
+ *
+ * Release the clipboard (for example, when the client loses the
+ * clipboard grab): Inform the guest no clipboard data is available.
+ *
+ * Since: 0.6
+ **/
+void spice_main_clipboard_selection_release(SpiceMainChannel *channel, guint selection)
+{
+    g_return_if_fail(channel != NULL);
+    g_return_if_fail(SPICE_IS_MAIN_CHANNEL(channel));
+
+    SpiceMainChannelPrivate *c = channel->priv;
+
+    if (!c->agent_connected)
+        return;
+
+    agent_clipboard_release(channel, selection);
+    spice_channel_wakeup(SPICE_CHANNEL(channel), FALSE);
+}
+
+/**
+ * spice_main_clipboard_notify:
+ * @channel:
+ * @type: a #VD_AGENT_CLIPBOARD type
+ * @data: clipboard data
+ * @size: data length in bytes
+ *
+ * Send the clipboard data to the guest.
+ *
+ * Deprecated: 0.6: use spice_main_clipboard_selection_notify() instead.
+ **/
+void spice_main_clipboard_notify(SpiceMainChannel *channel,
+                                 guint32 type, const guchar *data, size_t size)
+{
+    spice_main_clipboard_selection_notify(channel, VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD,
+                                          type, data, size);
+}
+
+/**
+ * spice_main_clipboard_selection_notify:
+ * @channel:
+ * @selection: one of the clipboard #VD_AGENT_CLIPBOARD_SELECTION_*
+ * @type: a #VD_AGENT_CLIPBOARD type
+ * @data: clipboard data
+ * @size: data length in bytes
+ *
+ * Send the clipboard data to the guest.
+ *
+ * Since: 0.6
+ **/
+void spice_main_clipboard_selection_notify(SpiceMainChannel *channel, guint selection,
+                                           guint32 type, const guchar *data, size_t size)
+{
+    g_return_if_fail(channel != NULL);
+    g_return_if_fail(SPICE_IS_MAIN_CHANNEL(channel));
+
+    agent_clipboard_notify(channel, selection, type, data, size);
+    spice_channel_wakeup(SPICE_CHANNEL(channel), FALSE);
+}
+
+/**
+ * spice_main_clipboard_request:
+ * @channel:
+ * @type: a #VD_AGENT_CLIPBOARD type
+ *
+ * Request clipboard data of @type from the guest. The reply is sent
+ * through the #SpiceMainChannel::main-clipboard signal.
+ *
+ * Deprecated: 0.6: use spice_main_clipboard_selection_request() instead.
+ **/
+void spice_main_clipboard_request(SpiceMainChannel *channel, guint32 type)
+{
+    spice_main_clipboard_selection_request(channel, VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD, type);
+}
+
+/**
+ * spice_main_clipboard_selection_request:
+ * @channel:
+ * @selection: one of the clipboard #VD_AGENT_CLIPBOARD_SELECTION_*
+ * @type: a #VD_AGENT_CLIPBOARD type
+ *
+ * Request clipboard data of @type from the guest. The reply is sent
+ * through the #SpiceMainChannel::main-clipboard-selection signal.
+ *
+ * Since: 0.6
+ **/
+void spice_main_clipboard_selection_request(SpiceMainChannel *channel, guint selection, guint32 type)
+{
+    g_return_if_fail(channel != NULL);
+    g_return_if_fail(SPICE_IS_MAIN_CHANNEL(channel));
+
+    agent_clipboard_request(channel, selection, type);
+    spice_channel_wakeup(SPICE_CHANNEL(channel), FALSE);
+}
+
+/**
+ * spice_main_set_display_enabled:
+ * @channel: a #SpiceMainChannel
+ * @id: display ID (if -1: set all displays)
+ * @enabled: wether display @id is enabled
+ *
+ * When sending monitor configuration to agent guest, don't set
+ * display @id, which the agent translates to disabling the display
+ * id. Note: this will take effect next time the monitor
+ * configuration is sent.
+ *
+ * Since: 0.6
+ **/
+void spice_main_set_display_enabled(SpiceMainChannel *channel, int id, gboolean enabled)
+{
+    g_return_if_fail(channel != NULL);
+    g_return_if_fail(SPICE_IS_MAIN_CHANNEL(channel));
+    g_return_if_fail(id >= -1);
+
+    SpiceMainChannelPrivate *c = channel->priv;
+
+    if (id == -1) {
+        gint i;
+        for (i = 0; i < G_N_ELEMENTS(c->display); i++) {
+            c->display[i].enabled = enabled;
+            c->display[i].enabled_set = TRUE;
+        }
+    } else {
+        g_return_if_fail(id < G_N_ELEMENTS(c->display));
+        if (c->display[id].enabled == enabled)
+            return;
+        c->display[id].enabled = enabled;
+        c->display[id].enabled_set = TRUE;
+    }
+
+    update_display_timer(channel, 1);
+}
+
+static void file_xfer_completed(SpiceFileXferTask *task, GError *error)
+{
+    /* In case of multiple errors we only report the first error */
+    if (task->error)
+        g_clear_error(&error);
+    if (error) {
+        SPICE_DEBUG("File %s xfer failed: %s",
+                    g_file_get_path(task->file), error->message);
+        task->error = error;
+    }
+
+    if (task->pending)
+        return;
+
+    if (!task->file_stream) {
+        file_xfer_close_cb(NULL, NULL, task);
+        return;
+    }
+
+    g_input_stream_close_async(G_INPUT_STREAM(task->file_stream),
+                               G_PRIORITY_DEFAULT,
+                               task->cancellable,
+                               file_xfer_close_cb,
+                               task);
+    task->pending = TRUE;
+}
+
+static void file_xfer_info_async_cb(GObject *obj, GAsyncResult *res, gpointer data)
+{
+    GFileInfo *info;
+    GFile *file = G_FILE(obj);
+    GError *error = NULL;
+    GKeyFile *keyfile = NULL;
+    gchar *basename = NULL;
+    VDAgentFileXferStartMessage msg;
+    gsize /*msg_size*/ data_len;
+    gchar *string;
+    SpiceFileXferTask *task = (SpiceFileXferTask *)data;
+
+    task->pending = FALSE;
+    info = g_file_query_info_finish(file, res, &error);
+    if (error || task->error)
+        goto failed;
+
+    task->file_size =
+        g_file_info_get_attribute_uint64(info, G_FILE_ATTRIBUTE_STANDARD_SIZE);
+    keyfile = g_key_file_new();
+
+    /* File name */
+    basename = g_file_get_basename(file);
+    g_key_file_set_string(keyfile, "vdagent-file-xfer", "name", basename);
+    g_free(basename);
+    /* File size */
+    g_key_file_set_uint64(keyfile, "vdagent-file-xfer", "size", task->file_size);
+
+    /* Save keyfile content to memory. TODO: more file attributions
+       need to be sent to guest */
+    string = g_key_file_to_data(keyfile, &data_len, &error);
+    g_key_file_free(keyfile);
+    if (error)
+        goto failed;
+
+    /* Create file-xfer start message */
+    msg.id = task->id;
+    agent_msg_queue_many(task->channel, VD_AGENT_FILE_XFER_START,
+                         &msg, sizeof(msg),
+                         string, data_len + 1, NULL);
+    g_free(string);
+    spice_channel_wakeup(SPICE_CHANNEL(task->channel), FALSE);
+    return;
+
+failed:
+    file_xfer_completed(task, error);
+}
+
+static void file_xfer_read_async_cb(GObject *obj, GAsyncResult *res, gpointer data)
+{
+    GFile *file = G_FILE(obj);
+    SpiceFileXferTask *task = (SpiceFileXferTask *)data;
+    GError *error = NULL;
+
+    task->pending = FALSE;
+    task->file_stream = g_file_read_finish(file, res, &error);
+    if (error || task->error) {
+        file_xfer_completed(task, error);
+        return;
+    }
+
+    g_file_query_info_async(task->file,
+                            G_FILE_ATTRIBUTE_STANDARD_SIZE,
+                            G_FILE_QUERY_INFO_NONE,
+                            G_PRIORITY_DEFAULT,
+                            task->cancellable,
+                            file_xfer_info_async_cb,
+                            task);
+    task->pending = TRUE;
+}
+
+static void file_xfer_send_start_msg_async(SpiceMainChannel *channel,
+                                           GFile **files,
+                                           GFileCopyFlags flags,
+                                           GCancellable *cancellable,
+                                           GFileProgressCallback progress_callback,
+                                           gpointer progress_callback_data,
+                                           GAsyncReadyCallback callback,
+                                           gpointer user_data)
+{
+    SpiceMainChannelPrivate *c = channel->priv;
+    SpiceFileXferTask *task;
+    static uint32_t xfer_id;    /* Used to identify task id */
+    gint i;
+
+    for (i = 0; files[i] != NULL && !g_cancellable_is_cancelled(cancellable); i++) {
+        task = g_malloc0(sizeof(SpiceFileXferTask));
+        task->id = ++xfer_id;
+        task->channel = g_object_ref(channel);
+        task->file = g_object_ref(files[i]);
+        task->flags = flags;
+        task->cancellable = cancellable;
+        task->progress_callback = progress_callback;
+        task->progress_callback_data = progress_callback_data;
+        task->callback = callback;
+        task->user_data = user_data;
+
+        CHANNEL_DEBUG(task->channel, "Insert a xfer task:%d to task list", task->id);
+        g_hash_table_insert(c->file_xfer_tasks, GUINT_TO_POINTER(task->id), task);
+
+        g_file_read_async(files[i],
+                          G_PRIORITY_DEFAULT,
+                          cancellable,
+                          file_xfer_read_async_cb,
+                          task);
+        task->pending = TRUE;
+    }
+}
+
+/**
+ * spice_main_file_copy_async:
+ * @sources: #GFile to be transfer
+ * @flags: set of #GFileCopyFlags
+ * @cancellable: (allow-none): optional #GCancellable object, %NULL to ignore
+ * @progress_callback: (allow-none) (scope call): function to callback with
+ *     progress information, or %NULL if progress information is not needed
+ * @progress_callback_data: (closure): user data to pass to @progress_callback
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: the data to pass to callback function
+ *
+ * Copies the file @sources to guest
+ *
+ * If @cancellable is not %NULL, then the operation can be cancelled by
+ * triggering the cancellable object from another thread. If the operation
+ * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned.
+ *
+ * If @progress_callback is not %NULL, then the operation can be monitored by
+ * setting this to a #GFileProgressCallback function. @progress_callback_data
+ * will be passed to this function. It is guaranteed that this callback will
+ * be called after all data has been transferred with the total number of bytes
+ * copied during the operation.
+ *
+ * When the operation is finished, callback will be called. You can then call
+ * spice_main_file_copy_finish() to get the result of the operation.
+ *
+ **/
+void spice_main_file_copy_async(SpiceMainChannel *channel,
+                                GFile **sources,
+                                GFileCopyFlags flags,
+                                GCancellable *cancellable,
+                                GFileProgressCallback progress_callback,
+                                gpointer progress_callback_data,
+                                GAsyncReadyCallback callback,
+                                gpointer user_data)
+{
+    SpiceMainChannelPrivate *c = channel->priv;
+
+    g_return_if_fail(channel != NULL);
+    g_return_if_fail(SPICE_IS_MAIN_CHANNEL(channel));
+    g_return_if_fail(sources != NULL);
+
+    if (!c->agent_connected) {
+        g_simple_async_report_error_in_idle(G_OBJECT(channel),
+                                            callback,
+                                            user_data,
+                                            SPICE_CLIENT_ERROR,
+                                            SPICE_CLIENT_ERROR_FAILED,
+                                            "The agent is not connected");
+        return;
+    }
+
+    file_xfer_send_start_msg_async(channel,
+                                   sources,
+                                   flags,
+                                   cancellable,
+                                   progress_callback,
+                                   progress_callback_data,
+                                   callback,
+                                   user_data);
+}
+
+/**
+ * spice_main_file_copy_finish:
+ * @result: a #GAsyncResult.
+ * @error: a #GError, or %NULL
+ *
+ * Finishes copying the file started with
+ * spice_main_file_copy_async().
+ *
+ * Returns: a %TRUE on success, %FALSE on error.
+ **/
+gboolean spice_main_file_copy_finish(SpiceMainChannel *channel,
+                                     GAsyncResult *result,
+                                     GError **error)
+{
+    GSimpleAsyncResult *simple;
+
+    g_return_val_if_fail(SPICE_IS_MAIN_CHANNEL(channel), FALSE);
+    g_return_val_if_fail(g_simple_async_result_is_valid(result,
+        G_OBJECT(channel), spice_main_file_copy_async), FALSE);
+
+    simple = (GSimpleAsyncResult *)result;
+
+    if (g_simple_async_result_propagate_error(simple, error)) {
+        return FALSE;
+    }
+
+    return g_simple_async_result_get_op_res_gboolean(simple);
+}
diff --git a/src/channel-main.h b/src/channel-main.h
new file mode 100644
index 0000000..3e4fc42
--- /dev/null
+++ b/src/channel-main.h
@@ -0,0 +1,109 @@
+/* -*- 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_CLIENT_MAIN_CHANNEL_H__
+#define __SPICE_CLIENT_MAIN_CHANNEL_H__
+
+#include "spice-client.h"
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_MAIN_CHANNEL            (spice_main_channel_get_type())
+#define SPICE_MAIN_CHANNEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), SPICE_TYPE_MAIN_CHANNEL, SpiceMainChannel))
+#define SPICE_MAIN_CHANNEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), SPICE_TYPE_MAIN_CHANNEL, SpiceMainChannelClass))
+#define SPICE_IS_MAIN_CHANNEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), SPICE_TYPE_MAIN_CHANNEL))
+#define SPICE_IS_MAIN_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SPICE_TYPE_MAIN_CHANNEL))
+#define SPICE_MAIN_CHANNEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), SPICE_TYPE_MAIN_CHANNEL, SpiceMainChannelClass))
+
+typedef struct _SpiceMainChannel SpiceMainChannel;
+typedef struct _SpiceMainChannelClass SpiceMainChannelClass;
+typedef struct _SpiceMainChannelPrivate SpiceMainChannelPrivate;
+
+/**
+ * SpiceMainChannel:
+ *
+ * The #SpiceMainChannel struct is opaque and should not be accessed directly.
+ */
+struct _SpiceMainChannel {
+    SpiceChannel parent;
+
+    /*< private >*/
+    SpiceMainChannelPrivate *priv;
+    /* Do not add fields to this struct */
+};
+
+/**
+ * SpiceMainChannelClass:
+ * @parent_class: Parent class.
+ * @mouse_update: Signal class handler for the #SpiceMainChannel::mouse-update signal.
+ * @agent_update: Signal class handler for the #SpiceMainChannel::agent-update signal.
+ *
+ * Class structure for #SpiceMainChannel.
+ */
+struct _SpiceMainChannelClass {
+    SpiceChannelClass parent_class;
+
+    /* signals */
+    void (*mouse_update)(SpiceChannel *channel);
+    void (*agent_update)(SpiceChannel *channel);
+
+    /*< private >*/
+    /* Do not add fields to this struct */
+};
+
+GType spice_main_channel_get_type(void);
+
+void spice_main_set_display(SpiceMainChannel *channel, int id,
+                            int x, int y, int width, int height);
+void spice_main_update_display(SpiceMainChannel *channel, int id,
+                               int x, int y, int width, int height, gboolean update);
+void spice_main_set_display_enabled(SpiceMainChannel *channel, int id, gboolean enabled);
+gboolean spice_main_send_monitor_config(SpiceMainChannel *channel);
+
+void spice_main_clipboard_selection_grab(SpiceMainChannel *channel, guint selection, guint32 *types, int ntypes);
+void spice_main_clipboard_selection_release(SpiceMainChannel *channel, guint selection);
+void spice_main_clipboard_selection_notify(SpiceMainChannel *channel, guint selection, guint32 type, const guchar *data, size_t size);
+void spice_main_clipboard_selection_request(SpiceMainChannel *channel, guint selection, guint32 type);
+
+gboolean spice_main_agent_test_capability(SpiceMainChannel *channel, guint32 cap);
+void spice_main_file_copy_async(SpiceMainChannel *channel,
+                                GFile **sources,
+                                GFileCopyFlags flags,
+                                GCancellable *cancellable,
+                                GFileProgressCallback progress_callback,
+                                gpointer progress_callback_data,
+                                GAsyncReadyCallback callback,
+                                gpointer user_data);
+
+gboolean spice_main_file_copy_finish(SpiceMainChannel *channel,
+                                     GAsyncResult *result,
+                                     GError **error);
+
+#ifndef SPICE_DISABLE_DEPRECATED
+SPICE_DEPRECATED_FOR(spice_main_clipboard_selection_grab)
+void spice_main_clipboard_grab(SpiceMainChannel *channel, guint32 *types, int ntypes);
+SPICE_DEPRECATED_FOR(spice_main_clipboard_selection_release)
+void spice_main_clipboard_release(SpiceMainChannel *channel);
+SPICE_DEPRECATED_FOR(spice_main_clipboard_selection_notify)
+void spice_main_clipboard_notify(SpiceMainChannel *channel, guint32 type, const guchar *data, size_t size);
+SPICE_DEPRECATED_FOR(spice_main_clipboard_selection_request)
+void spice_main_clipboard_request(SpiceMainChannel *channel, guint32 type);
+#endif
+
+G_END_DECLS
+
+#endif /* __SPICE_CLIENT_MAIN_CHANNEL_H__ */
diff --git a/src/channel-playback-priv.h b/src/channel-playback-priv.h
new file mode 100644
index 0000000..aa33d2c
--- /dev/null
+++ b/src/channel-playback-priv.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2013 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_CLIENT_PLAYBACK_CHANNEL_PRIV_H__
+#define __SPICE_CLIENT_PLAYBACK_CHANNEL_PRIV_H__
+
+gboolean spice_playback_channel_is_active(SpicePlaybackChannel *channel);
+guint32 spice_playback_channel_get_latency(SpicePlaybackChannel *channel);
+void spice_playback_channel_sync_latency(SpicePlaybackChannel *channel);
+#endif
diff --git a/src/channel-playback.c b/src/channel-playback.c
new file mode 100644
index 0000000..d8a181e
--- /dev/null
+++ b/src/channel-playback.c
@@ -0,0 +1,496 @@
+/* -*- 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-channel-priv.h"
+#include "spice-session-priv.h"
+
+#include "spice-marshal.h"
+
+#include "common/snd_codec.h"
+
+/**
+ * SECTION:channel-playback
+ * @short_description: audio stream for playback
+ * @title: Playback Channel
+ * @section_id:
+ * @see_also: #SpiceChannel, and #SpiceAudio
+ * @stability: Stable
+ * @include: channel-playback.h
+ *
+ * #SpicePlaybackChannel class handles an audio playback stream. The
+ * audio data is received via #SpicePlaybackChannel::playback-data
+ * signal, and is controlled by the guest with
+ * #SpicePlaybackChannel::playback-stop and
+ * #SpicePlaybackChannel::playback-start signal events.
+ *
+ * Note: You may be interested to let the #SpiceAudio class play and
+ * record audio channels for your application.
+ */
+
+#define SPICE_PLAYBACK_CHANNEL_GET_PRIVATE(obj)                                  \
+    (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_PLAYBACK_CHANNEL, SpicePlaybackChannelPrivate))
+
+struct _SpicePlaybackChannelPrivate {
+    int                         mode;
+    SndCodec                    codec;
+    guint32                     frame_count;
+    guint32                     last_time;
+    guint8                      nchannels;
+    guint16                     *volume;
+    guint8                      mute;
+    gboolean                    is_active;
+    guint32                     latency;
+    guint32                     min_latency;
+};
+
+G_DEFINE_TYPE(SpicePlaybackChannel, spice_playback_channel, SPICE_TYPE_CHANNEL)
+
+/* Properties */
+enum {
+    PROP_0,
+    PROP_NCHANNELS,
+    PROP_VOLUME,
+    PROP_MUTE,
+    PROP_MIN_LATENCY,
+};
+
+/* Signals */
+enum {
+    SPICE_PLAYBACK_START,
+    SPICE_PLAYBACK_DATA,
+    SPICE_PLAYBACK_STOP,
+    SPICE_PLAYBACK_GET_DELAY,
+
+    SPICE_PLAYBACK_LAST_SIGNAL,
+};
+
+static guint signals[SPICE_PLAYBACK_LAST_SIGNAL];
+static void channel_set_handlers(SpiceChannelClass *klass);
+
+/* ------------------------------------------------------------------ */
+
+#define SPICE_PLAYBACK_DEFAULT_LATENCY_MS 200
+
+static void spice_playback_channel_reset_capabilities(SpiceChannel *channel)
+{
+    if (!g_getenv("SPICE_DISABLE_CELT"))
+        if (snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_CELT_0_5_1, SND_CODEC_ANY_FREQUENCY))
+            spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_PLAYBACK_CAP_CELT_0_5_1);
+    if (!g_getenv("SPICE_DISABLE_OPUS"))
+        if (snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_OPUS, SND_CODEC_ANY_FREQUENCY))
+            spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_PLAYBACK_CAP_OPUS);
+    spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_PLAYBACK_CAP_VOLUME);
+    spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_PLAYBACK_CAP_LATENCY);
+}
+
+static void spice_playback_channel_init(SpicePlaybackChannel *channel)
+{
+    channel->priv = SPICE_PLAYBACK_CHANNEL_GET_PRIVATE(channel);
+
+    spice_playback_channel_reset_capabilities(SPICE_CHANNEL(channel));
+}
+
+static void spice_playback_channel_finalize(GObject *obj)
+{
+    SpicePlaybackChannelPrivate *c = SPICE_PLAYBACK_CHANNEL(obj)->priv;
+
+    snd_codec_destroy(&c->codec);
+
+    g_free(c->volume);
+    c->volume = NULL;
+
+    if (G_OBJECT_CLASS(spice_playback_channel_parent_class)->finalize)
+        G_OBJECT_CLASS(spice_playback_channel_parent_class)->finalize(obj);
+}
+
+static void spice_playback_channel_get_property(GObject    *gobject,
+                                                guint       prop_id,
+                                                GValue     *value,
+                                                GParamSpec *pspec)
+{
+    SpicePlaybackChannel *channel = SPICE_PLAYBACK_CHANNEL(gobject);
+    SpicePlaybackChannelPrivate *c = channel->priv;
+
+    switch (prop_id) {
+    case PROP_VOLUME:
+        g_value_set_pointer(value, c->volume);
+        break;
+    case PROP_NCHANNELS:
+        g_value_set_uint(value, c->nchannels);
+        break;
+    case PROP_MUTE:
+        g_value_set_boolean(value, c->mute);
+        break;
+    case PROP_MIN_LATENCY:
+        g_value_set_uint(value, c->min_latency);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
+        break;
+    }
+}
+
+static void spice_playback_channel_set_property(GObject      *gobject,
+                                                guint         prop_id,
+                                                const GValue *value,
+                                                GParamSpec   *pspec)
+{
+    switch (prop_id) {
+    case PROP_VOLUME:
+        /* TODO: request guest volume change */
+        break;
+    case PROP_MUTE:
+        /* TODO: request guest mute change */
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
+        break;
+    }
+}
+
+/* main or coroutine context */
+static void spice_playback_channel_reset(SpiceChannel *channel, gboolean migrating)
+{
+    SpicePlaybackChannelPrivate *c = SPICE_PLAYBACK_CHANNEL(channel)->priv;
+
+    snd_codec_destroy(&c->codec);
+    g_coroutine_signal_emit(channel, signals[SPICE_PLAYBACK_STOP], 0);
+    c->is_active = FALSE;
+
+    SPICE_CHANNEL_CLASS(spice_playback_channel_parent_class)->channel_reset(channel, migrating);
+}
+
+static void spice_playback_channel_class_init(SpicePlaybackChannelClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+    SpiceChannelClass *channel_class = SPICE_CHANNEL_CLASS(klass);
+
+    gobject_class->finalize     = spice_playback_channel_finalize;
+    gobject_class->get_property = spice_playback_channel_get_property;
+    gobject_class->set_property = spice_playback_channel_set_property;
+
+    channel_class->channel_reset = spice_playback_channel_reset;
+    channel_class->channel_reset_capabilities = spice_playback_channel_reset_capabilities;
+
+    g_object_class_install_property
+        (gobject_class, PROP_NCHANNELS,
+         g_param_spec_uint("nchannels",
+                           "Number of Channels",
+                           "Number of Channels",
+                           0, G_MAXUINT8, 2,
+                           G_PARAM_READWRITE |
+                           G_PARAM_STATIC_STRINGS));
+
+    g_object_class_install_property
+        (gobject_class, PROP_VOLUME,
+         g_param_spec_pointer("volume",
+                              "Playback volume",
+                              "Playback volume",
+                              G_PARAM_READWRITE |
+                              G_PARAM_STATIC_STRINGS));
+
+    g_object_class_install_property
+        (gobject_class, PROP_MUTE,
+         g_param_spec_boolean("mute",
+                              "Mute",
+                              "Mute",
+                              FALSE,
+                              G_PARAM_READWRITE |
+                              G_PARAM_STATIC_STRINGS));
+    g_object_class_install_property
+        (gobject_class, PROP_MIN_LATENCY,
+         g_param_spec_uint("min-latency",
+                           "Playback min buffer size (ms)",
+                           "Playback min buffer size (ms)",
+                           0, G_MAXUINT32, SPICE_PLAYBACK_DEFAULT_LATENCY_MS,
+                           G_PARAM_READWRITE |
+                           G_PARAM_STATIC_STRINGS));
+    /**
+     * SpicePlaybackChannel::playback-start:
+     * @channel: the #SpicePlaybackChannel that emitted the signal
+     * @format: a #SPICE_AUDIO_FMT
+     * @channels: number of channels
+     * @rate: audio rate
+     * @latency: minimum playback latency in ms
+     *
+     * Notify when the playback should start, and provide audio format
+     * characteristics.
+     **/
+    signals[SPICE_PLAYBACK_START] =
+        g_signal_new("playback-start",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_FIRST,
+                     G_STRUCT_OFFSET(SpicePlaybackChannelClass, playback_start),
+                     NULL, NULL,
+                     g_cclosure_user_marshal_VOID__INT_INT_INT,
+                     G_TYPE_NONE,
+                     3,
+                     G_TYPE_INT, G_TYPE_INT, G_TYPE_INT);
+
+    /**
+     * SpicePlaybackChannel::playback-data:
+     * @channel: the #SpicePlaybackChannel that emitted the signal
+     * @data: pointer to audio data
+     * @data_size: size in byte of @data
+     *
+     * Provide audio data to be played.
+     **/
+    signals[SPICE_PLAYBACK_DATA] =
+        g_signal_new("playback-data",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_FIRST,
+                     G_STRUCT_OFFSET(SpicePlaybackChannelClass, playback_data),
+                     NULL, NULL,
+                     g_cclosure_user_marshal_VOID__POINTER_INT,
+                     G_TYPE_NONE,
+                     2,
+                     G_TYPE_POINTER, G_TYPE_INT);
+
+    /**
+     * SpicePlaybackChannel::playback-stop:
+     * @channel: the #SpicePlaybackChannel that emitted the signal
+     *
+     * Notify when the playback should stop.
+     **/
+    signals[SPICE_PLAYBACK_STOP] =
+        g_signal_new("playback-stop",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_FIRST,
+                     G_STRUCT_OFFSET(SpicePlaybackChannelClass, playback_stop),
+                     NULL, NULL,
+                     g_cclosure_marshal_VOID__VOID,
+                     G_TYPE_NONE,
+                     0);
+
+    /**
+     * SpicePlaybackChannel::playback-get-delay:
+     * @channel: the #SpicePlaybackChannel that emitted the signal
+     *
+     * Notify when the current playback delay is requested
+     **/
+    signals[SPICE_PLAYBACK_GET_DELAY] =
+        g_signal_new("playback-get-delay",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_FIRST,
+                     0,
+                     NULL, NULL,
+                     g_cclosure_marshal_VOID__VOID,
+                     G_TYPE_NONE,
+                     0);
+
+    g_type_class_add_private(klass, sizeof(SpicePlaybackChannelPrivate));
+    channel_set_handlers(SPICE_CHANNEL_CLASS(klass));
+}
+
+/* ------------------------------------------------------------------ */
+
+/* coroutine context */
+static void playback_handle_data(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpicePlaybackChannelPrivate *c = SPICE_PLAYBACK_CHANNEL(channel)->priv;
+    SpiceMsgPlaybackPacket *packet = spice_msg_in_parsed(in);
+
+#ifdef DEBUG
+    CHANNEL_DEBUG(channel, "%s: time %d data %p size %d", __FUNCTION__,
+                  packet->time, packet->data, packet->data_size);
+#endif
+
+    if (c->last_time > packet->time)
+        g_warn_if_reached();
+
+    c->last_time = packet->time;
+
+    uint8_t *data = packet->data;
+    int n = packet->data_size;
+    uint8_t pcm[SND_CODEC_MAX_FRAME_SIZE * 2 * 2];
+
+    if (c->mode != SPICE_AUDIO_DATA_MODE_RAW) {
+        n = sizeof(pcm);
+        data = pcm;
+
+        if (snd_codec_decode(c->codec, packet->data, packet->data_size,
+                    pcm, &n) != SND_CODEC_OK) {
+            g_warning("snd_codec_decode() error");
+            return;
+        }
+    }
+
+    g_coroutine_signal_emit(channel, signals[SPICE_PLAYBACK_DATA], 0, data, n);
+
+    if ((c->frame_count++ % 100) == 0) {
+        g_coroutine_signal_emit(channel, signals[SPICE_PLAYBACK_GET_DELAY], 0);
+    }
+}
+
+/* coroutine context */
+static void playback_handle_mode(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpicePlaybackChannelPrivate *c = SPICE_PLAYBACK_CHANNEL(channel)->priv;
+    SpiceMsgPlaybackMode *mode = spice_msg_in_parsed(in);
+
+    CHANNEL_DEBUG(channel, "%s: time %d mode %d data %p size %d", __FUNCTION__,
+                  mode->time, mode->mode, mode->data, mode->data_size);
+
+    c->mode = mode->mode;
+    switch (c->mode) {
+    case SPICE_AUDIO_DATA_MODE_RAW:
+    case SPICE_AUDIO_DATA_MODE_CELT_0_5_1:
+    case SPICE_AUDIO_DATA_MODE_OPUS:
+        break;
+    default:
+        g_warning("%s: unhandled mode", __FUNCTION__);
+        break;
+    }
+}
+
+/* coroutine context */
+static void playback_handle_start(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpicePlaybackChannelPrivate *c = SPICE_PLAYBACK_CHANNEL(channel)->priv;
+    SpiceMsgPlaybackStart *start = spice_msg_in_parsed(in);
+
+    CHANNEL_DEBUG(channel, "%s: fmt %d channels %d freq %d time %d", __FUNCTION__,
+                  start->format, start->channels, start->frequency, start->time);
+
+    c->frame_count = 0;
+    c->last_time = start->time;
+    c->is_active = TRUE;
+    c->min_latency = SPICE_PLAYBACK_DEFAULT_LATENCY_MS;
+    snd_codec_destroy(&c->codec);
+
+    if (c->mode != SPICE_AUDIO_DATA_MODE_RAW) {
+        if (snd_codec_create(&c->codec, c->mode, start->frequency, SND_CODEC_DECODE) != SND_CODEC_OK) {
+            g_warning("create decoder failed");
+            return;
+        }
+    }
+    g_coroutine_signal_emit(channel, signals[SPICE_PLAYBACK_START], 0,
+                            start->format, start->channels, start->frequency);
+}
+
+/* coroutine context */
+static void playback_handle_stop(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpicePlaybackChannelPrivate *c = SPICE_PLAYBACK_CHANNEL(channel)->priv;
+
+    g_coroutine_signal_emit(channel, signals[SPICE_PLAYBACK_STOP], 0);
+    c->is_active = FALSE;
+}
+
+/* coroutine context */
+static void playback_handle_set_volume(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpicePlaybackChannelPrivate *c = SPICE_PLAYBACK_CHANNEL(channel)->priv;
+    SpiceMsgAudioVolume *vol = spice_msg_in_parsed(in);
+
+    if (vol->nchannels == 0) {
+        g_warning("spice-server send audio-volume-msg with 0 channels");
+        return;
+    }
+
+    g_free(c->volume);
+    c->nchannels = vol->nchannels;
+    c->volume = g_new(guint16, c->nchannels);
+    memcpy(c->volume, vol->volume, sizeof(guint16) * c->nchannels);
+    g_coroutine_object_notify(G_OBJECT(channel), "volume");
+}
+
+/* coroutine context */
+static void playback_handle_set_mute(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpicePlaybackChannelPrivate *c = SPICE_PLAYBACK_CHANNEL(channel)->priv;
+    SpiceMsgAudioMute *m = spice_msg_in_parsed(in);
+
+    c->mute = m->mute;
+    g_coroutine_object_notify(G_OBJECT(channel), "mute");
+}
+
+/* coroutine context */
+static void playback_handle_set_latency(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpicePlaybackChannelPrivate *c = SPICE_PLAYBACK_CHANNEL(channel)->priv;
+    SpiceMsgPlaybackLatency *msg = spice_msg_in_parsed(in);
+
+    c->min_latency = msg->latency_ms;
+    SPICE_DEBUG("%s: notify latency update %u", __FUNCTION__, c->min_latency);
+    g_coroutine_object_notify(G_OBJECT(channel), "min-latency");
+}
+
+static void channel_set_handlers(SpiceChannelClass *klass)
+{
+    static const spice_msg_handler handlers[] = {
+        [ SPICE_MSG_PLAYBACK_DATA ]            = playback_handle_data,
+        [ SPICE_MSG_PLAYBACK_MODE ]            = playback_handle_mode,
+        [ SPICE_MSG_PLAYBACK_START ]           = playback_handle_start,
+        [ SPICE_MSG_PLAYBACK_STOP ]            = playback_handle_stop,
+        [ SPICE_MSG_PLAYBACK_VOLUME ]          = playback_handle_set_volume,
+        [ SPICE_MSG_PLAYBACK_MUTE ]            = playback_handle_set_mute,
+        [ SPICE_MSG_PLAYBACK_LATENCY ]         = playback_handle_set_latency,
+    };
+
+    spice_channel_set_handlers(klass, handlers, G_N_ELEMENTS(handlers));
+}
+
+void spice_playback_channel_set_delay(SpicePlaybackChannel *channel, guint32 delay_ms)
+{
+    SpicePlaybackChannelPrivate *c;
+    SpiceSession *session;
+
+    g_return_if_fail(SPICE_IS_PLAYBACK_CHANNEL(channel));
+
+    CHANNEL_DEBUG(channel, "playback set_delay %u ms", delay_ms);
+
+    c = channel->priv;
+    c->latency = delay_ms;
+
+    session = spice_channel_get_session(SPICE_CHANNEL(channel));
+    if (session) {
+        spice_session_set_mm_time(session, c->last_time - delay_ms);
+    } else {
+        CHANNEL_DEBUG(channel, "channel detached from session, mm time skipped");
+    }
+}
+
+G_GNUC_INTERNAL
+gboolean spice_playback_channel_is_active(SpicePlaybackChannel *channel)
+{
+    g_return_val_if_fail(SPICE_IS_PLAYBACK_CHANNEL(channel), FALSE);
+    return channel->priv->is_active;
+}
+
+G_GNUC_INTERNAL
+guint32 spice_playback_channel_get_latency(SpicePlaybackChannel *channel)
+{
+    g_return_val_if_fail(SPICE_IS_PLAYBACK_CHANNEL(channel), 0);
+    if (!channel->priv->is_active) {
+        return 0;
+    }
+    return channel->priv->latency;
+}
+
+G_GNUC_INTERNAL
+void spice_playback_channel_sync_latency(SpicePlaybackChannel *channel)
+{
+    g_return_if_fail(SPICE_IS_PLAYBACK_CHANNEL(channel));
+    g_return_if_fail(channel->priv->is_active);
+    SPICE_DEBUG("%s: notify latency update %u", __FUNCTION__, channel->priv->min_latency);
+    g_coroutine_object_notify(G_OBJECT(SPICE_CHANNEL(channel)), "min-latency");
+}
diff --git a/src/channel-playback.h b/src/channel-playback.h
new file mode 100644
index 0000000..9cf68cf
--- /dev/null
+++ b/src/channel-playback.h
@@ -0,0 +1,76 @@
+/* -*- 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_CLIENT_PLAYBACK_CHANNEL_H__
+#define __SPICE_CLIENT_PLAYBACK_CHANNEL_H__
+
+#include "spice-client.h"
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_PLAYBACK_CHANNEL            (spice_playback_channel_get_type())
+#define SPICE_PLAYBACK_CHANNEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), SPICE_TYPE_PLAYBACK_CHANNEL, SpicePlaybackChannel))
+#define SPICE_PLAYBACK_CHANNEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), SPICE_TYPE_PLAYBACK_CHANNEL, SpicePlaybackChannelClass))
+#define SPICE_IS_PLAYBACK_CHANNEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), SPICE_TYPE_PLAYBACK_CHANNEL))
+#define SPICE_IS_PLAYBACK_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SPICE_TYPE_PLAYBACK_CHANNEL))
+#define SPICE_PLAYBACK_CHANNEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), SPICE_TYPE_PLAYBACK_CHANNEL, SpicePlaybackChannelClass))
+
+typedef struct _SpicePlaybackChannel SpicePlaybackChannel;
+typedef struct _SpicePlaybackChannelClass SpicePlaybackChannelClass;
+typedef struct _SpicePlaybackChannelPrivate SpicePlaybackChannelPrivate;
+
+/**
+ * SpicePlaybackChannel:
+ *
+ * The #SpicePlaybackChannel struct is opaque and should not be accessed directly.
+ */
+struct _SpicePlaybackChannel {
+    SpiceChannel parent;
+
+    /*< private >*/
+    SpicePlaybackChannelPrivate *priv;
+    /* Do not add fields to this struct */
+};
+
+/**
+ * SpicePlaybackChannelClass:
+ * @parent_class: Parent class.
+ * @playback_start: Signal class handler for the #SpicePlaybackChannel::playback-start signal.
+ * @playback_data: Signal class handler for the #SpicePlaybackChannel::playback-data signal.
+ * @playback_stop: Signal class handler for the #SpicePlaybackChannel::playback-stop signal.
+ *
+ * Class structure for #SpicePlaybackChannel.
+ */
+struct _SpicePlaybackChannelClass {
+    SpiceChannelClass parent_class;
+
+    /* signals */
+    void (*playback_start)(SpicePlaybackChannel *channel,
+                           gint format, gint channels, gint freq);
+    void (*playback_data)(SpicePlaybackChannel *channel, gpointer *data, gint size);
+    void (*playback_stop)(SpicePlaybackChannel *channel);
+
+    /*< private >*/
+    /* Do not add fields to this struct */
+};
+
+GType           spice_playback_channel_get_type(void);
+void            spice_playback_channel_set_delay(SpicePlaybackChannel *channel, guint32 delay_ms);
+
+G_END_DECLS
+
+#endif /* __SPICE_CLIENT_PLAYBACK_CHANNEL_H__ */
diff --git a/src/channel-port.c b/src/channel-port.c
new file mode 100644
index 0000000..f0b6d1e
--- /dev/null
+++ b/src/channel-port.c
@@ -0,0 +1,361 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2012 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-channel-priv.h"
+#include "spice-marshal.h"
+#include "glib-compat.h"
+
+/**
+ * SECTION:channel-port
+ * @short_description: private communication channel
+ * @title: Port Channel
+ * @section_id:
+ * @see_also: #SpiceChannel
+ * @stability: Stable
+ * @include: channel-port.h
+ *
+ * A Spice port channel carry arbitrary data between the Spice client
+ * and the Spice server. It may be used to provide additional
+ * services on top of a Spice connection. For example, a channel can
+ * be associated with the qemu monitor for the client to interact
+ * with it, just like any qemu chardev. Or it may be used with
+ * various protocols, such as the Spice Controller.
+ *
+ * A port kind is identified simply by a fqdn, such as
+ * org.qemu.monitor, org.spice.spicy.test or org.ovirt.controller...
+ *
+ * Once connected and initialized, the client may read the name of the
+ * port via SpicePortChannel:port-name.
+
+ * When the other end of the port is ready,
+ * SpicePortChannel:port-opened is set to %TRUE and you can start
+ * receiving data via the signal SpicePortChannel::port-data, or
+ * sending data via spice_port_write_async().
+ *
+ * Since: 0.15
+ */
+
+#define SPICE_PORT_CHANNEL_GET_PRIVATE(obj)                                  \
+    (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_PORT_CHANNEL, SpicePortChannelPrivate))
+
+struct _SpicePortChannelPrivate {
+    gchar *name;
+    gboolean opened;
+};
+
+G_DEFINE_TYPE(SpicePortChannel, spice_port_channel, SPICE_TYPE_CHANNEL)
+
+/* Properties */
+enum {
+    PROP_0,
+    PROP_PORT_NAME,
+    PROP_PORT_OPENED,
+};
+
+/* Signals */
+enum {
+    SPICE_PORT_DATA,
+    SPICE_PORT_EVENT,
+    LAST_SIGNAL,
+};
+
+static guint signals[LAST_SIGNAL];
+static void channel_set_handlers(SpiceChannelClass *klass);
+
+static void spice_port_channel_init(SpicePortChannel *channel)
+{
+    channel->priv = SPICE_PORT_CHANNEL_GET_PRIVATE(channel);
+}
+
+static void spice_port_get_property(GObject    *object,
+                                      guint       prop_id,
+                                      GValue     *value,
+                                      GParamSpec *pspec)
+{
+    SpicePortChannelPrivate *c = SPICE_PORT_CHANNEL(object)->priv;
+
+    switch (prop_id) {
+    case PROP_PORT_NAME:
+        g_value_set_string(value, c->name);
+        break;
+    case PROP_PORT_OPENED:
+        g_value_set_boolean(value, c->opened);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void spice_port_channel_finalize(GObject *object)
+{
+    SpicePortChannelPrivate *c = SPICE_PORT_CHANNEL(object)->priv;
+
+    g_free(c->name);
+
+    if (G_OBJECT_CLASS(spice_port_channel_parent_class)->finalize)
+        G_OBJECT_CLASS(spice_port_channel_parent_class)->finalize(object);
+}
+
+static void spice_port_channel_reset(SpiceChannel *channel, gboolean migrating)
+{
+    SpicePortChannelPrivate *c = SPICE_PORT_CHANNEL(channel)->priv;
+
+    g_clear_pointer(&c->name, g_free);
+    c->opened = FALSE;
+
+    SPICE_CHANNEL_CLASS(spice_port_channel_parent_class)->channel_reset(channel, migrating);
+}
+
+static void spice_port_channel_class_init(SpicePortChannelClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+    SpiceChannelClass *channel_class = SPICE_CHANNEL_CLASS(klass);
+
+    gobject_class->finalize     = spice_port_channel_finalize;
+    gobject_class->get_property = spice_port_get_property;
+    channel_class->channel_reset = spice_port_channel_reset;
+
+    g_object_class_install_property
+        (gobject_class, PROP_PORT_NAME,
+         g_param_spec_string("port-name",
+                             "Port name",
+                             "Port name",
+                             NULL,
+                             G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+    g_object_class_install_property
+        (gobject_class, PROP_PORT_OPENED,
+         g_param_spec_boolean("port-opened",
+                              "Port opened",
+                              "Port opened",
+                              FALSE,
+                              G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpicePort::port-data:
+     * @channel: the channel that emitted the signal
+     * @data: the data received
+     * @size: number of bytes read
+     *
+     * The #SpicePortChannel::port-data signal is emitted when new
+     * port data is received.
+     * Since: 0.15
+     **/
+    signals[SPICE_PORT_DATA] =
+        g_signal_new("port-data",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_LAST,
+                     0,
+                     NULL, NULL,
+                     g_cclosure_user_marshal_VOID__POINTER_INT,
+                     G_TYPE_NONE,
+                     2,
+                     G_TYPE_POINTER, G_TYPE_INT);
+
+
+    /**
+     * SpicePort::port-event:
+     * @channel: the channel that emitted the signal
+     * @event: the event received
+     * @size: number of bytes read
+     *
+     * The #SpicePortChannel::port-event signal is emitted when new
+     * port event is received.
+     * Since: 0.15
+     **/
+    signals[SPICE_PORT_EVENT] =
+        g_signal_new("port-event",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_LAST,
+                     0,
+                     NULL, NULL,
+                     g_cclosure_marshal_VOID__INT,
+                     G_TYPE_NONE,
+                     1,
+                     G_TYPE_INT);
+
+    g_type_class_add_private(klass, sizeof(SpicePortChannelPrivate));
+    channel_set_handlers(SPICE_CHANNEL_CLASS(klass));
+}
+
+
+/* coroutine context */
+static void port_set_opened(SpicePortChannel *self, gboolean opened)
+{
+    SpicePortChannelPrivate *c = self->priv;
+
+    if (c->opened == opened)
+        return;
+
+    c->opened = opened;
+    g_coroutine_object_notify(G_OBJECT(self), "port-opened");
+}
+
+/* coroutine context */
+static void port_handle_init(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpicePortChannel *self = SPICE_PORT_CHANNEL(channel);
+    SpicePortChannelPrivate *c = self->priv;
+    SpiceMsgPortInit *init = spice_msg_in_parsed(in);
+
+    CHANNEL_DEBUG(channel, "init: %s %d", init->name, init->opened);
+    g_return_if_fail(init->name != NULL && *init->name != '\0');
+    g_return_if_fail(c->name == NULL);
+
+    c->name = g_strdup((gchar*)init->name);
+
+    port_set_opened(self, init->opened);
+    if (init->opened)
+        g_coroutine_signal_emit(channel, signals[SPICE_PORT_EVENT], 0, SPICE_PORT_EVENT_OPENED);
+
+    g_coroutine_object_notify(G_OBJECT(channel), "port-name");
+}
+
+/* coroutine context */
+static void port_handle_event(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpicePortChannel *self = SPICE_PORT_CHANNEL(channel);
+    SpiceMsgPortEvent *event = spice_msg_in_parsed(in);
+
+    CHANNEL_DEBUG(channel, "port event: %d", event->event);
+    switch (event->event) {
+    case SPICE_PORT_EVENT_OPENED:
+        port_set_opened(self, true);
+        break;
+    case SPICE_PORT_EVENT_CLOSED:
+        port_set_opened(self, false);
+        break;
+    }
+
+    g_coroutine_signal_emit(channel, signals[SPICE_PORT_EVENT], 0, event->event);
+}
+
+/* coroutine context */
+static void port_handle_msg(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpicePortChannel *self = SPICE_PORT_CHANNEL(channel);
+    int size;
+    uint8_t *buf;
+
+    buf = spice_msg_in_raw(in, &size);
+    CHANNEL_DEBUG(channel, "port %p got %d %p", channel, size, buf);
+    port_set_opened(self, true);
+    g_coroutine_signal_emit(channel, signals[SPICE_PORT_DATA], 0, buf, size);
+}
+
+/**
+ * spice_port_write_async:
+ * @port: A #SpicePortChannel
+ * @buffer: (array length=count) (element-type guint8): the buffer
+ * containing the data to write
+ * @count: the number of bytes to write
+ * @cancellable: (allow-none): optional GCancellable object, NULL to ignore
+ * @callback: (scope async): callback to call when the request is satisfied
+ * @user_data: (closure): the data to pass to callback function
+ *
+ * Request an asynchronous write of count bytes from @buffer into the
+ * @port. When the operation is finished @callback will be called. You
+ * can then call spice_port_write_finish() to get the result of
+ * the operation.
+ *
+ * Since: 0.15
+ **/
+void spice_port_write_async(SpicePortChannel *self,
+                            const void *buffer, gsize count,
+                            GCancellable *cancellable,
+                            GAsyncReadyCallback callback,
+                            gpointer user_data)
+{
+    SpicePortChannelPrivate *c;
+
+    g_return_if_fail(SPICE_IS_PORT_CHANNEL(self));
+    g_return_if_fail(buffer != NULL);
+    c = self->priv;
+
+    if (!c->opened) {
+        g_simple_async_report_error_in_idle(G_OBJECT(self), callback, user_data,
+            SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+            "The port is not opened");
+        return;
+    }
+
+    spice_vmc_write_async(SPICE_CHANNEL(self), buffer, count,
+                          cancellable, callback, user_data);
+}
+
+/**
+ * spice_port_write_finish:
+ * @port: a #SpicePortChannel
+ * @result: a #GAsyncResult
+ * @error: a #GError location to store the error occurring, or %NULL
+ * to ignore
+ *
+ * Finishes a port write operation.
+ *
+ * Returns: a #gssize containing the number of bytes written to the stream.
+ * Since: 0.15
+ **/
+gssize spice_port_write_finish(SpicePortChannel *self,
+                               GAsyncResult *result, GError **error)
+{
+    g_return_val_if_fail(SPICE_IS_PORT_CHANNEL(self), -1);
+
+    return spice_vmc_write_finish(SPICE_CHANNEL(self), result, error);
+}
+
+/**
+ * spice_port_event:
+ * @port: a #SpicePortChannel
+ * @event: a SPICE_PORT_EVENT value
+ *
+ * Send an event to the port.
+ *
+ * Note: The values SPICE_PORT_EVENT_CLOSED and
+ * SPICE_PORT_EVENT_OPENED are managed by the channel connection
+ * state.
+ *
+ * Since: 0.15
+ **/
+void spice_port_event(SpicePortChannel *self, guint8 event)
+{
+    SpiceMsgcPortEvent e;
+    SpiceMsgOut *msg;
+
+    g_return_if_fail(SPICE_IS_PORT_CHANNEL(self));
+    g_return_if_fail(event > SPICE_PORT_EVENT_CLOSED);
+
+    msg = spice_msg_out_new(SPICE_CHANNEL(self), SPICE_MSGC_PORT_EVENT);
+    e.event = event;
+    msg->marshallers->msgc_port_event(msg->marshaller, &e);
+    spice_msg_out_send(msg);
+}
+
+static void channel_set_handlers(SpiceChannelClass *klass)
+{
+    static const spice_msg_handler handlers[] = {
+        [ SPICE_MSG_PORT_INIT ]              = port_handle_init,
+        [ SPICE_MSG_PORT_EVENT ]             = port_handle_event,
+        [ SPICE_MSG_SPICEVMC_DATA ]          = port_handle_msg,
+    };
+
+    spice_channel_set_handlers(klass, handlers, G_N_ELEMENTS(handlers));
+}
diff --git a/src/channel-port.h b/src/channel-port.h
new file mode 100644
index 0000000..08c15dc
--- /dev/null
+++ b/src/channel-port.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2012 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_CLIENT_PORT_CHANNEL_H__
+#define __SPICE_CLIENT_PORT_CHANNEL_H__
+
+#include <gio/gio.h>
+#include "spice-channel.h"
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_PORT_CHANNEL            (spice_port_channel_get_type())
+#define SPICE_PORT_CHANNEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), SPICE_TYPE_PORT_CHANNEL, SpicePortChannel))
+#define SPICE_PORT_CHANNEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), SPICE_TYPE_PORT_CHANNEL, SpicePortChannelClass))
+#define SPICE_IS_PORT_CHANNEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), SPICE_TYPE_PORT_CHANNEL))
+#define SPICE_IS_PORT_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SPICE_TYPE_PORT_CHANNEL))
+#define SPICE_PORT_CHANNEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), SPICE_TYPE_PORT_CHANNEL, SpicePortChannelClass))
+
+typedef struct _SpicePortChannel SpicePortChannel;
+typedef struct _SpicePortChannelClass SpicePortChannelClass;
+typedef struct _SpicePortChannelPrivate SpicePortChannelPrivate;
+
+/**
+ * SpicePortChannel:
+ *
+ * The #SpicePortChannel struct is opaque and should not be accessed directly.
+ */
+struct _SpicePortChannel {
+    SpiceChannel parent;
+
+    /*< private >*/
+    SpicePortChannelPrivate *priv;
+    /* Do not add fields to this struct */
+};
+
+/**
+ * SpicePortChannelClass:
+ * @parent_class: Parent class.
+ *
+ * Class structure for #SpicePortChannel.
+ */
+struct _SpicePortChannelClass {
+    SpiceChannelClass parent_class;
+
+    /*< private >*/
+    /* Do not add fields to this struct */
+};
+
+GType spice_port_channel_get_type(void);
+
+void spice_port_write_async(SpicePortChannel *port,
+                            const void *buffer, gsize count,
+                            GCancellable *cancellable,
+                            GAsyncReadyCallback callback,
+                            gpointer user_data);
+gssize spice_port_write_finish(SpicePortChannel *port,
+                               GAsyncResult *result, GError **error);
+void spice_port_event(SpicePortChannel *port, guint8 event);
+
+G_END_DECLS
+
+#endif /* __SPICE_CLIENT_PORT_CHANNEL_H__ */
diff --git a/src/channel-record.c b/src/channel-record.c
new file mode 100644
index 0000000..d07d84e
--- /dev/null
+++ b/src/channel-record.c
@@ -0,0 +1,482 @@
+/* -*- 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-channel-priv.h"
+
+#include "spice-marshal.h"
+#include "spice-session-priv.h"
+
+#include "common/snd_codec.h"
+
+/**
+ * SECTION:channel-record
+ * @short_description: audio stream for recording
+ * @title: Record Channel
+ * @section_id:
+ * @see_also: #SpiceChannel, and #SpiceAudio
+ * @stability: Stable
+ * @include: channel-record.h
+ *
+ * #SpiceRecordChannel class handles an audio recording stream. The
+ * audio stream should start when #SpiceRecordChannel::record-start is
+ * emitted and should be stopped when #SpiceRecordChannel::record-stop
+ * is received.
+ *
+ * The audio is sent to the guest by calling spice_record_send_data()
+ * with the recorded PCM data.
+ *
+ * Note: You may be interested to let the #SpiceAudio class play and
+ * record audio channels for your application.
+ */
+
+#define SPICE_RECORD_CHANNEL_GET_PRIVATE(obj)                                  \
+    (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_RECORD_CHANNEL, SpiceRecordChannelPrivate))
+
+struct _SpiceRecordChannelPrivate {
+    int                         mode;
+    gboolean                    started;
+    SndCodec                    codec;
+    gsize                       frame_bytes;
+    guint8                      *last_frame;
+    gsize                       last_frame_current;
+    guint8                      nchannels;
+    guint16                     *volume;
+    guint8                      mute;
+};
+
+G_DEFINE_TYPE(SpiceRecordChannel, spice_record_channel, SPICE_TYPE_CHANNEL)
+
+/* Properties */
+enum {
+    PROP_0,
+    PROP_NCHANNELS,
+    PROP_VOLUME,
+    PROP_MUTE,
+};
+
+/* Signals */
+enum {
+    SPICE_RECORD_START,
+    SPICE_RECORD_STOP,
+
+    SPICE_RECORD_LAST_SIGNAL,
+};
+
+static guint signals[SPICE_RECORD_LAST_SIGNAL];
+
+static void channel_set_handlers(SpiceChannelClass *klass);
+
+/* ------------------------------------------------------------------ */
+
+static void spice_record_channel_reset_capabilities(SpiceChannel *channel)
+{
+    if (!g_getenv("SPICE_DISABLE_CELT"))
+        if (snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_CELT_0_5_1, SND_CODEC_ANY_FREQUENCY))
+            spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_RECORD_CAP_CELT_0_5_1);
+    if (!g_getenv("SPICE_DISABLE_OPUS"))
+        if (snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_OPUS, SND_CODEC_ANY_FREQUENCY))
+            spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_RECORD_CAP_OPUS);
+    spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_RECORD_CAP_VOLUME);
+}
+
+static void spice_record_channel_init(SpiceRecordChannel *channel)
+{
+    channel->priv = SPICE_RECORD_CHANNEL_GET_PRIVATE(channel);
+
+    spice_record_channel_reset_capabilities(SPICE_CHANNEL(channel));
+}
+
+static void spice_record_channel_finalize(GObject *obj)
+{
+    SpiceRecordChannelPrivate *c = SPICE_RECORD_CHANNEL(obj)->priv;
+
+    g_free(c->last_frame);
+    c->last_frame = NULL;
+
+    snd_codec_destroy(&c->codec);
+
+    g_free(c->volume);
+    c->volume = NULL;
+
+    if (G_OBJECT_CLASS(spice_record_channel_parent_class)->finalize)
+        G_OBJECT_CLASS(spice_record_channel_parent_class)->finalize(obj);
+}
+
+static void spice_record_channel_get_property(GObject    *gobject,
+                                              guint       prop_id,
+                                              GValue     *value,
+                                              GParamSpec *pspec)
+{
+    SpiceRecordChannel *channel = SPICE_RECORD_CHANNEL(gobject);
+    SpiceRecordChannelPrivate *c = channel->priv;
+
+    switch (prop_id) {
+    case PROP_VOLUME:
+        g_value_set_pointer(value, c->volume);
+        break;
+    case PROP_NCHANNELS:
+        g_value_set_uint(value, c->nchannels);
+        break;
+    case PROP_MUTE:
+        g_value_set_boolean(value, c->mute);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
+        break;
+    }
+}
+
+static void spice_record_channel_set_property(GObject      *gobject,
+                                              guint         prop_id,
+                                              const GValue *value,
+                                              GParamSpec   *pspec)
+{
+    switch (prop_id) {
+    case PROP_VOLUME:
+        /* TODO: request guest volume change */
+        break;
+    case PROP_MUTE:
+        /* TODO: request guest mute change */
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
+        break;
+    }
+}
+
+static void channel_reset(SpiceChannel *channel, gboolean migrating)
+{
+    SpiceRecordChannelPrivate *c = SPICE_RECORD_CHANNEL(channel)->priv;
+
+    g_free(c->last_frame);
+    c->last_frame = NULL;
+
+    g_coroutine_signal_emit(channel, signals[SPICE_RECORD_STOP], 0);
+    c->started = FALSE;
+
+    snd_codec_destroy(&c->codec);
+
+    SPICE_CHANNEL_CLASS(spice_record_channel_parent_class)->channel_reset(channel, migrating);
+}
+
+static void spice_record_channel_class_init(SpiceRecordChannelClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+    SpiceChannelClass *channel_class = SPICE_CHANNEL_CLASS(klass);
+
+    gobject_class->finalize     = spice_record_channel_finalize;
+    gobject_class->get_property = spice_record_channel_get_property;
+    gobject_class->set_property = spice_record_channel_set_property;
+    channel_class->channel_reset = channel_reset;
+    channel_class->channel_reset_capabilities = spice_record_channel_reset_capabilities;
+
+    g_object_class_install_property
+        (gobject_class, PROP_NCHANNELS,
+         g_param_spec_uint("nchannels",
+                           "Number of Channels",
+                           "Number of Channels",
+                           0, G_MAXUINT8, 2,
+                           G_PARAM_READWRITE |
+                           G_PARAM_STATIC_STRINGS));
+
+    g_object_class_install_property
+        (gobject_class, PROP_VOLUME,
+         g_param_spec_pointer("volume",
+                              "Playback volume",
+                              "",
+                              G_PARAM_READWRITE |
+                              G_PARAM_STATIC_STRINGS));
+
+    g_object_class_install_property
+        (gobject_class, PROP_MUTE,
+         g_param_spec_boolean("mute",
+                              "Mute",
+                              "Mute",
+                              FALSE,
+                              G_PARAM_READWRITE |
+                              G_PARAM_STATIC_STRINGS));
+    /**
+     * SpiceRecordChannel::record-start:
+     * @channel: the #SpiceRecordChannel that emitted the signal
+     * @format: a #SPICE_AUDIO_FMT
+     * @channels: number of channels
+     * @rate: audio rate
+     *
+     * Notify when the recording should start, and provide audio format
+     * characteristics.
+     **/
+    signals[SPICE_RECORD_START] =
+        g_signal_new("record-start",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_FIRST,
+                     G_STRUCT_OFFSET(SpiceRecordChannelClass, record_start),
+                     NULL, NULL,
+                     g_cclosure_user_marshal_VOID__INT_INT_INT,
+                     G_TYPE_NONE,
+                     3,
+                     G_TYPE_INT, G_TYPE_INT, G_TYPE_INT);
+
+    /**
+     * SpiceRecordChannel::record-stop:
+     * @channel: the #SpiceRecordChannel that emitted the signal
+     *
+     * Notify when the recording should stop.
+     **/
+    signals[SPICE_RECORD_STOP] =
+        g_signal_new("record-stop",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_FIRST,
+                     G_STRUCT_OFFSET(SpiceRecordChannelClass, record_stop),
+                     NULL, NULL,
+                     g_cclosure_marshal_VOID__VOID,
+                     G_TYPE_NONE,
+                     0);
+
+    g_type_class_add_private(klass, sizeof(SpiceRecordChannelPrivate));
+    channel_set_handlers(SPICE_CHANNEL_CLASS(klass));
+}
+
+/* main context */
+static void spice_record_mode(SpiceRecordChannel *channel, uint32_t time,
+                              uint32_t mode, uint8_t *data, uint32_t data_size)
+{
+    SpiceMsgcRecordMode m = {0, };
+    SpiceMsgOut *msg;
+
+    g_return_if_fail(channel != NULL);
+    if (spice_channel_get_read_only(SPICE_CHANNEL(channel)))
+        return;
+
+    m.mode = mode;
+    m.time = time;
+    m.data = data;
+    m.data_size = data_size;
+
+    msg = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_RECORD_MODE);
+    msg->marshallers->msgc_record_mode(msg->marshaller, &m);
+    spice_msg_out_send(msg);
+}
+
+static int spice_record_desired_mode(SpiceChannel *channel, int frequency)
+{
+    if (!g_getenv("SPICE_DISABLE_OPUS") &&
+        snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_OPUS, frequency) &&
+        spice_channel_test_capability(channel, SPICE_RECORD_CAP_OPUS)) {
+        return SPICE_AUDIO_DATA_MODE_OPUS;
+    } else if (!g_getenv("SPICE_DISABLE_CELT") &&
+        snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_CELT_0_5_1, frequency) &&
+        spice_channel_test_capability(channel, SPICE_RECORD_CAP_CELT_0_5_1)) {
+        return SPICE_AUDIO_DATA_MODE_CELT_0_5_1;
+    } else {
+        return SPICE_AUDIO_DATA_MODE_RAW;
+    }
+}
+
+/* main context */
+static void spice_record_start_mark(SpiceRecordChannel *channel, uint32_t time)
+{
+    SpiceMsgcRecordStartMark m = {0, };
+    SpiceMsgOut *msg;
+
+    g_return_if_fail(channel != NULL);
+    if (spice_channel_get_read_only(SPICE_CHANNEL(channel)))
+        return;
+
+    m.time = time;
+
+    msg = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_RECORD_START_MARK);
+    msg->marshallers->msgc_record_start_mark(msg->marshaller, &m);
+    spice_msg_out_send(msg);
+}
+
+/**
+ * spice_record_send_data:
+ * @channel:
+ * @data: PCM data
+ * @bytes: size of @data
+ * @time: stream timestamp
+ *
+ * Send recorded PCM data to the guest.
+ **/
+void spice_record_send_data(SpiceRecordChannel *channel, gpointer data,
+                            gsize bytes, uint32_t time)
+{
+    SpiceRecordChannelPrivate *rc;
+    SpiceMsgcRecordPacket p = {0, };
+
+    g_return_if_fail(SPICE_IS_RECORD_CHANNEL(channel));
+    rc = channel->priv;
+    if (rc->last_frame == NULL) {
+        CHANNEL_DEBUG(channel, "recording didn't start or was reset");
+        return;
+    }
+
+    g_return_if_fail(spice_channel_get_read_only(SPICE_CHANNEL(channel)) == FALSE);
+
+    uint8_t *encode_buf = NULL;
+
+    if (!rc->started) {
+        spice_record_mode(channel, time, rc->mode, NULL, 0);
+        spice_record_start_mark(channel, time);
+        rc->started = TRUE;
+    }
+
+    if (rc->mode != SPICE_AUDIO_DATA_MODE_RAW)
+        encode_buf = g_alloca(SND_CODEC_MAX_COMPRESSED_BYTES);
+
+    p.time = time;
+
+    while (bytes > 0) {
+        gsize n;
+        int frame_size;
+        SpiceMsgOut *msg;
+        uint8_t *frame;
+
+        if (rc->last_frame_current > 0) {
+            /* complete previous frame */
+            n = MIN(bytes, rc->frame_bytes - rc->last_frame_current);
+            memcpy(rc->last_frame + rc->last_frame_current, data, n);
+            rc->last_frame_current += n;
+            if (rc->last_frame_current < rc->frame_bytes)
+                /* if the frame is still incomplete, return */
+                break;
+            frame = rc->last_frame;
+            frame_size = rc->frame_bytes;
+        } else {
+            n = MIN(bytes, rc->frame_bytes);
+            frame_size = n;
+            frame = data;
+        }
+
+        if (rc->last_frame_current == 0 &&
+            n < rc->frame_bytes) {
+            /* start a new frame */
+            memcpy(rc->last_frame, data, n);
+            rc->last_frame_current = n;
+            break;
+        }
+
+        if (rc->mode != SPICE_AUDIO_DATA_MODE_RAW) {
+            int len = SND_CODEC_MAX_COMPRESSED_BYTES;
+            if (snd_codec_encode(rc->codec, frame, frame_size, encode_buf, &len) != SND_CODEC_OK) {
+                g_warning("encode failed");
+                return;
+            }
+            frame = encode_buf;
+            frame_size = len;
+        }
+
+        msg = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_RECORD_DATA);
+        msg->marshallers->msgc_record_data(msg->marshaller, &p);
+        spice_marshaller_add(msg->marshaller, frame, frame_size);
+        spice_msg_out_send(msg);
+
+        if (rc->last_frame_current == rc->frame_bytes)
+            rc->last_frame_current = 0;
+
+        bytes -= n;
+        data = (guint8*)data + n;
+    }
+}
+
+/* ------------------------------------------------------------------ */
+
+/* coroutine context */
+static void record_handle_start(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceRecordChannelPrivate *c = SPICE_RECORD_CHANNEL(channel)->priv;
+    SpiceMsgRecordStart *start = spice_msg_in_parsed(in);
+    int frame_size = SND_CODEC_MAX_FRAME_SIZE;
+
+    c->mode = spice_record_desired_mode(channel, start->frequency);
+
+    CHANNEL_DEBUG(channel, "%s: fmt %d channels %d freq %d", __FUNCTION__,
+                  start->format, start->channels, start->frequency);
+
+    g_return_if_fail(start->format == SPICE_AUDIO_FMT_S16);
+
+    snd_codec_destroy(&c->codec);
+
+    if (c->mode != SPICE_AUDIO_DATA_MODE_RAW) {
+        if (snd_codec_create(&c->codec, c->mode, start->frequency, SND_CODEC_ENCODE) != SND_CODEC_OK) {
+            g_warning("Failed to create encoder");
+            return;
+        }
+        frame_size = snd_codec_frame_size(c->codec);
+    }
+
+    g_free(c->last_frame);
+    c->frame_bytes = frame_size * 16 * start->channels / 8;
+    c->last_frame = g_malloc0(c->frame_bytes);
+    c->last_frame_current = 0;
+
+    g_coroutine_signal_emit(channel, signals[SPICE_RECORD_START], 0,
+                            start->format, start->channels, start->frequency);
+}
+
+/* coroutine context */
+static void record_handle_stop(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceRecordChannelPrivate *rc = SPICE_RECORD_CHANNEL(channel)->priv;
+
+    g_coroutine_signal_emit(channel, signals[SPICE_RECORD_STOP], 0);
+    rc->started = FALSE;
+}
+
+/* coroutine context */
+static void record_handle_set_volume(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceRecordChannelPrivate *c = SPICE_RECORD_CHANNEL(channel)->priv;
+    SpiceMsgAudioVolume *vol = spice_msg_in_parsed(in);
+
+    if (vol->nchannels == 0) {
+        g_warning("spice-server send audio-volume-msg with 0 channels");
+        return;
+    }
+
+    g_free(c->volume);
+    c->nchannels = vol->nchannels;
+    c->volume = g_new(guint16, c->nchannels);
+    memcpy(c->volume, vol->volume, sizeof(guint16) * c->nchannels);
+    g_coroutine_object_notify(G_OBJECT(channel), "volume");
+}
+
+/* coroutine context */
+static void record_handle_set_mute(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceRecordChannelPrivate *c = SPICE_RECORD_CHANNEL(channel)->priv;
+    SpiceMsgAudioMute *m = spice_msg_in_parsed(in);
+
+    c->mute = m->mute;
+    g_coroutine_object_notify(G_OBJECT(channel), "mute");
+}
+
+static void channel_set_handlers(SpiceChannelClass *klass)
+{
+    static const spice_msg_handler handlers[] = {
+        [ SPICE_MSG_RECORD_START ]             = record_handle_start,
+        [ SPICE_MSG_RECORD_STOP ]              = record_handle_stop,
+        [ SPICE_MSG_RECORD_VOLUME ]            = record_handle_set_volume,
+        [ SPICE_MSG_RECORD_MUTE ]              = record_handle_set_mute,
+    };
+
+    spice_channel_set_handlers(klass, handlers, G_N_ELEMENTS(handlers));
+}
diff --git a/src/channel-record.h b/src/channel-record.h
new file mode 100644
index 0000000..20a9ad3
--- /dev/null
+++ b/src/channel-record.h
@@ -0,0 +1,77 @@
+/* -*- 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_CLIENT_RECORD_CHANNEL_H__
+#define __SPICE_CLIENT_RECORD_CHANNEL_H__
+
+#include "spice-client.h"
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_RECORD_CHANNEL            (spice_record_channel_get_type())
+#define SPICE_RECORD_CHANNEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), SPICE_TYPE_RECORD_CHANNEL, SpiceRecordChannel))
+#define SPICE_RECORD_CHANNEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), SPICE_TYPE_RECORD_CHANNEL, SpiceRecordChannelClass))
+#define SPICE_IS_RECORD_CHANNEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), SPICE_TYPE_RECORD_CHANNEL))
+#define SPICE_IS_RECORD_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SPICE_TYPE_RECORD_CHANNEL))
+#define SPICE_RECORD_CHANNEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), SPICE_TYPE_RECORD_CHANNEL, SpiceRecordChannelClass))
+
+typedef struct _SpiceRecordChannel SpiceRecordChannel;
+typedef struct _SpiceRecordChannelClass SpiceRecordChannelClass;
+typedef struct _SpiceRecordChannelPrivate SpiceRecordChannelPrivate;
+
+/**
+ * SpiceRecordChannel:
+ *
+ * The #SpiceRecordChannel struct is opaque and should not be accessed directly.
+ */
+struct _SpiceRecordChannel {
+    SpiceChannel parent;
+
+    /*< private >*/
+    SpiceRecordChannelPrivate *priv;
+    /* Do not add fields to this struct */
+};
+
+/**
+ * SpiceRecordChannelClass:
+ * @parent_class: Parent class.
+ * @record_start: Signal class handler for the #SpiceRecordChannel::record-start signal.
+ * @record_stop: Signal class handler for the #SpiceRecordChannel::record-stop signal.
+ * @record_data: Unused (deprecated).
+ *
+ * Class structure for #SpiceRecordChannel.
+ */
+struct _SpiceRecordChannelClass {
+    SpiceChannelClass parent_class;
+
+    /* signals */
+    void (*record_start)(SpiceRecordChannel *channel,
+                         gint format, gint channels, gint freq);
+    void (*record_data)(SpiceRecordChannel *channel, gpointer *data, gint size);
+    void (*record_stop)(SpiceRecordChannel *channel);
+
+    /*< private >*/
+    /* Do not add fields to this struct */
+};
+
+GType	        spice_record_channel_get_type(void);
+void            spice_record_send_data(SpiceRecordChannel *channel, gpointer data,
+                                       gsize bytes, guint32 time);
+
+G_END_DECLS
+
+#endif /* __SPICE_CLIENT_RECORD_CHANNEL_H__ */
diff --git a/src/channel-smartcard.c b/src/channel-smartcard.c
new file mode 100644
index 0000000..d91c9a0
--- /dev/null
+++ b/src/channel-smartcard.c
@@ -0,0 +1,587 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 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"
+
+#ifdef USE_SMARTCARD
+#include <vreader.h>
+#endif
+
+#include "spice-client.h"
+#include "spice-common.h"
+
+#include "spice-channel-priv.h"
+#include "smartcard-manager.h"
+#include "smartcard-manager-priv.h"
+#include "spice-session-priv.h"
+
+/**
+ * SECTION:channel-smartcard
+ * @short_description: smartcard authentication
+ * @title: Smartcard Channel
+ * @section_id:
+ * @see_also: #SpiceSmartcardManager, #SpiceSession
+ * @stability: API Stable (channel in development)
+ * @include: channel-smartcard.h
+ *
+ * The Spice protocol defines a set of messages to forward smartcard
+ * information from the Spice client to the VM. This channel handles
+ * these messages. While it's mainly focus on smartcard readers and
+ * smartcards, it's also possible to use it with a software smartcard
+ * (ie a set of 3 certificates from the client machine).
+ * This class doesn't provide useful methods, see #SpiceSession properties
+ * for a way to enable/disable this channel, and #SpiceSmartcardManager
+ * if you want to detect smartcard reader hotplug/unplug, and smartcard
+ * insertion/removal.
+ */
+
+#define SPICE_SMARTCARD_CHANNEL_GET_PRIVATE(obj)                                  \
+    (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_SMARTCARD_CHANNEL, SpiceSmartcardChannelPrivate))
+
+struct _SpiceSmartcardChannelMessage {
+#ifdef USE_SMARTCARD
+    VSCMsgType message_type;
+#endif
+    SpiceMsgOut *message;
+};
+typedef struct _SpiceSmartcardChannelMessage SpiceSmartcardChannelMessage;
+
+
+struct _SpiceSmartcardChannelPrivate {
+    /* track readers that have been added but for which we didn't receive
+     * an ack from the spice server yet. We rely on the fact that the
+     * readers in this list are ordered by the time we sent the request to
+     * the server. When we get an ack from the server for a reader addition,
+     * we can pop the 1st entry to get the reader the ack corresponds to. */
+    GList *pending_reader_additions;
+
+    /* used to removals of readers that were not ack'ed yet by the spice
+     * server */
+    GHashTable *pending_reader_removals;
+
+    /* used to track card insertions on readers that were not ack'ed yet
+     * by the spice server */
+    GHashTable *pending_card_insertions;
+
+    /* next commands to be sent to the spice server. This is needed since
+     * we have to wait for a command answer before sending the next one
+     */
+    GQueue *message_queue;
+
+    /* message that is currently being processed by the spice server (ie last
+     * message that was sent to the server)
+     */
+    SpiceSmartcardChannelMessage *in_flight_message;
+};
+
+G_DEFINE_TYPE(SpiceSmartcardChannel, spice_smartcard_channel, SPICE_TYPE_CHANNEL)
+
+enum {
+
+    SPICE_SMARTCARD_LAST_SIGNAL,
+};
+
+static void spice_smartcard_channel_up(SpiceChannel *channel);
+static void handle_smartcard_msg(SpiceChannel *channel, SpiceMsgIn *in);
+static void smartcard_message_free(SpiceSmartcardChannelMessage *message);
+
+/* ------------------------------------------------------------------ */
+#ifdef USE_SMARTCARD
+static void reader_added_cb(SpiceSmartcardManager *manager, VReader *reader,
+                            gpointer user_data);
+static void reader_removed_cb(SpiceSmartcardManager *manager, VReader *reader,
+                              gpointer user_data);
+static void card_inserted_cb(SpiceSmartcardManager *manager, VReader *reader,
+                             gpointer user_data);
+static void card_removed_cb(SpiceSmartcardManager *manager, VReader *reader,
+                            gpointer user_data);
+#endif
+
+static void spice_smartcard_channel_init(SpiceSmartcardChannel *channel)
+{
+    SpiceSmartcardChannelPrivate *priv;
+
+    channel->priv = SPICE_SMARTCARD_CHANNEL_GET_PRIVATE(channel);
+    priv = channel->priv;
+    priv->message_queue = g_queue_new();
+
+#ifdef USE_SMARTCARD
+    priv->pending_card_insertions =
+        g_hash_table_new_full(g_direct_hash, g_direct_equal,
+                              (GDestroyNotify)vreader_free, NULL);
+    priv->pending_reader_removals =
+         g_hash_table_new_full(g_direct_hash, g_direct_equal,
+                               (GDestroyNotify)vreader_free, NULL);
+#endif
+}
+
+static void spice_smartcard_channel_constructed(GObject *object)
+{
+    SpiceSession *s = spice_channel_get_session(SPICE_CHANNEL(object));
+
+    g_return_if_fail(s != NULL);
+
+#ifdef USE_SMARTCARD
+    if (!spice_session_is_for_migration(s)) {
+        SpiceSmartcardChannel *channel = SPICE_SMARTCARD_CHANNEL(object);
+        SpiceSmartcardManager *manager = spice_smartcard_manager_get();
+
+        spice_g_signal_connect_object(G_OBJECT(manager), "reader-added",
+                                      (GCallback)reader_added_cb, channel, 0);
+        spice_g_signal_connect_object(G_OBJECT(manager), "reader-removed",
+                                      (GCallback)reader_removed_cb, channel, 0);
+        spice_g_signal_connect_object(G_OBJECT(manager), "card-inserted",
+                                      (GCallback)card_inserted_cb, channel, 0);
+        spice_g_signal_connect_object(G_OBJECT(manager), "card-removed",
+                                      (GCallback)card_removed_cb, channel, 0);
+    }
+#endif
+
+    if (G_OBJECT_CLASS(spice_smartcard_channel_parent_class)->constructed)
+        G_OBJECT_CLASS(spice_smartcard_channel_parent_class)->constructed(object);
+
+}
+
+static void spice_smartcard_channel_finalize(GObject *obj)
+{
+    SpiceSmartcardChannel *channel = SPICE_SMARTCARD_CHANNEL(obj);
+    SpiceSmartcardChannelPrivate *c = channel->priv;
+
+    if (c->pending_card_insertions != NULL) {
+        g_hash_table_destroy(c->pending_card_insertions);
+        c->pending_card_insertions = NULL;
+    }
+    if (c->pending_reader_removals != NULL) {
+        g_hash_table_destroy(c->pending_reader_removals);
+        c->pending_reader_removals = NULL;
+    }
+    if (c->message_queue != NULL) {
+        g_queue_foreach(c->message_queue, (GFunc)smartcard_message_free, NULL);
+        g_queue_free(c->message_queue);
+        c->message_queue = NULL;
+    }
+    if (c->in_flight_message != NULL) {
+        smartcard_message_free(c->in_flight_message);
+        c->in_flight_message = NULL;
+    }
+
+    g_list_free(c->pending_reader_additions);
+    c->pending_reader_additions = NULL;
+
+    if (G_OBJECT_CLASS(spice_smartcard_channel_parent_class)->finalize)
+        G_OBJECT_CLASS(spice_smartcard_channel_parent_class)->finalize(obj);
+}
+
+static void spice_smartcard_channel_reset(SpiceChannel *channel, gboolean migrating)
+{
+    SpiceSmartcardChannel *smartcard_channel = SPICE_SMARTCARD_CHANNEL(channel);
+    SpiceSmartcardChannelPrivate *c = smartcard_channel->priv;
+
+    g_hash_table_remove_all(c->pending_card_insertions);
+    g_hash_table_remove_all(c->pending_reader_removals);
+
+    if (c->message_queue != NULL) {
+        g_queue_foreach(c->message_queue, (GFunc)smartcard_message_free, NULL);
+        g_queue_clear(c->message_queue);
+    }
+
+    if (c->in_flight_message != NULL) {
+        smartcard_message_free(c->in_flight_message);
+        c->in_flight_message = NULL;
+    }
+
+    g_list_free(c->pending_reader_additions);
+    c->pending_reader_additions = NULL;
+
+    SPICE_CHANNEL_CLASS(spice_smartcard_channel_parent_class)->channel_reset(channel, migrating);
+}
+
+static void channel_set_handlers(SpiceChannelClass *klass)
+{
+    static const spice_msg_handler handlers[] = {
+        [ SPICE_MSG_SMARTCARD_DATA ] = handle_smartcard_msg,
+    };
+    spice_channel_set_handlers(klass, handlers, G_N_ELEMENTS(handlers));
+}
+
+static void spice_smartcard_channel_class_init(SpiceSmartcardChannelClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+    SpiceChannelClass *channel_class = SPICE_CHANNEL_CLASS(klass);
+
+    gobject_class->finalize     = spice_smartcard_channel_finalize;
+    gobject_class->constructed  = spice_smartcard_channel_constructed;
+
+    channel_class->channel_up   = spice_smartcard_channel_up;
+    channel_class->channel_reset = spice_smartcard_channel_reset;
+
+    g_type_class_add_private(klass, sizeof(SpiceSmartcardChannelPrivate));
+    channel_set_handlers(SPICE_CHANNEL_CLASS(klass));
+}
+
+/* ------------------------------------------------------------------ */
+/* private api                                                        */
+
+static void
+smartcard_message_free(SpiceSmartcardChannelMessage *message)
+{
+    if (message->message)
+        spice_msg_out_unref(message->message);
+    g_slice_free(SpiceSmartcardChannelMessage, message);
+}
+
+#if USE_SMARTCARD
+static gboolean is_attached_to_server(VReader *reader)
+{
+    return (vreader_get_id(reader) != (vreader_id_t)-1);
+}
+
+static gboolean
+spice_channel_has_pending_card_insertion(SpiceSmartcardChannel *channel,
+                                         VReader *reader)
+{
+    return (g_hash_table_lookup(channel->priv->pending_card_insertions, reader) != NULL);
+}
+
+static void
+spice_channel_queue_card_insertion(SpiceSmartcardChannel *channel,
+                                   VReader *reader)
+{
+    vreader_reference(reader);
+    g_hash_table_insert(channel->priv->pending_card_insertions,
+                        reader, reader);
+}
+
+static void
+spice_channel_drop_pending_card_insertion(SpiceSmartcardChannel *channel,
+                                          VReader *reader)
+{
+    g_hash_table_remove(channel->priv->pending_card_insertions, reader);
+}
+
+static gboolean
+spice_channel_has_pending_reader_removal(SpiceSmartcardChannel *channel,
+                                         VReader *reader)
+{
+    return (g_hash_table_lookup(channel->priv->pending_reader_removals, reader) != NULL);
+}
+
+static void
+spice_channel_queue_reader_removal(SpiceSmartcardChannel *channel,
+                                   VReader *reader)
+{
+    vreader_reference(reader);
+    g_hash_table_insert(channel->priv->pending_reader_removals,
+                        reader, reader);
+}
+
+static void
+spice_channel_drop_pending_reader_removal(SpiceSmartcardChannel *channel,
+                                          VReader *reader)
+{
+    g_hash_table_remove(channel->priv->pending_reader_removals, reader);
+}
+
+static SpiceSmartcardChannelMessage *
+smartcard_message_new(VSCMsgType msg_type, SpiceMsgOut *msg_out)
+{
+    SpiceSmartcardChannelMessage *message;
+
+    message = g_slice_new0(SpiceSmartcardChannelMessage);
+    message->message = msg_out;
+    message->message_type = msg_type;
+
+    return message;
+}
+
+/* Indicates that handling of the message that is currently in flight has
+ * been completed. If needed, sends the next queued command to the server. */
+static void
+smartcard_message_complete_in_flight(SpiceSmartcardChannel *channel)
+{
+    g_return_if_fail(channel->priv->in_flight_message != NULL);
+
+    smartcard_message_free(channel->priv->in_flight_message);
+    channel->priv->in_flight_message = g_queue_pop_head(channel->priv->message_queue);
+    if (channel->priv->in_flight_message != NULL) {
+        spice_msg_out_send(channel->priv->in_flight_message->message);
+        channel->priv->in_flight_message->message = NULL;
+    }
+}
+
+static void smartcard_message_send(SpiceSmartcardChannel *channel,
+                                   VSCMsgType msg_type,
+                                   SpiceMsgOut *msg_out, gboolean queue)
+{
+    SpiceSmartcardChannelMessage *message;
+
+    if (spice_channel_get_read_only(SPICE_CHANNEL(channel)))
+        return;
+
+    CHANNEL_DEBUG(channel, "send message %d, %s",
+                  msg_type, queue ? "queued" : "now");
+    if (!queue) {
+        spice_msg_out_send(msg_out);
+        return;
+    }
+
+    message = smartcard_message_new(msg_type, msg_out);
+    if (channel->priv->in_flight_message == NULL) {
+        g_return_if_fail(g_queue_is_empty(channel->priv->message_queue));
+        channel->priv->in_flight_message = message;
+        spice_msg_out_send(channel->priv->in_flight_message->message);
+        channel->priv->in_flight_message->message = NULL;
+    } else {
+        g_queue_push_tail(channel->priv->message_queue, message);
+    }
+}
+
+static void
+send_msg_generic_with_data(SpiceSmartcardChannel *channel, VReader *reader,
+                           VSCMsgType msg_type,
+                           const uint8_t *data, gsize data_len,
+                           gboolean serialize_msg)
+{
+    SpiceMsgOut *msg_out;
+    VSCMsgHeader header = {
+        .type = msg_type,
+        .length = data_len
+    };
+
+    if(vreader_get_id(reader) == -1)
+        header.reader_id = VSCARD_UNDEFINED_READER_ID;
+    else
+        header.reader_id = vreader_get_id(reader);
+
+    msg_out = spice_msg_out_new(SPICE_CHANNEL(channel),
+                                SPICE_MSGC_SMARTCARD_DATA);
+    msg_out->marshallers->msgc_smartcard_header(msg_out->marshaller, &header);
+    if ((data != NULL) && (data_len != 0)) {
+        spice_marshaller_add(msg_out->marshaller, data, data_len);
+    }
+
+    smartcard_message_send(channel, msg_type, msg_out, serialize_msg);
+}
+
+static void send_msg_generic(SpiceSmartcardChannel *channel, VReader *reader,
+                             VSCMsgType msg_type)
+{
+    send_msg_generic_with_data(channel, reader, msg_type, NULL, 0, TRUE);
+}
+
+static void send_msg_atr(SpiceSmartcardChannel *channel, VReader *reader)
+{
+#define MAX_ATR_LEN 40 //this should be defined in libcacard
+    uint8_t atr[MAX_ATR_LEN];
+    int atr_len = MAX_ATR_LEN;
+
+    g_return_if_fail(vreader_get_id(reader) != VSCARD_UNDEFINED_READER_ID);
+    vreader_power_on(reader, atr, &atr_len);
+    send_msg_generic_with_data(channel, reader, VSC_ATR, atr, atr_len, TRUE);
+}
+
+static void reader_added_cb(SpiceSmartcardManager *manager, VReader *reader,
+                            gpointer user_data)
+{
+    SpiceSmartcardChannel *channel = SPICE_SMARTCARD_CHANNEL(user_data);
+    const char *reader_name = vreader_get_name(reader);
+
+    if (vreader_get_id(reader) != -1 ||
+        g_list_find(channel->priv->pending_reader_additions, reader))
+        return;
+
+    channel->priv->pending_reader_additions =
+        g_list_append(channel->priv->pending_reader_additions, reader);
+
+    send_msg_generic_with_data(channel, reader, VSC_ReaderAdd,
+                               (uint8_t*)reader_name, strlen(reader_name), TRUE);
+}
+
+static void reader_removed_cb(SpiceSmartcardManager *manager, VReader *reader,
+                              gpointer user_data)
+{
+    SpiceSmartcardChannel *channel = SPICE_SMARTCARD_CHANNEL(user_data);
+
+    if (is_attached_to_server(reader)) {
+        send_msg_generic(channel, reader, VSC_ReaderRemove);
+    } else {
+        spice_channel_queue_reader_removal(channel, reader);
+    }
+}
+
+/* ------------------------------------------------------------------ */
+/* callbacks                                                          */
+static void card_inserted_cb(SpiceSmartcardManager *manager, VReader *reader,
+                             gpointer user_data)
+{
+    SpiceSmartcardChannel *channel = SPICE_SMARTCARD_CHANNEL(user_data);
+
+    if (is_attached_to_server(reader)) {
+        send_msg_atr(channel, reader);
+    } else {
+        spice_channel_queue_card_insertion(channel, reader);
+    }
+}
+
+static void card_removed_cb(SpiceSmartcardManager *manager, VReader *reader,
+                            gpointer user_data)
+{
+    SpiceSmartcardChannel *channel = SPICE_SMARTCARD_CHANNEL(user_data);
+
+    if (is_attached_to_server(reader)) {
+        send_msg_generic(channel, reader, VSC_CardRemove);
+    } else {
+        /* this does nothing when reader has no card insertion pending */
+        spice_channel_drop_pending_card_insertion(channel, reader);
+    }
+}
+#endif /* USE_SMARTCARD */
+
+static void spice_smartcard_channel_up_cb(GObject *source_object,
+                                          GAsyncResult *res,
+                                          gpointer user_data)
+{
+    SpiceChannel *channel = SPICE_CHANNEL(user_data);
+#ifdef USE_SMARTCARD
+    SpiceSmartcardManager *manager = spice_smartcard_manager_get();
+    GList *l, *list = NULL;
+#endif
+    GError *error = NULL;
+
+    g_return_if_fail(channel != NULL);
+    g_return_if_fail(SPICE_IS_SESSION(source_object));
+
+    spice_smartcard_manager_init_finish(SPICE_SESSION(source_object),
+                                        res, &error);
+    if (error) {
+        g_warning("%s", error->message);
+        goto end;
+    }
+
+#ifdef USE_SMARTCARD
+    list = spice_smartcard_manager_get_readers(manager);
+    for (l = list; l != NULL; l = l->next) {
+        VReader *reader = l->data;
+        gboolean has_card = vreader_card_is_present(reader) == VREADER_OK;
+
+        reader_added_cb(manager, reader, channel);
+        if (has_card)
+            card_inserted_cb(manager, reader, channel);
+
+        g_boxed_free(SPICE_TYPE_SMARTCARD_READER, reader);
+    }
+#endif
+
+end:
+#ifdef USE_SMARTCARD
+    g_list_free(list);
+#endif
+    g_clear_error(&error);
+}
+
+static void spice_smartcard_channel_up(SpiceChannel *channel)
+{
+    if (spice_session_is_for_migration(spice_channel_get_session(channel)))
+        return;
+
+    spice_smartcard_manager_init_async(spice_channel_get_session(channel),
+                                       g_cancellable_new(),
+                                       spice_smartcard_channel_up_cb,
+                                       channel);
+}
+
+static void handle_smartcard_msg(SpiceChannel *channel, SpiceMsgIn *in)
+{
+#ifdef USE_SMARTCARD
+    SpiceSmartcardChannel *smartcard_channel = SPICE_SMARTCARD_CHANNEL(channel);
+    SpiceSmartcardChannelPrivate *priv = smartcard_channel->priv;
+    SpiceMsgSmartcard *msg = spice_msg_in_parsed(in);
+    VReader *reader;
+
+    CHANNEL_DEBUG(channel, "handle msg %d", msg->type);
+    switch (msg->type) {
+        case VSC_Error:
+            g_return_if_fail(priv->in_flight_message != NULL);
+            CHANNEL_DEBUG(channel, "in flight %d", priv->in_flight_message->message_type);
+            switch (priv->in_flight_message->message_type) {
+                case VSC_ReaderAdd:
+                    g_return_if_fail(priv->pending_reader_additions != NULL);
+                    reader = priv->pending_reader_additions->data;
+                    g_return_if_fail(reader != NULL);
+                    g_return_if_fail(vreader_get_id(reader) == -1);
+                    priv->pending_reader_additions =
+                        g_list_delete_link(priv->pending_reader_additions,
+                                           priv->pending_reader_additions);
+                    vreader_set_id(reader, msg->reader_id);
+
+                    if (spice_channel_has_pending_card_insertion(smartcard_channel, reader)) {
+                        send_msg_atr(smartcard_channel, reader);
+                        spice_channel_drop_pending_card_insertion(smartcard_channel, reader);
+                    }
+
+                    if (spice_channel_has_pending_reader_removal(smartcard_channel, reader)) {
+                        send_msg_generic(smartcard_channel, reader, VSC_CardRemove);
+                        spice_channel_drop_pending_reader_removal(smartcard_channel, reader);
+                    }
+                    break;
+                case VSC_APDU:
+                case VSC_ATR:
+                case VSC_CardRemove:
+                case VSC_Error:
+                case VSC_ReaderRemove:
+                    break;
+                default:
+                    g_warning("Unexpected message: %d", priv->in_flight_message->message_type);
+                    break;
+            }
+            smartcard_message_complete_in_flight(smartcard_channel);
+
+            break;
+
+        case VSC_APDU:
+        case VSC_Init: {
+            const unsigned int APDU_BUFFER_SIZE = 270;
+            VReaderStatus reader_status;
+            uint8_t data_out[APDU_BUFFER_SIZE + sizeof(uint32_t)];
+            int data_out_len = sizeof(data_out);
+
+            g_return_if_fail(msg->reader_id != VSCARD_UNDEFINED_READER_ID);
+            reader = vreader_get_reader_by_id(msg->reader_id);
+            g_return_if_fail(reader != NULL); //FIXME: add log message
+
+            reader_status = vreader_xfr_bytes(reader,
+                                              msg->data, msg->length,
+                                              data_out, &data_out_len);
+            if (reader_status == VREADER_OK) {
+                send_msg_generic_with_data(smartcard_channel,
+                                           reader, VSC_APDU,
+                                           data_out, data_out_len, FALSE);
+            } else {
+                uint32_t error_code;
+                error_code = GUINT32_TO_LE(reader_status);
+                send_msg_generic_with_data(smartcard_channel,
+                                           reader, VSC_Error,
+                                           (uint8_t*)&error_code,
+                                           sizeof (error_code), FALSE);
+            }
+            break;
+        }
+        default:
+            g_return_if_reached();
+    }
+#endif
+}
diff --git a/src/channel-smartcard.h b/src/channel-smartcard.h
new file mode 100644
index 0000000..28c8b88
--- /dev/null
+++ b/src/channel-smartcard.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 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/>.
+*/
+#ifndef __SPICE_CLIENT_SMARTCARD_CHANNEL_H__
+#define __SPICE_CLIENT_SMARTCARD_CHANNEL_H__
+
+#include "spice-client.h"
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_SMARTCARD_CHANNEL            (spice_smartcard_channel_get_type())
+#define SPICE_SMARTCARD_CHANNEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), SPICE_TYPE_SMARTCARD_CHANNEL, SpiceSmartcardChannel))
+#define SPICE_SMARTCARD_CHANNEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), SPICE_TYPE_SMARTCARD_CHANNEL, SpiceSmartcardChannelClass))
+#define SPICE_IS_SMARTCARD_CHANNEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), SPICE_TYPE_SMARTCARD_CHANNEL))
+#define SPICE_IS_SMARTCARD_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SPICE_TYPE_SMARTCARD_CHANNEL))
+#define SPICE_SMARTCARD_CHANNEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), SPICE_TYPE_SMARTCARD_CHANNEL, SpiceSmartcardChannelClass))
+
+typedef struct _SpiceSmartcardChannel SpiceSmartcardChannel;
+typedef struct _SpiceSmartcardChannelClass SpiceSmartcardChannelClass;
+typedef struct _SpiceSmartcardChannelPrivate SpiceSmartcardChannelPrivate;
+
+/**
+ * SpiceSmartcardChannel:
+ *
+ * The #SpiceSmartcardChannel struct is opaque and should not be accessed directly.
+ */
+struct _SpiceSmartcardChannel {
+    SpiceChannel parent;
+
+    /*< private >*/
+    SpiceSmartcardChannelPrivate *priv;
+    /* Do not add fields to this struct */
+};
+
+/**
+ * SpiceSmartcardChannelClass:
+ * @parent_class: Parent class.
+ *
+ * Class structure for #SpiceSmartcardChannel.
+ */
+struct _SpiceSmartcardChannelClass {
+    SpiceChannelClass parent_class;
+
+    /* signals */
+
+    /*< private >*/
+    /* Do not add fields to this struct */
+};
+
+GType spice_smartcard_channel_get_type(void);
+
+G_END_DECLS
+
+#endif /* __SPICE_CLIENT_SMARTCARD_CHANNEL_H__ */
diff --git a/src/channel-usbredir-priv.h b/src/channel-usbredir-priv.h
new file mode 100644
index 0000000..2c4c6f7
--- /dev/null
+++ b/src/channel-usbredir-priv.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2011 Red Hat, Inc.
+
+   Red Hat Authors:
+   Hans de Goede <hdegoede at redhat.com>
+
+   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_CLIENT_USBREDIR_CHANNEL_PRIV_H__
+#define __SPICE_CLIENT_USBREDIR_CHANNEL_PRIV_H__
+
+#include <libusb.h>
+#include <usbredirfilter.h>
+#include "spice-client.h"
+
+G_BEGIN_DECLS
+
+/* Note: this must be called before calling any other functions, and the
+   context should not be destroyed before the last device has been
+   disconnected */
+void spice_usbredir_channel_set_context(SpiceUsbredirChannel *channel,
+                                        libusb_context       *context);
+
+/* Note the context must be set, and the channel must be brought up
+   (through spice_channel_connect()), before calling this. */
+void spice_usbredir_channel_connect_device_async(
+                                        SpiceUsbredirChannel *channel,
+                                        libusb_device        *device,
+                                        SpiceUsbDevice       *spice_device,
+                                        GCancellable         *cancellable,
+                                        GAsyncReadyCallback   callback,
+                                        gpointer              user_data);
+gboolean spice_usbredir_channel_connect_device_finish(
+                                        SpiceUsbredirChannel *channel,
+                                        GAsyncResult         *res,
+                                        GError              **err);
+
+void spice_usbredir_channel_disconnect_device(SpiceUsbredirChannel *channel);
+
+libusb_device *spice_usbredir_channel_get_device(SpiceUsbredirChannel *channel);
+
+void spice_usbredir_channel_get_guest_filter(
+                          SpiceUsbredirChannel               *channel,
+                          const struct usbredirfilter_rule  **rules_ret,
+                          int                                *rules_count_ret);
+
+G_END_DECLS
+
+#endif /* __SPICE_CLIENT_USBREDIR_CHANNEL_PRIV_H__ */
diff --git a/src/channel-usbredir.c b/src/channel-usbredir.c
new file mode 100644
index 0000000..d974434
--- /dev/null
+++ b/src/channel-usbredir.c
@@ -0,0 +1,686 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2010-2012 Red Hat, Inc.
+
+   Red Hat Authors:
+   Hans de Goede <hdegoede at redhat.com>
+   Richard Hughes <rhughes at redhat.com>
+
+   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"
+
+#ifdef USE_USBREDIR
+#include <glib/gi18n.h>
+#include <usbredirhost.h>
+#if USE_POLKIT
+#include "usb-acl-helper.h"
+#endif
+#include "channel-usbredir-priv.h"
+#include "usb-device-manager-priv.h"
+#include "usbutil.h"
+#endif
+
+#include "spice-client.h"
+#include "spice-common.h"
+
+#include "spice-channel-priv.h"
+#include "glib-compat.h"
+
+/**
+ * SECTION:channel-usbredir
+ * @short_description: usb redirection
+ * @title: USB Redirection Channel
+ * @section_id:
+ * @stability: API Stable (channel in development)
+ * @include: channel-usbredir.h
+ *
+ * The Spice protocol defines a set of messages to redirect USB devices
+ * from the Spice client to the VM. This channel handles these messages.
+ */
+
+#ifdef USE_USBREDIR
+
+#define SPICE_USBREDIR_CHANNEL_GET_PRIVATE(obj)                                  \
+    (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_USBREDIR_CHANNEL, SpiceUsbredirChannelPrivate))
+
+enum SpiceUsbredirChannelState {
+    STATE_DISCONNECTED,
+#if USE_POLKIT
+    STATE_WAITING_FOR_ACL_HELPER,
+#endif
+    STATE_CONNECTED,
+    STATE_DISCONNECTING,
+};
+
+struct _SpiceUsbredirChannelPrivate {
+    libusb_device *device;
+    SpiceUsbDevice *spice_device;
+    libusb_context *context;
+    struct usbredirhost *host;
+    /* To catch usbredirhost error messages and report them as a GError */
+    GError **catch_error;
+    /* Data passed from channel handle msg to the usbredirhost read cb */
+    const uint8_t *read_buf;
+    int read_buf_size;
+    enum SpiceUsbredirChannelState state;
+#if USE_POLKIT
+    GSimpleAsyncResult *result;
+    SpiceUsbAclHelper *acl_helper;
+#endif
+};
+
+static void channel_set_handlers(SpiceChannelClass *klass);
+static void spice_usbredir_channel_up(SpiceChannel *channel);
+static void spice_usbredir_channel_dispose(GObject *obj);
+static void spice_usbredir_channel_finalize(GObject *obj);
+static void usbredir_handle_msg(SpiceChannel *channel, SpiceMsgIn *in);
+
+static void usbredir_log(void *user_data, int level, const char *msg);
+static int usbredir_read_callback(void *user_data, uint8_t *data, int count);
+static int usbredir_write_callback(void *user_data, uint8_t *data, int count);
+static void usbredir_write_flush_callback(void *user_data);
+
+static void *usbredir_alloc_lock(void);
+static void usbredir_lock_lock(void *user_data);
+static void usbredir_unlock_lock(void *user_data);
+static void usbredir_free_lock(void *user_data);
+
+#endif
+
+G_DEFINE_TYPE(SpiceUsbredirChannel, spice_usbredir_channel, SPICE_TYPE_CHANNEL)
+
+/* ------------------------------------------------------------------ */
+
+static void spice_usbredir_channel_init(SpiceUsbredirChannel *channel)
+{
+#ifdef USE_USBREDIR
+    channel->priv = SPICE_USBREDIR_CHANNEL_GET_PRIVATE(channel);
+#endif
+}
+
+#ifdef USE_USBREDIR
+static void spice_usbredir_channel_reset(SpiceChannel *c, gboolean migrating)
+{
+    SpiceUsbredirChannel *channel = SPICE_USBREDIR_CHANNEL(c);
+    SpiceUsbredirChannelPrivate *priv = channel->priv;
+
+    if (priv->host) {
+        if (priv->state == STATE_CONNECTED)
+            spice_usbredir_channel_disconnect_device(channel);
+        usbredirhost_close(priv->host);
+        priv->host = NULL;
+        /* Call set_context to re-create the host */
+        spice_usbredir_channel_set_context(channel, priv->context);
+    }
+    SPICE_CHANNEL_CLASS(spice_usbredir_channel_parent_class)->channel_reset(c, migrating);
+}
+#endif
+
+static void spice_usbredir_channel_class_init(SpiceUsbredirChannelClass *klass)
+{
+#ifdef USE_USBREDIR
+    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+    SpiceChannelClass *channel_class = SPICE_CHANNEL_CLASS(klass);
+
+    gobject_class->dispose       = spice_usbredir_channel_dispose;
+    gobject_class->finalize      = spice_usbredir_channel_finalize;
+    channel_class->channel_up    = spice_usbredir_channel_up;
+    channel_class->channel_reset = spice_usbredir_channel_reset;
+
+    g_type_class_add_private(klass, sizeof(SpiceUsbredirChannelPrivate));
+    channel_set_handlers(SPICE_CHANNEL_CLASS(klass));
+#endif
+}
+
+#ifdef USE_USBREDIR
+static void spice_usbredir_channel_dispose(GObject *obj)
+{
+    SpiceUsbredirChannel *channel = SPICE_USBREDIR_CHANNEL(obj);
+
+    spice_usbredir_channel_disconnect_device(channel);
+
+    /* Chain up to the parent class */
+    if (G_OBJECT_CLASS(spice_usbredir_channel_parent_class)->dispose)
+        G_OBJECT_CLASS(spice_usbredir_channel_parent_class)->dispose(obj);
+}
+
+/*
+ * Note we don't unref our device / acl_helper / result references in our
+ * finalize. The reason for this is that depending on our state at dispose
+ * time they are either:
+ * 1) Already unreferenced
+ * 2) Will be unreferenced by the disconnect_device call from dispose
+ * 3) Will be unreferenced by spice_usbredir_channel_open_acl_cb
+ *
+ * Now the last one may seem like an issue, since what will happen if
+ * spice_usbredir_channel_open_acl_cb will run after finalization?
+ *
+ * This will never happens since the GSimpleAsyncResult created before we
+ * get into the STATE_WAITING_FOR_ACL_HELPER takes a reference to its
+ * source object, which is our SpiceUsbredirChannel object, so
+ * the finalize won't hapen until spice_usbredir_channel_open_acl_cb runs,
+ * and unrefs priv->result which will in turn unref ourselve once the
+ * complete_in_idle call it does has completed. And once
+ * spice_usbredir_channel_open_acl_cb has run, all references we hold have
+ * been released even in the 3th scenario.
+ */
+static void spice_usbredir_channel_finalize(GObject *obj)
+{
+    SpiceUsbredirChannel *channel = SPICE_USBREDIR_CHANNEL(obj);
+
+    if (channel->priv->host)
+        usbredirhost_close(channel->priv->host);
+
+    /* Chain up to the parent class */
+    if (G_OBJECT_CLASS(spice_usbredir_channel_parent_class)->finalize)
+        G_OBJECT_CLASS(spice_usbredir_channel_parent_class)->finalize(obj);
+}
+
+static void channel_set_handlers(SpiceChannelClass *klass)
+{
+    static const spice_msg_handler handlers[] = {
+        [ SPICE_MSG_SPICEVMC_DATA ] = usbredir_handle_msg,
+    };
+
+    spice_channel_set_handlers(klass, handlers, G_N_ELEMENTS(handlers));
+}
+
+/* ------------------------------------------------------------------ */
+/* private api                                                        */
+
+G_GNUC_INTERNAL
+void spice_usbredir_channel_set_context(SpiceUsbredirChannel *channel,
+                                        libusb_context       *context)
+{
+    SpiceUsbredirChannelPrivate *priv = channel->priv;
+
+    g_return_if_fail(priv->host == NULL);
+
+    priv->context = context;
+    priv->host = usbredirhost_open_full(
+                                   context, NULL,
+                                   usbredir_log,
+                                   usbredir_read_callback,
+                                   usbredir_write_callback,
+                                   usbredir_write_flush_callback,
+                                   usbredir_alloc_lock,
+                                   usbredir_lock_lock,
+                                   usbredir_unlock_lock,
+                                   usbredir_free_lock,
+                                   channel, PACKAGE_STRING,
+                                   spice_util_get_debug() ? usbredirparser_debug : usbredirparser_warning,
+                                   usbredirhost_fl_write_cb_owns_buffer);
+    if (!priv->host)
+        g_error("Out of memory allocating usbredirhost");
+}
+
+static gboolean spice_usbredir_channel_open_device(
+    SpiceUsbredirChannel *channel, GError **err)
+{
+    SpiceUsbredirChannelPrivate *priv = channel->priv;
+    libusb_device_handle *handle = NULL;
+    int rc, status;
+
+    g_return_val_if_fail(priv->state == STATE_DISCONNECTED
+#if USE_POLKIT
+                         || priv->state == STATE_WAITING_FOR_ACL_HELPER
+#endif
+                         , FALSE);
+
+    rc = libusb_open(priv->device, &handle);
+    if (rc != 0) {
+        g_set_error(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                    "Could not open usb device: %s [%i]",
+                    spice_usbutil_libusb_strerror(rc), rc);
+        return FALSE;
+    }
+
+    priv->catch_error = err;
+    status = usbredirhost_set_device(priv->host, handle);
+    priv->catch_error = NULL;
+    if (status != usb_redir_success) {
+        g_return_val_if_fail(err == NULL || *err != NULL, FALSE);
+        return FALSE;
+    }
+
+    if (!spice_usb_device_manager_start_event_listening(
+            spice_usb_device_manager_get(
+                spice_channel_get_session(SPICE_CHANNEL(channel)), NULL),
+            err)) {
+        usbredirhost_set_device(priv->host, NULL);
+        return FALSE;
+    }
+
+    priv->state = STATE_CONNECTED;
+
+    return TRUE;
+}
+
+#if USE_POLKIT
+static void spice_usbredir_channel_open_acl_cb(
+    GObject *gobject, GAsyncResult *acl_res, gpointer user_data)
+{
+    SpiceUsbAclHelper *acl_helper = SPICE_USB_ACL_HELPER(gobject);
+    SpiceUsbredirChannel *channel = SPICE_USBREDIR_CHANNEL(user_data);
+    SpiceUsbredirChannelPrivate *priv = channel->priv;
+    GError *err = NULL;
+
+    g_return_if_fail(acl_helper == priv->acl_helper);
+    g_return_if_fail(priv->state == STATE_WAITING_FOR_ACL_HELPER ||
+                     priv->state == STATE_DISCONNECTING);
+
+    spice_usb_acl_helper_open_acl_finish(acl_helper, acl_res, &err);
+    if (!err && priv->state == STATE_DISCONNECTING) {
+        err = g_error_new_literal(G_IO_ERROR, G_IO_ERROR_CANCELLED,
+                                  "USB redirection channel connect cancelled");
+    }
+    if (!err) {
+        spice_usbredir_channel_open_device(channel, &err);
+    }
+    if (err) {
+        g_simple_async_result_take_error(priv->result, err);
+        libusb_unref_device(priv->device);
+        priv->device = NULL;
+        g_boxed_free(spice_usb_device_get_type(), priv->spice_device);
+        priv->spice_device = NULL;
+        priv->state  = STATE_DISCONNECTED;
+    }
+
+    spice_usb_acl_helper_close_acl(priv->acl_helper);
+    g_clear_object(&priv->acl_helper);
+    g_object_set(spice_channel_get_session(SPICE_CHANNEL(channel)),
+                 "inhibit-keyboard-grab", FALSE, NULL);
+
+    g_simple_async_result_complete_in_idle(priv->result);
+    g_clear_object(&priv->result);
+}
+#endif
+
+G_GNUC_INTERNAL
+void spice_usbredir_channel_connect_device_async(
+                                          SpiceUsbredirChannel *channel,
+                                          libusb_device        *device,
+                                          SpiceUsbDevice       *spice_device,
+                                          GCancellable         *cancellable,
+                                          GAsyncReadyCallback   callback,
+                                          gpointer              user_data)
+{
+    SpiceUsbredirChannelPrivate *priv = channel->priv;
+    GSimpleAsyncResult *result;
+#if ! USE_POLKIT
+    GError *err = NULL;
+#endif
+
+    g_return_if_fail(SPICE_IS_USBREDIR_CHANNEL(channel));
+    g_return_if_fail(device != NULL);
+
+    CHANNEL_DEBUG(channel, "connecting usb channel %p", channel);
+
+    result = g_simple_async_result_new(G_OBJECT(channel), callback, user_data,
+                                 spice_usbredir_channel_connect_device_async);
+
+    if (!priv->host) {
+        g_simple_async_result_set_error(result,
+                            SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                            "Error libusb context not set");
+        goto done;
+    }
+
+    if (priv->state != STATE_DISCONNECTED) {
+        g_simple_async_result_set_error(result,
+                            SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                            "Error channel is busy");
+        goto done;
+    }
+
+    priv->device = libusb_ref_device(device);
+    priv->spice_device = g_boxed_copy(spice_usb_device_get_type(),
+                                      spice_device);
+#if USE_POLKIT
+    priv->result = result;
+    priv->state  = STATE_WAITING_FOR_ACL_HELPER;
+    priv->acl_helper = spice_usb_acl_helper_new();
+    g_object_set(spice_channel_get_session(SPICE_CHANNEL(channel)),
+                 "inhibit-keyboard-grab", TRUE, NULL);
+    spice_usb_acl_helper_open_acl(priv->acl_helper,
+                                  libusb_get_bus_number(device),
+                                  libusb_get_device_address(device),
+                                  cancellable,
+                                  spice_usbredir_channel_open_acl_cb,
+                                  channel);
+    return;
+#else
+    if (!spice_usbredir_channel_open_device(channel, &err)) {
+        g_simple_async_result_take_error(result, err);
+        libusb_unref_device(priv->device);
+        priv->device = NULL;
+        g_boxed_free(spice_usb_device_get_type(), priv->spice_device);
+        priv->spice_device = NULL;
+    }
+#endif
+
+done:
+    g_simple_async_result_complete_in_idle(result);
+    g_object_unref(result);
+}
+
+G_GNUC_INTERNAL
+gboolean spice_usbredir_channel_connect_device_finish(
+                                               SpiceUsbredirChannel *channel,
+                                               GAsyncResult         *res,
+                                               GError              **err)
+{
+    GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT(res);
+
+    g_return_val_if_fail(g_simple_async_result_is_valid(res, G_OBJECT(channel),
+                                 spice_usbredir_channel_connect_device_async),
+                         FALSE);
+
+    if (g_simple_async_result_propagate_error(result, err))
+        return FALSE;
+
+    return TRUE;
+}
+
+G_GNUC_INTERNAL
+void spice_usbredir_channel_disconnect_device(SpiceUsbredirChannel *channel)
+{
+    SpiceUsbredirChannelPrivate *priv = channel->priv;
+
+    CHANNEL_DEBUG(channel, "disconnecting device from usb channel %p", channel);
+
+    switch (priv->state) {
+    case STATE_DISCONNECTED:
+    case STATE_DISCONNECTING:
+        break;
+#if USE_POLKIT
+    case STATE_WAITING_FOR_ACL_HELPER:
+        priv->state = STATE_DISCONNECTING;
+        /* We're still waiting for the acl helper -> cancel it */
+        spice_usb_acl_helper_close_acl(priv->acl_helper);
+        break;
+#endif
+    case STATE_CONNECTED:
+        /*
+         * This sets the usb event thread run condition to FALSE, therefor
+         * it must be done before usbredirhost_set_device NULL, as
+         * usbredirhost_set_device NULL will interrupt the
+         * libusb_handle_events call in the thread.
+         */
+        {
+            SpiceSession *session = spice_channel_get_session(SPICE_CHANNEL(channel));
+            if (session != NULL)
+                spice_usb_device_manager_stop_event_listening(
+                    spice_usb_device_manager_get(session, NULL));
+        }
+        /* This also closes the libusb handle we passed from open_device */
+        usbredirhost_set_device(priv->host, NULL);
+        libusb_unref_device(priv->device);
+        priv->device = NULL;
+        g_boxed_free(spice_usb_device_get_type(), priv->spice_device);
+        priv->spice_device = NULL;
+        priv->state  = STATE_DISCONNECTED;
+        break;
+    }
+}
+
+G_GNUC_INTERNAL
+libusb_device *spice_usbredir_channel_get_device(SpiceUsbredirChannel *channel)
+{
+    return channel->priv->device;
+}
+
+G_GNUC_INTERNAL
+void spice_usbredir_channel_get_guest_filter(
+                          SpiceUsbredirChannel               *channel,
+                          const struct usbredirfilter_rule  **rules_ret,
+                          int                                *rules_count_ret)
+{
+    SpiceUsbredirChannelPrivate *priv = channel->priv;
+
+    g_return_if_fail(priv->host != NULL);
+
+    usbredirhost_get_guest_filter(priv->host, rules_ret, rules_count_ret);
+}
+
+/* ------------------------------------------------------------------ */
+/* callbacks (any context)                                            */
+
+/* Note that this function must be re-entrant safe, as it can get called
+   from both the main thread as well as from the usb event handling thread */
+static void usbredir_write_flush_callback(void *user_data)
+{
+    SpiceUsbredirChannel *channel = SPICE_USBREDIR_CHANNEL(user_data);
+    SpiceUsbredirChannelPrivate *priv = channel->priv;
+
+    if (spice_channel_get_state(SPICE_CHANNEL(channel)) !=
+            SPICE_CHANNEL_STATE_READY)
+        return;
+
+    if (!priv->host)
+        return;
+
+    usbredirhost_write_guest_data(priv->host);
+}
+
+static void usbredir_log(void *user_data, int level, const char *msg)
+{
+    SpiceUsbredirChannel *channel = user_data;
+    SpiceUsbredirChannelPrivate *priv = channel->priv;
+
+    if (priv->catch_error && level == usbredirparser_error) {
+        CHANNEL_DEBUG(channel, "%s", msg);
+        /* Remove "usbredirhost: " prefix from usbredirhost messages */
+        if (strncmp(msg, "usbredirhost: ", 14) == 0)
+            g_set_error_literal(priv->catch_error, SPICE_CLIENT_ERROR,
+                                SPICE_CLIENT_ERROR_FAILED, msg + 14);
+        else
+            g_set_error_literal(priv->catch_error, SPICE_CLIENT_ERROR,
+                                SPICE_CLIENT_ERROR_FAILED, msg);
+        return;
+    }
+
+    switch (level) {
+        case usbredirparser_error:
+            g_critical("%s", msg); break;
+        case usbredirparser_warning:
+            g_warning("%s", msg); break;
+        default:
+            CHANNEL_DEBUG(channel, "%s", msg); break;
+    }
+}
+
+static int usbredir_read_callback(void *user_data, uint8_t *data, int count)
+{
+    SpiceUsbredirChannel *channel = user_data;
+    SpiceUsbredirChannelPrivate *priv = channel->priv;
+
+    if (priv->read_buf_size < count) {
+        count = priv->read_buf_size;
+    }
+
+    memcpy(data, priv->read_buf, count);
+
+    priv->read_buf_size -= count;
+    if (priv->read_buf_size) {
+        priv->read_buf += count;
+    } else {
+        priv->read_buf = NULL;
+    }
+
+    return count;
+}
+
+static void usbredir_free_write_cb_data(uint8_t *data, void *user_data)
+{
+    SpiceUsbredirChannel *channel = user_data;
+    SpiceUsbredirChannelPrivate *priv = channel->priv;
+
+    usbredirhost_free_write_buffer(priv->host, data);
+}
+
+static int usbredir_write_callback(void *user_data, uint8_t *data, int count)
+{
+    SpiceUsbredirChannel *channel = user_data;
+    SpiceMsgOut *msg_out;
+
+    msg_out = spice_msg_out_new(SPICE_CHANNEL(channel),
+                                SPICE_MSGC_SPICEVMC_DATA);
+    spice_marshaller_add_ref_full(msg_out->marshaller, data, count,
+                                  usbredir_free_write_cb_data, channel);
+    spice_msg_out_send(msg_out);
+
+    return count;
+}
+
+static void *usbredir_alloc_lock(void) {
+#if GLIB_CHECK_VERSION(2,32,0)
+    GMutex *mutex;
+
+    mutex = g_new0(GMutex, 1);
+    g_mutex_init(mutex);
+
+    return mutex;
+#else
+    return g_mutex_new();
+#endif
+}
+
+static void usbredir_lock_lock(void *user_data) {
+    GMutex *mutex = user_data;
+
+    g_mutex_lock(mutex);
+}
+
+static void usbredir_unlock_lock(void *user_data) {
+    GMutex *mutex = user_data;
+
+    g_mutex_unlock(mutex);
+}
+
+static void usbredir_free_lock(void *user_data) {
+    GMutex *mutex = user_data;
+
+#if GLIB_CHECK_VERSION(2,32,0)
+    g_mutex_clear(mutex);
+    g_free(mutex);
+#else
+    g_mutex_free(mutex);
+#endif
+}
+
+/* --------------------------------------------------------------------- */
+
+typedef struct device_error_data {
+    SpiceUsbredirChannel *channel;
+    SpiceUsbDevice *spice_device;
+    GError *error;
+    struct coroutine *caller;
+} device_error_data;
+
+/* main context */
+static gboolean device_error(gpointer user_data)
+{
+    device_error_data *data = user_data;
+    SpiceUsbredirChannel *channel = data->channel;
+    SpiceUsbredirChannelPrivate *priv = channel->priv;
+
+    /* Check that the device has not changed before we manage to run */
+    if (data->spice_device == priv->spice_device) {
+        spice_usbredir_channel_disconnect_device(channel);
+        spice_usb_device_manager_device_error(
+                spice_usb_device_manager_get(
+                    spice_channel_get_session(SPICE_CHANNEL(channel)), NULL),
+                data->spice_device, data->error);
+    }
+
+    coroutine_yieldto(data->caller, NULL);
+    return FALSE;
+}
+
+/* --------------------------------------------------------------------- */
+/* coroutine context                                                     */
+static void spice_usbredir_channel_up(SpiceChannel *c)
+{
+    SpiceUsbredirChannel *channel = SPICE_USBREDIR_CHANNEL(c);
+    SpiceUsbredirChannelPrivate *priv = channel->priv;
+
+    /* Flush any pending writes */
+    usbredirhost_write_guest_data(priv->host);
+}
+
+static void usbredir_handle_msg(SpiceChannel *c, SpiceMsgIn *in)
+{
+    SpiceUsbredirChannel *channel = SPICE_USBREDIR_CHANNEL(c);
+    SpiceUsbredirChannelPrivate *priv = channel->priv;
+    device_error_data data;
+    int r, size;
+    uint8_t *buf;
+
+    g_return_if_fail(priv->host != NULL);
+
+    /* No recursion allowed! */
+    g_return_if_fail(priv->read_buf == NULL);
+
+    buf = spice_msg_in_raw(in, &size);
+    priv->read_buf = buf;
+    priv->read_buf_size = size;
+
+    r = usbredirhost_read_guest_data(priv->host);
+    if (r != 0) {
+        SpiceUsbDevice *spice_device = priv->spice_device;
+        gchar *desc;
+        GError *err;
+
+        g_return_if_fail(spice_device != NULL);
+
+        desc = spice_usb_device_get_description(spice_device, NULL);
+        switch (r) {
+        case usbredirhost_read_parse_error:
+            err = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                              _("usbredir protocol parse error for %s"), desc);
+            break;
+        case usbredirhost_read_device_rejected:
+            err = g_error_new(SPICE_CLIENT_ERROR,
+                              SPICE_CLIENT_USB_DEVICE_REJECTED,
+                              _("%s rejected by host"), desc);
+            break;
+        case usbredirhost_read_device_lost:
+            err = g_error_new(SPICE_CLIENT_ERROR,
+                              SPICE_CLIENT_USB_DEVICE_LOST,
+                              _("%s disconnected (fatal IO error)"), desc);
+            break;
+        default:
+            err = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                              _("Unknown error (%d) for %s"), r, desc);
+        }
+        g_free(desc);
+
+        CHANNEL_DEBUG(c, "%s", err->message);
+
+        data.channel = channel;
+        data.caller = coroutine_self();
+        data.spice_device = g_boxed_copy(spice_usb_device_get_type(), spice_device);
+        data.error = err;
+        g_idle_add(device_error, &data);
+        coroutine_yield(NULL);
+
+        g_boxed_free(spice_usb_device_get_type(), data.spice_device);
+
+        g_error_free(err);
+    }
+}
+
+#endif /* USE_USBREDIR */
diff --git a/src/channel-usbredir.h b/src/channel-usbredir.h
new file mode 100644
index 0000000..0cc4fbf
--- /dev/null
+++ b/src/channel-usbredir.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2011 Red Hat, Inc.
+
+   Red Hat Authors:
+   Hans de Goede <hdegoede at redhat.com>
+
+   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_CLIENT_USBREDIR_CHANNEL_H__
+#define __SPICE_CLIENT_USBREDIR_CHANNEL_H__
+
+#include "spice-client.h"
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_USBREDIR_CHANNEL            (spice_usbredir_channel_get_type())
+#define SPICE_USBREDIR_CHANNEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), SPICE_TYPE_USBREDIR_CHANNEL, SpiceUsbredirChannel))
+#define SPICE_USBREDIR_CHANNEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), SPICE_TYPE_USBREDIR_CHANNEL, SpiceUsbredirChannelClass))
+#define SPICE_IS_USBREDIR_CHANNEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), SPICE_TYPE_USBREDIR_CHANNEL))
+#define SPICE_IS_USBREDIR_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SPICE_TYPE_USBREDIR_CHANNEL))
+#define SPICE_USBREDIR_CHANNEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), SPICE_TYPE_USBREDIR_CHANNEL, SpiceUsbredirChannelClass))
+
+typedef struct _SpiceUsbredirChannel SpiceUsbredirChannel;
+typedef struct _SpiceUsbredirChannelClass SpiceUsbredirChannelClass;
+typedef struct _SpiceUsbredirChannelPrivate SpiceUsbredirChannelPrivate;
+
+/**
+ * SpiceUsbredirChannel:
+ *
+ * The #SpiceUsbredirChannel struct is opaque and should not be accessed directly.
+ */
+struct _SpiceUsbredirChannel {
+    SpiceChannel parent;
+
+    /*< private >*/
+    SpiceUsbredirChannelPrivate *priv;
+    /* Do not add fields to this struct */
+};
+
+/**
+ * SpiceUsbredirChannelClass:
+ * @parent_class: Parent class.
+ *
+ * Class structure for #SpiceUsbredirChannel.
+ */
+struct _SpiceUsbredirChannelClass {
+    SpiceChannelClass parent_class;
+
+    /* signals */
+
+    /*< private >*/
+    /* Do not add fields to this struct */
+};
+
+GType spice_usbredir_channel_get_type(void);
+
+G_END_DECLS
+
+#endif /* __SPICE_CLIENT_USBREDIR_CHANNEL_H__ */
diff --git a/src/channel-webdav.c b/src/channel-webdav.c
new file mode 100644
index 0000000..bde728e
--- /dev/null
+++ b/src/channel-webdav.c
@@ -0,0 +1,613 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2013 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-channel-priv.h"
+#include "spice-session-priv.h"
+#include "spice-marshal.h"
+#include "glib-compat.h"
+#include "vmcstream.h"
+#include "giopipe.h"
+
+/**
+ * SECTION:channel-webdav
+ * @short_description: exports a directory
+ * @title: WebDAV Channel
+ * @section_id:
+ * @see_also: #SpiceChannel
+ * @stability: Stable
+ * @include: channel-webdav.h
+ *
+ * The "webdav" channel exports a directory to the guest for file
+ * manipulation (read/write/copy etc). The underlying protocol is
+ * implemented using WebDAV (RFC 4918).
+ *
+ * By default, the shared directory is the one associated with GLib
+ * %G_USER_DIRECTORY_PUBLIC_SHARE. You can specify a different
+ * directory with #SpiceSession #SpiceSession:shared-dir property.
+ *
+ * Since: 0.24
+ */
+
+#define SPICE_WEBDAV_CHANNEL_GET_PRIVATE(obj)                                  \
+    (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_WEBDAV_CHANNEL, SpiceWebdavChannelPrivate))
+
+typedef struct _OutputQueue OutputQueue;
+
+struct _SpiceWebdavChannelPrivate {
+    SpiceVmcStream *stream;
+    GCancellable *cancellable;
+    GHashTable *clients;
+    OutputQueue *queue;
+
+    gboolean demuxing;
+    struct _demux {
+        gint64 client;
+        guint16 size;
+        guint8 *buf;
+    } demux;
+};
+
+G_DEFINE_TYPE(SpiceWebdavChannel, spice_webdav_channel, SPICE_TYPE_PORT_CHANNEL)
+
+static void spice_webdav_handle_msg(SpiceChannel *channel, SpiceMsgIn *msg);
+
+struct _OutputQueue {
+    GOutputStream *output;
+    gboolean flushing;
+    guint idle_id;
+    GQueue *queue;
+};
+
+typedef struct _OutputQueueElem {
+    OutputQueue *queue;
+    const guint8 *buf;
+    gsize size;
+    GFunc pushed_cb;
+    gpointer user_data;
+} OutputQueueElem;
+
+static OutputQueue* output_queue_new(GOutputStream *output)
+{
+    OutputQueue *queue = g_new0(OutputQueue, 1);
+
+    queue->output = g_object_ref(output);
+    queue->queue = g_queue_new();
+
+    return queue;
+}
+
+static void output_queue_free(OutputQueue *queue)
+{
+    g_warn_if_fail(g_queue_get_length(queue->queue) == 0);
+    g_warn_if_fail(!queue->flushing);
+
+    g_queue_free_full(queue->queue, g_free);
+    g_clear_object(&queue->output);
+    if (queue->idle_id)
+        g_source_remove(queue->idle_id);
+    g_free(queue);
+}
+
+static gboolean output_queue_idle(gpointer user_data);
+
+static void output_queue_flush_cb(GObject *source_object,
+                                  GAsyncResult *res,
+                                  gpointer user_data)
+{
+    GError *error = NULL;
+    OutputQueueElem *e = user_data;
+    OutputQueue *q = e->queue;
+
+    q->flushing = FALSE;
+    g_output_stream_flush_finish(G_OUTPUT_STREAM(source_object),
+                                 res, &error);
+    if (error)
+        g_warning("error: %s", error->message);
+
+    g_clear_error(&error);
+
+    if (!q->idle_id)
+        q->idle_id = g_idle_add(output_queue_idle, q);
+
+    g_free(e);
+}
+
+static gboolean output_queue_idle(gpointer user_data)
+{
+    OutputQueue *q = user_data;
+    OutputQueueElem *e;
+    GError *error = NULL;
+
+    if (q->flushing) {
+        q->idle_id = 0;
+        return FALSE;
+    }
+
+    e = g_queue_pop_head(q->queue);
+    if (!e) {
+        q->idle_id = 0;
+        return FALSE;
+    }
+
+    if (!g_output_stream_write_all(q->output, e->buf, e->size, NULL, NULL, &error))
+        goto err;
+    else if (e->pushed_cb)
+        e->pushed_cb(q, e->user_data);
+
+    q->flushing = TRUE;
+    g_output_stream_flush_async(q->output, G_PRIORITY_DEFAULT, NULL, output_queue_flush_cb, e);
+
+    return TRUE;
+
+err:
+    g_warning("failed to write to output stream");
+    if (error)
+        g_warning("error: %s", error->message);
+    g_clear_error(&error);
+
+    q->idle_id = 0;
+    return FALSE;
+}
+
+static void output_queue_push(OutputQueue *q, const guint8 *buf, gsize size,
+                              GFunc pushed_cb, gpointer user_data)
+{
+    OutputQueueElem *e = g_new(OutputQueueElem, 1);
+
+    e->buf = buf;
+    e->size = size;
+    e->pushed_cb = pushed_cb;
+    e->user_data = user_data;
+    e->queue = q;
+    g_queue_push_tail(q->queue, e);
+
+    if (!q->idle_id && !q->flushing)
+        q->idle_id = g_idle_add(output_queue_idle, q);
+}
+
+typedef struct Client
+{
+    guint refs;
+    SpiceWebdavChannel *self;
+    GIOStream *pipe;
+    gint64 id;
+    GCancellable *cancellable;
+
+    struct _mux {
+        gint64 id;
+        guint16 size;
+        guint8 *buf;
+    } mux;
+} Client;
+
+static void
+client_unref(Client *client)
+{
+    if (--client->refs > 0)
+        return;
+
+    g_free(client->mux.buf);
+
+    g_object_unref(client->pipe);
+    g_object_unref(client->cancellable);
+
+    g_free(client);
+}
+
+static Client *
+client_ref(Client *client)
+{
+    client->refs++;
+    return client;
+}
+
+static void client_start_read(SpiceWebdavChannel *self, Client *client);
+
+static void remove_client(SpiceWebdavChannel *self, Client *client)
+{
+    SpiceWebdavChannelPrivate *c;
+
+    if (g_cancellable_is_cancelled(client->cancellable))
+        return;
+
+    g_cancellable_cancel(client->cancellable);
+
+    c = self->priv;
+    g_hash_table_remove(c->clients, &client->id);
+}
+
+static void mux_pushed_cb(OutputQueue *q, gpointer user_data)
+{
+    Client *client = user_data;
+
+    if (client->mux.size == 0) {
+        remove_client(client->self, client);
+    } else {
+        client_start_read(client->self, client);
+    }
+
+    client_unref(client);
+}
+
+#define MAX_MUX_SIZE G_MAXUINT16
+
+static void server_reply_cb(GObject *source_object,
+                            GAsyncResult *res,
+                            gpointer user_data)
+{
+    Client *client = user_data;
+    SpiceWebdavChannel *self = client->self;
+    SpiceWebdavChannelPrivate *c = self->priv;
+    GError *err = NULL;
+    gssize size;
+
+    size = g_input_stream_read_finish(G_INPUT_STREAM(source_object), res, &err);
+    if (err || g_cancellable_is_cancelled(client->cancellable))
+        goto end;
+
+    g_return_if_fail(size <= MAX_MUX_SIZE);
+    g_return_if_fail(size >= 0);
+    client->mux.size = size;
+
+    output_queue_push(c->queue, (guint8 *)&client->mux.id, sizeof(gint64), NULL, NULL);
+    client->mux.size = GUINT16_TO_LE(client->mux.size);
+    output_queue_push(c->queue, (guint8 *)&client->mux.size, sizeof(guint16), NULL, NULL);
+    output_queue_push(c->queue, (guint8 *)client->mux.buf, size, (GFunc)mux_pushed_cb, client);
+
+    return;
+
+end:
+    if (err) {
+        if (!g_cancellable_is_cancelled(client->cancellable))
+            g_warning("read error: %s", err->message);
+        remove_client(self, client);
+        g_clear_error(&err);
+    }
+
+    client_unref(client);
+}
+
+static void client_start_read(SpiceWebdavChannel *self, Client *client)
+{
+    GInputStream *input;
+
+    input = g_io_stream_get_input_stream(G_IO_STREAM(client->pipe));
+    g_input_stream_read_async(input, client->mux.buf, MAX_MUX_SIZE,
+                              G_PRIORITY_DEFAULT, client->cancellable, server_reply_cb,
+                              client_ref(client));
+}
+
+static void start_demux(SpiceWebdavChannel *self);
+
+static void demux_to_client_finish(SpiceWebdavChannel *self,
+                                   Client *client, gboolean fail)
+{
+    SpiceWebdavChannelPrivate *c = self->priv;
+
+    if (fail) {
+        remove_client(self, client);
+    }
+
+    c->demuxing = FALSE;
+    start_demux(self);
+}
+
+static void demux_to_client_cb(GObject *source, GAsyncResult *result, gpointer user_data)
+{
+    Client *client = user_data;
+    SpiceWebdavChannelPrivate *c = client->self->priv;
+    GError *error = NULL;
+    gboolean fail;
+    gsize size;
+
+    g_output_stream_write_all_finish(G_OUTPUT_STREAM(source), result, &size, &error);
+
+    if (error) {
+        CHANNEL_DEBUG(client->self, "write failed: %s", error->message);
+        g_clear_error(&error);
+    }
+
+    fail = (size != c->demux.size);
+    g_warn_if_fail(size == c->demux.size);
+    demux_to_client_finish(client->self, client, fail);
+}
+
+static void demux_to_client(SpiceWebdavChannel *self,
+                            Client *client)
+{
+    SpiceWebdavChannelPrivate *c = self->priv;
+    gsize size = c->demux.size;
+
+    CHANNEL_DEBUG(self, "pushing %"G_GSIZE_FORMAT" to client %p", size, client);
+
+    if (size > 0) {
+        g_output_stream_write_all_async(g_io_stream_get_output_stream(client->pipe),
+                                        c->demux.buf, size, G_PRIORITY_DEFAULT,
+                                        c->cancellable, demux_to_client_cb, client);
+        return;
+    } else {
+        /* Nothing to write */
+        demux_to_client_finish(self, client, FALSE);
+    }
+}
+
+static void start_client(SpiceWebdavChannel *self)
+{
+#ifdef USE_PHODAV
+    SpiceWebdavChannelPrivate *c = self->priv;
+    Client *client;
+    GIOStream *peer = NULL;
+    SpiceSession *session;
+    SoupServer *server;
+    GSocketAddress *addr;
+    GError *error = NULL;
+
+    session = spice_channel_get_session(SPICE_CHANNEL(self));
+    server = phodav_server_get_soup_server(spice_session_get_webdav_server(session));
+
+    CHANNEL_DEBUG(self, "starting client %" G_GINT64_FORMAT, c->demux.client);
+
+    client = g_new0(Client, 1);
+    client->refs = 1;
+    client->id = c->demux.client;
+    client->self = self;
+    client->mux.id = GINT64_TO_LE(client->id);
+    client->mux.buf = g_malloc0(MAX_MUX_SIZE);
+    client->cancellable = g_cancellable_new();
+    spice_make_pipe(&client->pipe, &peer);
+
+    addr = g_inet_socket_address_new_from_string ("127.0.0.1", 0);
+    if (!soup_server_accept_iostream(server, peer, addr, addr, &error))
+        goto fail;
+
+    g_hash_table_insert(c->clients, &client->id, client);
+
+    client_start_read(self, client);
+    demux_to_client(self, client);
+
+    g_clear_object(&addr);
+    return;
+
+fail:
+    if (error)
+        CHANNEL_DEBUG(self, "failed to start client: %s", error->message);
+
+    g_clear_object(&addr);
+    g_clear_object(&peer);
+    g_clear_error(&error);
+    client_unref(client);
+#endif
+}
+
+static void data_read_cb(GObject *source_object,
+                         GAsyncResult *res,
+                         gpointer user_data)
+{
+    SpiceWebdavChannel *self = user_data;
+    SpiceWebdavChannelPrivate *c;
+    Client *client;
+    GError *error = NULL;
+    gssize size;
+
+    size = spice_vmc_input_stream_read_all_finish(G_INPUT_STREAM(source_object), res, &error);
+    if (error) {
+        g_warning("error: %s", error->message);
+        g_clear_error(&error);
+        return;
+    }
+
+    c = self->priv;
+    g_return_if_fail(size == c->demux.size);
+
+    client = g_hash_table_lookup(c->clients, &c->demux.client);
+
+    if (client)
+        demux_to_client(self, client);
+    else
+        start_client(self);
+}
+
+
+static void size_read_cb(GObject *source_object,
+                         GAsyncResult *res,
+                         gpointer user_data)
+{
+    SpiceWebdavChannel *self = user_data;
+    SpiceWebdavChannelPrivate *c;
+    GInputStream *istream = G_INPUT_STREAM(source_object);
+    GError *error = NULL;
+    gssize size;
+
+    size = spice_vmc_input_stream_read_all_finish(G_INPUT_STREAM(source_object), res, &error);
+    if (error || size != sizeof(guint16))
+        goto end;
+
+    c = self->priv;
+    c->demux.size = GUINT16_FROM_LE(c->demux.size);
+    spice_vmc_input_stream_read_all_async(istream,
+        c->demux.buf, c->demux.size,
+        G_PRIORITY_DEFAULT, c->cancellable, data_read_cb, self);
+    return;
+
+end:
+    if (error) {
+        g_warning("error: %s", error->message);
+        g_clear_error(&error);
+    }
+}
+
+static void client_read_cb(GObject *source_object,
+                               GAsyncResult *res,
+                               gpointer user_data)
+{
+    SpiceWebdavChannel *self = user_data;
+    SpiceWebdavChannelPrivate *c = self->priv;
+    GInputStream *istream = G_INPUT_STREAM(source_object);
+    GError *error = NULL;
+    gssize size;
+
+    size = spice_vmc_input_stream_read_all_finish(G_INPUT_STREAM(source_object), res, &error);
+    if (error || size != sizeof(gint64))
+        goto end;
+
+    c->demux.client = GINT64_FROM_LE(c->demux.client);
+    spice_vmc_input_stream_read_all_async(istream,
+        &c->demux.size, sizeof(guint16),
+        G_PRIORITY_DEFAULT, c->cancellable, size_read_cb, self);
+    return;
+
+end:
+    if (error) {
+        g_warning("error: %s", error->message);
+        g_clear_error(&error);
+    }
+}
+
+static void start_demux(SpiceWebdavChannel *self)
+{
+    SpiceWebdavChannelPrivate *c = self->priv;
+    GInputStream *istream = g_io_stream_get_input_stream(G_IO_STREAM(c->stream));
+
+    if (c->demuxing)
+        return;
+
+    c->demuxing = TRUE;
+
+    CHANNEL_DEBUG(self, "start demux");
+    spice_vmc_input_stream_read_all_async(istream, &c->demux.client, sizeof(gint64),
+        G_PRIORITY_DEFAULT, c->cancellable, client_read_cb, self);
+
+}
+
+static void port_event(SpiceWebdavChannel *self, gint event)
+{
+    SpiceWebdavChannelPrivate *c = self->priv;
+
+    CHANNEL_DEBUG(self, "port event:%d", event);
+    if (event == SPICE_PORT_EVENT_OPENED) {
+        g_cancellable_reset(c->cancellable);
+        start_demux(self);
+    } else {
+        g_cancellable_cancel(c->cancellable);
+        c->demuxing = FALSE;
+        g_hash_table_remove_all(c->clients);
+    }
+}
+
+static void client_remove_unref(gpointer data)
+{
+    Client *client = data;
+
+    g_cancellable_cancel(client->cancellable);
+    client_unref(client);
+}
+
+static void spice_webdav_channel_init(SpiceWebdavChannel *channel)
+{
+    SpiceWebdavChannelPrivate *c = SPICE_WEBDAV_CHANNEL_GET_PRIVATE(channel);
+
+    channel->priv = c;
+    c->stream = spice_vmc_stream_new(SPICE_CHANNEL(channel));
+    c->cancellable = g_cancellable_new();
+    c->clients = g_hash_table_new_full(g_int64_hash, g_int64_equal,
+                                       NULL, client_remove_unref);
+    c->demux.buf = g_malloc0(MAX_MUX_SIZE);
+
+    GOutputStream *ostream = g_io_stream_get_output_stream(G_IO_STREAM(c->stream));
+    c->queue = output_queue_new(ostream);
+}
+
+static void spice_webdav_channel_finalize(GObject *object)
+{
+    SpiceWebdavChannelPrivate *c = SPICE_WEBDAV_CHANNEL(object)->priv;
+
+    g_free(c->demux.buf);
+
+    G_OBJECT_CLASS(spice_webdav_channel_parent_class)->finalize(object);
+}
+
+static void spice_webdav_channel_dispose(GObject *object)
+{
+    SpiceWebdavChannelPrivate *c = SPICE_WEBDAV_CHANNEL(object)->priv;
+
+    g_cancellable_cancel(c->cancellable);
+    g_clear_object(&c->cancellable);
+    g_clear_pointer(&c->queue, output_queue_free);
+    g_clear_object(&c->stream);
+    g_hash_table_unref(c->clients);
+
+    G_OBJECT_CLASS(spice_webdav_channel_parent_class)->dispose(object);
+}
+
+static void spice_webdav_channel_up(SpiceChannel *channel)
+{
+    CHANNEL_DEBUG(channel, "up");
+}
+
+static void spice_webdav_channel_class_init(SpiceWebdavChannelClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+    SpiceChannelClass *channel_class = SPICE_CHANNEL_CLASS(klass);
+
+    gobject_class->dispose      = spice_webdav_channel_dispose;
+    gobject_class->finalize     = spice_webdav_channel_finalize;
+    channel_class->handle_msg   = spice_webdav_handle_msg;
+    channel_class->channel_up   = spice_webdav_channel_up;
+
+    g_signal_override_class_handler("port-event",
+                                    SPICE_TYPE_WEBDAV_CHANNEL,
+                                    G_CALLBACK(port_event));
+
+    g_type_class_add_private(klass, sizeof(SpiceWebdavChannelPrivate));
+}
+
+/* coroutine context */
+static void webdav_handle_msg(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceWebdavChannel *self = SPICE_WEBDAV_CHANNEL(channel);
+    SpiceWebdavChannelPrivate *c = self->priv;
+    int size;
+    uint8_t *buf;
+
+    buf = spice_msg_in_raw(in, &size);
+    CHANNEL_DEBUG(channel, "len:%d buf:%p", size, buf);
+
+    spice_vmc_input_stream_co_data(
+        SPICE_VMC_INPUT_STREAM(g_io_stream_get_input_stream(G_IO_STREAM(c->stream))),
+        buf, size);
+}
+
+
+/* coroutine context */
+static void spice_webdav_handle_msg(SpiceChannel *channel, SpiceMsgIn *msg)
+{
+    int type = spice_msg_in_type(msg);
+    SpiceChannelClass *parent_class;
+
+    parent_class = SPICE_CHANNEL_CLASS(spice_webdav_channel_parent_class);
+
+    if (type == SPICE_MSG_SPICEVMC_DATA)
+        webdav_handle_msg(channel, msg);
+    else if (parent_class->handle_msg)
+        parent_class->handle_msg(channel, msg);
+    else
+        g_return_if_reached();
+}
diff --git a/src/channel-webdav.h b/src/channel-webdav.h
new file mode 100644
index 0000000..7940706
--- /dev/null
+++ b/src/channel-webdav.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2013 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_WEBDAV_CHANNEL_H__
+#define __SPICE_WEBDAV_CHANNEL_H__
+
+#include <gio/gio.h>
+#include "spice-client.h"
+#include "channel-port.h"
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_WEBDAV_CHANNEL            (spice_webdav_channel_get_type())
+#define SPICE_WEBDAV_CHANNEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), SPICE_TYPE_WEBDAV_CHANNEL, SpiceWebdavChannel))
+#define SPICE_WEBDAV_CHANNEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), SPICE_TYPE_WEBDAV_CHANNEL, SpiceWebdavChannelClass))
+#define SPICE_IS_WEBDAV_CHANNEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), SPICE_TYPE_WEBDAV_CHANNEL))
+#define SPICE_IS_WEBDAV_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SPICE_TYPE_WEBDAV_CHANNEL))
+#define SPICE_WEBDAV_CHANNEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), SPICE_TYPE_WEBDAV_CHANNEL, SpiceWebdavChannelClass))
+
+typedef struct _SpiceWebdavChannel SpiceWebdavChannel;
+typedef struct _SpiceWebdavChannelClass SpiceWebdavChannelClass;
+typedef struct _SpiceWebdavChannelPrivate SpiceWebdavChannelPrivate;
+
+/**
+ * SpiceWebdavChannel:
+ *
+ * The #SpiceWebdavChannel struct is opaque and should not be accessed directly.
+ */
+struct _SpiceWebdavChannel {
+    SpicePortChannel parent;
+
+    /*< private >*/
+    SpiceWebdavChannelPrivate *priv;
+    /* Do not add fields to this struct */
+};
+
+/**
+ * SpiceWebdavChannelClass:
+ * @parent_class: Parent class.
+ *
+ * Class structure for #SpiceWebdavChannel.
+ */
+struct _SpiceWebdavChannelClass {
+    SpicePortChannelClass parent_class;
+
+    /*< private >*/
+    /* Do not add fields to this struct */
+};
+
+GType spice_webdav_channel_get_type(void);
+
+G_END_DECLS
+
+#endif /* __SPICE_WEBDAV_CHANNEL_H__ */
diff --git a/src/client_sw_canvas.c b/src/client_sw_canvas.c
new file mode 100644
index 0000000..a69abe0
--- /dev/null
+++ b/src/client_sw_canvas.c
@@ -0,0 +1,20 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2014 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/>.
+*/
+#define SW_CANVAS_CACHE
+
+#include "common/sw_canvas.c"
diff --git a/src/client_sw_canvas.h b/src/client_sw_canvas.h
new file mode 100644
index 0000000..1180c5b
--- /dev/null
+++ b/src/client_sw_canvas.h
@@ -0,0 +1,25 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2014 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_CLIENT_SW_CANVAS_H__
+#define __SPICE_CLIENT_SW_CANVAS_H__
+
+#define SW_CANVAS_CACHE
+
+#include <common/sw_canvas.h>
+
+#endif /* __SPICE_CLIENT_SW_CANVAS_H__ */
diff --git a/src/continuation.c b/src/continuation.c
new file mode 100644
index 0000000..adce858
--- /dev/null
+++ b/src/continuation.c
@@ -0,0 +1,102 @@
+/*
+ * GTK VNC Widget
+ *
+ * Copyright (C) 2006  Anthony Liguori <anthony at codemonkey.ws>
+ *
+ * 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.0 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+#include "config.h"
+
+/* keep this above system headers, but below config.h */
+#ifdef _FORTIFY_SOURCE
+#undef _FORTIFY_SOURCE
+#endif
+
+#include <errno.h>
+#include <glib.h>
+
+#include "continuation.h"
+
+/*
+ * va_args to makecontext() must be type 'int', so passing
+ * the pointer we need may require several int args. This
+ * union is a quick hack to let us do that
+ */
+union cc_arg {
+	void *p;
+	int i[2];
+};
+
+static void continuation_trampoline(int i0, int i1)
+{
+	union cc_arg arg;
+	struct continuation *cc;
+	arg.i[0] = i0;
+	arg.i[1] = i1;
+	cc = arg.p;
+
+	if (_setjmp(cc->jmp) == 0) {
+		ucontext_t tmp;
+		swapcontext(&tmp, &cc->last);
+	}
+
+	cc->entry(cc);
+}
+
+void cc_init(struct continuation *cc)
+{
+	volatile union cc_arg arg;
+	arg.p = cc;
+	if (getcontext(&cc->uc) == -1)
+		g_error("getcontext() failed: %s", g_strerror(errno));
+	cc->uc.uc_link = &cc->last;
+	cc->uc.uc_stack.ss_sp = cc->stack;
+	cc->uc.uc_stack.ss_size = cc->stack_size;
+	cc->uc.uc_stack.ss_flags = 0;
+
+	makecontext(&cc->uc, (void *)continuation_trampoline, 2, arg.i[0], arg.i[1]);
+	swapcontext(&cc->last, &cc->uc);
+}
+
+int cc_release(struct continuation *cc)
+{
+	if (cc->release)
+		return cc->release(cc);
+
+	return 0;
+}
+
+int cc_swap(struct continuation *from, struct continuation *to)
+{
+	to->exited = 0;
+	if (getcontext(&to->last) == -1)
+		return -1;
+	else if (to->exited == 0)
+		to->exited = 1; // so when coroutine finishes
+        else if (to->exited == 1)
+                return 1; // it ends up here
+
+	if (_setjmp(from->jmp) == 0)
+		_longjmp(to->jmp, 1);
+
+	return 0;
+}
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ *  tab-width: 8
+ * End:
+ */
diff --git a/src/continuation.h b/src/continuation.h
new file mode 100644
index 0000000..675a257
--- /dev/null
+++ b/src/continuation.h
@@ -0,0 +1,61 @@
+/*
+ * GTK VNC Widget
+ *
+ * Copyright (C) 2006  Anthony Liguori <anthony at codemonkey.ws>
+ *
+ * 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.0 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+#ifndef _CONTINUATION_H_
+#define _CONTINUATION_H_
+
+#include <stddef.h>
+#include <ucontext.h>
+#include <setjmp.h>
+
+struct continuation
+{
+	char *stack;
+	size_t stack_size;
+	void (*entry)(struct continuation *cc);
+	int (*release)(struct continuation *cc);
+
+	/* private */
+	ucontext_t uc;
+	ucontext_t last;
+	int exited;
+	jmp_buf jmp;
+};
+
+void cc_init(struct continuation *cc);
+
+int cc_release(struct continuation *cc);
+
+/* you can use an uninitialized struct continuation for from if you do not have
+   the current continuation handy. */
+int cc_swap(struct continuation *from, struct continuation *to);
+
+#define offset_of(type, member) ((unsigned long)(&((type *)0)->member))
+#define container_of(obj, type, member) \
+        (type *)(((char *)obj) - offset_of(type, member))
+
+#endif
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ *  tab-width: 8
+ * End:
+ */
diff --git a/src/controller/Makefile.am b/src/controller/Makefile.am
new file mode 100644
index 0000000..00552e8
--- /dev/null
+++ b/src/controller/Makefile.am
@@ -0,0 +1,100 @@
+NULL =
+
+AM_CPPFLAGS =					\
+	-DG_LOG_DOMAIN=\"GSpiceController\"	\
+	$(GIO_CFLAGS)				\
+	$(COMMON_CFLAGS)			\
+	$(NULL)
+
+# http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html
+AM_LDFLAGS =					\
+	-no-undefined				\
+	$(GIO_LIBS)				\
+	$(NULL)
+
+AM_VALAFLAGS =							\
+	--pkg gio-2.0						\
+	--pkg spice-protocol --vapidir=$(top_srcdir)/data	\
+	--pkg custom --vapidir=$(srcdir)			\
+	-C 							\
+	$(NULL)
+
+lib_LTLIBRARIES = libspice-controller.la
+noinst_PROGRAMS = test-controller spice-controller-dump
+
+libspice_controller_la_VALASOURCES =		\
+	menu.vala				\
+	controller.vala				\
+	foreign-menu.vala			\
+	util.vala				\
+	$(NULL)
+
+libspice_controller_la_BUILT_SOURCES =			\
+	$(libspice_controller_la_VALASOURCES:.vala=.c)	\
+	spice-controller.h				\
+	$(NULL)
+
+BUILT_SOURCES =						\
+	$(libspice_controller_la_BUILT_SOURCES)		\
+	controller.vala.stamp				\
+	$(NULL)
+
+libspice_controller_la_SOURCES =			\
+	$(libspice_controller_la_BUILT_SOURCES)		\
+	custom.h					\
+	spice-controller-listener.c			\
+	spice-controller-listener.h			\
+	spice-foreign-menu-listener.c			\
+	spice-foreign-menu-listener.h			\
+	$(NULL)
+
+if OS_WIN32
+libspice_controller_la_SOURCES +=		\
+	namedpipe.c				\
+	namedpipe.h				\
+	namedpipeconnection.c			\
+	namedpipeconnection.h			\
+	namedpipelistener.c			\
+	namedpipelistener.h			\
+	win32-util.c                            \
+	win32-util.h                            \
+	$(NULL)
+endif
+libspice_controller_la_LDFLAGS =		\
+	$(AM_LDFLAGS)				\
+	-version-info 0:0:0			\
+	$(NULL)
+
+libspice_controllerincludedir = $(includedir)/spice-controller
+libspice_controllerinclude_HEADERS =		\
+	spice-controller.h
+
+test_controller_SOURCES = test.c
+test_controller_LDADD = libspice-controller.la
+
+spice_controller_dump_SOURCES = dump.c
+spice_controller_dump_LDADD = libspice-controller.la
+
+controller.vala.stamp: $(libspice_controller_la_VALASOURCES) custom.vapi
+	@if test -z "$(VALAC)"; then						  \
+		echo "" ;							  \
+		echo "  *** Error: missing valac!" ;				  \
+		echo "  *** You must run autogen.sh or configure --enable-vala" ; \
+		echo "" ;							  \
+		exit 1 ;							  \
+	fi
+	$(VALA_V)$(VALAC) $(VALAFLAGS) $(AM_VALAFLAGS)			\
+	  $(addprefix $(srcdir)/,$(libspice_controller_la_VALASOURCES))	\
+	  -H spice-controller.h
+	@touch $@
+
+$(libspice_controller_la_BUILT_SOURCES): controller.vala.stamp
+
+EXTRA_DIST =					\
+	$(libspice_controller_la_VALASOURCES)	\
+	controller.vala.stamp			\
+	custom.vapi				\
+	gio-windows-2.0.vapi			\
+	$(NULL)
+
+-include $(top_srcdir)/git.mk
diff --git a/src/controller/controller.vala b/src/controller/controller.vala
new file mode 100644
index 0000000..84b4527
--- /dev/null
+++ b/src/controller/controller.vala
@@ -0,0 +1,286 @@
+// Copyright (C) 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/>.
+
+using GLib;
+using Custom;
+using Win32;
+using Spice;
+using SpiceProtocol;
+
+namespace SpiceCtrl {
+
+public errordomain Error {
+	VALUE,
+}
+
+public class Controller: Object {
+	public string host { private set; get; }
+	public uint32 port { private set; get; }
+	public uint32 sport { private set; get; }
+	public string password { private set; get; }
+	public SpiceProtocol.Controller.Display display_flags { private set; get; }
+	public string tls_ciphers { private set; get; }
+	public string host_subject { private set; get; }
+	public string ca_file { private set; get; }
+	public string title { private set; get; }
+	public string hotkeys { private set; get; }
+	public string[] secure_channels { private set; get; }
+	public string[] disable_channels { private set; get; }
+	public SpiceCtrl.Menu? menu  { private set; get; }
+	public bool enable_smartcard { private set; get; }
+	public bool send_cad { private set; get; }
+	public string[] disable_effects {private set; get; }
+	public uint32 color_depth {private set; get; }
+	public bool enable_usbredir { private set; get; }
+	public bool enable_usb_autoshare { private set; get; }
+	public string usb_filter { private set; get; }
+	public string proxy { private set; get; }
+
+	public signal void do_connect ();
+	public signal void show ();
+	public signal void hide ();
+
+	public signal void client_connected ();
+
+	public void menu_item_click_msg (int32 item_id) {
+		var msg = SpiceProtocol.Controller.MsgValue ();
+		msg.base.size = (uint32)sizeof (SpiceProtocol.Controller.MsgValue);
+		msg.base.id = SpiceProtocol.Controller.MsgId.MENU_ITEM_CLICK;
+		msg.value = item_id;
+		unowned uint8[] p = ((uint8[])(&msg))[0:msg.base.size];
+		send_msg.begin (p);
+	}
+
+	public async bool send_msg (uint8[] p) throws GLib.Error {
+		// vala FIXME: pass Controller.Msg instead
+		// vala doesn't keep reference on the struct in async methods
+		// it copies only base, which is not enough to transmit the whole
+		// message.
+		try {
+			if (excl_connection != null) {
+				yield output_stream_write (excl_connection.output_stream, p);
+			} else {
+				foreach (var c in clients)
+					yield output_stream_write (c.output_stream, p);
+			}
+		} catch (GLib.Error e) {
+			warning (e.message);
+		}
+
+		return true;
+	}
+
+	private GLib.IOStream? excl_connection;
+	private int nclients;
+	List<IOStream> clients;
+
+	private bool handle_message (SpiceProtocol.Controller.Msg* msg) {
+		var v = (SpiceProtocol.Controller.MsgValue*)(msg);
+		var d = (SpiceProtocol.Controller.MsgData*)(msg);
+		unowned string str = (string)(&d.data);
+
+		switch (msg.id) {
+		case SpiceProtocol.Controller.MsgId.HOST:
+			host = str;
+			debug ("got HOST: %s".printf (str));
+			break;
+		case SpiceProtocol.Controller.MsgId.PORT:
+			port = v.value;
+			debug ("got PORT: %u".printf (port));
+			break;
+		case SpiceProtocol.Controller.MsgId.SPORT:
+			sport = v.value;
+			debug ("got SPORT: %u".printf (sport));
+			break;
+		case SpiceProtocol.Controller.MsgId.PASSWORD:
+			password = str;
+			debug ("got PASSWORD");
+			break;
+
+		case SpiceProtocol.Controller.MsgId.SECURE_CHANNELS:
+			secure_channels = str.split(",");
+			debug ("got SECURE_CHANNELS %s".printf (str));
+			break;
+
+		case SpiceProtocol.Controller.MsgId.DISABLE_CHANNELS:
+			disable_channels = str.split(",");
+			debug ("got DISABLE_CHANNELS %s".printf (str));
+			break;
+
+		case SpiceProtocol.Controller.MsgId.TLS_CIPHERS:
+			tls_ciphers = str;
+			debug ("got TLS_CIPHERS %s".printf (str));
+			break;
+		case SpiceProtocol.Controller.MsgId.CA_FILE:
+			ca_file = str;
+			debug ("got CA_FILE %s".printf (str));
+			break;
+		case SpiceProtocol.Controller.MsgId.HOST_SUBJECT:
+			host_subject = str;
+			debug ("got HOST_SUBJECT %s".printf (str));
+			break;
+
+		case SpiceProtocol.Controller.MsgId.FULL_SCREEN:
+			display_flags = (SpiceProtocol.Controller.Display)v.value;
+			debug ("got FULL_SCREEN 0x%x".printf (v.value));
+			break;
+		case SpiceProtocol.Controller.MsgId.SET_TITLE:
+			title = str;
+			debug ("got TITLE %s".printf (str));
+			break;
+		case SpiceProtocol.Controller.MsgId.ENABLE_SMARTCARD:
+			enable_smartcard = (bool)v.value;
+			debug ("got ENABLE_SMARTCARD 0x%x".printf (v.value));
+			break;
+
+		case SpiceProtocol.Controller.MsgId.CREATE_MENU:
+			menu = new SpiceCtrl.Menu.from_string (str);
+			debug ("got CREATE_MENU %s".printf (str));
+			break;
+		case SpiceProtocol.Controller.MsgId.DELETE_MENU:
+			menu = null;
+			debug ("got DELETE_MENU request");
+			break;
+
+		case SpiceProtocol.Controller.MsgId.SEND_CAD:
+			send_cad = (bool)v.value;
+			debug ("got SEND_CAD %u".printf (v.value));
+			break;
+
+		case SpiceProtocol.Controller.MsgId.HOTKEYS:
+			hotkeys = str;
+			debug ("got HOTKEYS %s".printf (str));
+			break;
+
+		case SpiceProtocol.Controller.MsgId.COLOR_DEPTH:
+			color_depth = v.value;
+			debug ("got COLOR_DEPTH %u".printf (v.value));
+			break;
+		case SpiceProtocol.Controller.MsgId.DISABLE_EFFECTS:
+			disable_effects = str.split(",");
+			debug ("got DISABLE_EFFECTS %s".printf (str));
+			break;
+
+		case SpiceProtocol.Controller.MsgId.CONNECT:
+			do_connect ();
+			debug ("got CONNECT request");
+			break;
+		case SpiceProtocol.Controller.MsgId.SHOW:
+			show ();
+			debug ("got SHOW request");
+			break;
+		case SpiceProtocol.Controller.MsgId.HIDE:
+			hide ();
+			debug ("got HIDE request");
+			break;
+		case SpiceProtocol.Controller.MsgId.ENABLE_USB:
+			enable_usbredir = (bool)v.value;
+			debug ("got ENABLE_USB %u".printf (v.value));
+			break;
+		case SpiceProtocol.Controller.MsgId.ENABLE_USB_AUTOSHARE:
+			enable_usb_autoshare = (bool)v.value;
+			debug ("got ENABLE_USB_AUTOSHARE %u".printf (v.value));
+			break;
+		case SpiceProtocol.Controller.MsgId.USB_FILTER:
+			usb_filter = str;
+			debug ("got USB_FILTER %s".printf (str));
+			break;
+		case SpiceProtocol.Controller.MsgId.PROXY:
+			proxy = str;
+			debug ("got PROXY %s".printf (str));
+			break;
+		default:
+			debug ("got unknown msg.id %u".printf (msg.id));
+			warn_if_reached ();
+			return false;
+		}
+		return true;
+	}
+
+	private async void handle_client (IOStream c) throws GLib.Error {
+		var excl = false;
+
+		debug ("new socket client, reading init header");
+
+		var p = new uint8[sizeof(SpiceProtocol.Controller.Init)];
+		var init = (SpiceProtocol.Controller.Init*)p;
+		yield input_stream_read (c.input_stream, p);
+		if (warn_if (init.base.magic != SpiceProtocol.Controller.MAGIC))
+			return;
+		if (warn_if (init.base.version != SpiceProtocol.Controller.VERSION))
+			return;
+		if (warn_if (init.base.size < sizeof (SpiceProtocol.Controller.Init)))
+			return;
+		if (warn_if (init.credentials != 0))
+			return;
+		if (warn_if (excl_connection != null))
+			return;
+
+		excl = (bool)(init.flags & SpiceProtocol.Controller.Flag.EXCLUSIVE);
+		if (excl) {
+			if (nclients > 1) {
+				warning (@"Can't make the client exclusive, there is already $nclients connected clients");
+				return;
+			}
+			excl_connection = c;
+		}
+
+		client_connected ();
+
+		for (;;) {
+			var t = new uint8[sizeof(SpiceProtocol.Controller.Msg)];
+			yield input_stream_read (c.input_stream, t);
+			var msg = (SpiceProtocol.Controller.Msg*)t;
+			debug ("new message " + msg.id.to_string () + "size " + msg.size.to_string ());
+			if (warn_if (msg.size < sizeof (SpiceProtocol.Controller.Msg)))
+				break;
+
+			if (msg.size > sizeof (SpiceProtocol.Controller.Msg)) {
+				t.resize ((int)msg.size);
+				msg = (SpiceProtocol.Controller.Msg*)t;
+				yield input_stream_read (c.input_stream, t[sizeof(SpiceProtocol.Controller.Msg):msg.size]);
+			}
+
+			handle_message (msg);
+		}
+
+		if (excl)
+			excl_connection = null;
+	}
+
+	public Controller() {
+	}
+
+	public async void listen (string? addr = null) throws GLib.Error, SpiceCtrl.Error
+	{
+		var listener = ControllerListener.new_listener (addr);
+
+		for (;;) {
+			var c = yield listener.accept_async ();
+			nclients += 1;
+			clients.append (c);
+			try {
+				yield handle_client (c);
+			} catch (GLib.Error e) {
+				warning (e.message);
+			}
+			c.close ();
+			clients.remove (c);
+			nclients -= 1;
+		}
+	}
+}
+
+} // SpiceCtrl
diff --git a/src/controller/custom.h b/src/controller/custom.h
new file mode 100644
index 0000000..7f849fc
--- /dev/null
+++ b/src/controller/custom.h
@@ -0,0 +1,22 @@
+#ifndef CUSTOM_H_
+#define CUSTOM_H_
+
+#include <glib.h>
+
+static inline gboolean g_warn_if_expr (gboolean condition,
+                                       const char *pretty_func,
+                                       const char *expression) {
+  if G_UNLIKELY(condition) {
+      g_log (G_LOG_DOMAIN,
+             G_LOG_LEVEL_CRITICAL,
+             "%s: `%s' condition reached",
+             pretty_func,
+             expression);
+    }
+
+  return condition;
+}
+
+#define g_warn_if(expr) g_warn_if_expr((expr), __PRETTY_FUNCTION__, #expr)
+
+#endif
diff --git a/src/controller/custom.vapi b/src/controller/custom.vapi
new file mode 100644
index 0000000..a12fdec
--- /dev/null
+++ b/src/controller/custom.vapi
@@ -0,0 +1,28 @@
+using GLib;
+
+namespace Custom {
+
+	[CCode (cname = "g_warn_if", cheader_filename = "custom.h")]
+	public bool warn_if(bool condition);
+}
+
+namespace Spice {
+
+	[CCode (cname = "GObject", ref_function = "g_object_ref", unref_function = "g_object_unref", free_function = "")]
+	class ControllerListener {
+		[CCode (cname = "spice_controller_listener_new", cheader_filename = "spice-controller-listener.h")]
+		public static ControllerListener new_listener (string addr) throws GLib.Error;
+
+		[CCode (cname = "spice_controller_listener_accept_async", cheader_filename = "spice-controller-listener.h")]
+		public async unowned GLib.IOStream accept_async (GLib.Cancellable? cancellable = null, out GLib.Object? source_object = null) throws GLib.Error;
+	}
+
+	[CCode (cname = "GObject", ref_function = "g_object_ref", unref_function = "g_object_unref", free_function = "")]
+	class ForeignMenuListener {
+		[CCode (cname = "spice_foreign_menu_listener_new", cheader_filename = "spice-foreign-menu-listener.h")]
+		public static ForeignMenuListener new_listener (string addr) throws GLib.Error;
+
+		[CCode (cname = "spice_foreign_menu_listener_accept_async", cheader_filename = "spice-foreign-menu-listener.h")]
+		public async unowned GLib.IOStream accept_async (GLib.Cancellable? cancellable = null, out GLib.Object? source_object = null) throws GLib.Error;
+	}
+}
diff --git a/src/controller/dump.c b/src/controller/dump.c
new file mode 100644
index 0000000..831a1d7
--- /dev/null
+++ b/src/controller/dump.c
@@ -0,0 +1,118 @@
+/* Copyright (C) 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 <stdio.h>
+#include <stdint.h>
+
+#ifdef WIN32
+#include <windows.h>
+#else
+#include <sys/socket.h>
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+#include <sys/un.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#endif
+
+#include "spice-controller.h"
+
+SpiceCtrlController *ctrl = NULL;
+SpiceCtrlForeignMenu *menu = NULL;
+GMainLoop *loop = NULL;
+
+void signaled (GObject *gobject, const gchar *signal_name)
+{
+    g_message ("signaled: %s", signal_name);
+}
+
+void notified (GObject *gobject, GParamSpec *pspec,
+               gpointer user_data)
+{
+    GValue value = { 0, };
+    GValue strvalue = { 0, };
+
+    g_return_if_fail (gobject != NULL);
+    g_return_if_fail (pspec != NULL);
+
+    g_value_init (&value, pspec->value_type);
+    g_value_init (&strvalue, G_TYPE_STRING);
+    g_object_get_property (gobject, pspec->name, &value);
+
+    if (pspec->value_type == G_TYPE_STRV) {
+      gchar** p = (gchar **)g_value_get_boxed (&value);
+      g_message ("notify::%s == ", pspec->name);
+      while (*p)
+        g_message ("%s", *p++);
+    } else if (G_TYPE_IS_OBJECT(pspec->value_type)) {
+      GObject *o = g_value_get_object (&value);
+      g_message ("notify::%s == %s", pspec->name, o ? G_OBJECT_TYPE_NAME (o) : "null");
+    } else {
+      g_value_transform (&value, &strvalue);
+      g_message ("notify::%s  = %s", pspec->name, g_value_get_string (&strvalue));
+    }
+
+    g_value_unset (&value);
+    g_value_unset (&strvalue);
+}
+
+void connect_signals (gpointer obj)
+{
+    guint i, n_ids = 0;
+    guint *ids = NULL;
+    GType type = G_OBJECT_TYPE (obj);
+
+    ids = g_signal_list_ids (type, &n_ids);
+    for (i = 0; i < n_ids; i++) {
+        const gchar *name = g_signal_name (ids[i]);
+        g_signal_connect (obj, name, G_CALLBACK (signaled), (gpointer)name);
+    }
+}
+
+int main (int argc, char *argv[])
+{
+#if !GLIB_CHECK_VERSION(2,36,0)
+    g_type_init ();
+#endif
+    loop = g_main_loop_new (NULL, FALSE);
+
+    if (argc > 1 && g_str_equal(argv[1], "--menu")) {
+        menu = spice_ctrl_foreign_menu_new ();
+        g_signal_connect (menu, "notify", G_CALLBACK (notified), NULL);
+        connect_signals (menu);
+
+        spice_ctrl_foreign_menu_listen (menu, NULL, NULL, NULL);
+    } else {
+        ctrl = spice_ctrl_controller_new ();
+        g_signal_connect (ctrl, "notify", G_CALLBACK (notified), NULL);
+        connect_signals (ctrl);
+
+        spice_ctrl_controller_listen (ctrl, NULL, NULL, NULL);
+    }
+
+    g_main_loop_run (loop);
+
+    if (ctrl != NULL)
+        g_object_unref (ctrl);
+    if (menu != NULL)
+        g_object_unref (menu);
+
+    return 0;
+}
diff --git a/src/controller/foreign-menu.vala b/src/controller/foreign-menu.vala
new file mode 100644
index 0000000..005955a
--- /dev/null
+++ b/src/controller/foreign-menu.vala
@@ -0,0 +1,197 @@
+// Copyright (C) 2012 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/>.
+
+using Custom;
+
+namespace SpiceCtrl {
+
+public class ForeignMenu: Object {
+
+	public Menu menu { get; private set; }
+    public string title { get; private set; }
+
+	public signal void client_connected ();
+
+	private int nclients;
+	private List<IOStream> clients;
+
+	public ForeignMenu() {
+		menu = new Menu ();
+	}
+
+	public void menu_item_click_msg (int32 item_id) {
+		debug ("clicked id: %d".printf (item_id));
+
+		var msg = SpiceProtocol.ForeignMenu.Event ();
+		msg.base.size = (uint32)sizeof (SpiceProtocol.ForeignMenu.Event);
+		msg.base.id = SpiceProtocol.ForeignMenu.MsgId.ITEM_EVENT;
+		msg.id = item_id;
+		msg.action = SpiceProtocol.ForeignMenu.EventType.CLICK;
+
+		unowned uint8[] p = ((uint8[])(&msg))[0:msg.base.size];
+		send_msg.begin (p);
+	}
+
+	public void menu_item_checked_msg (int32 item_id, bool checked = true) {
+		debug ("%schecked id: %d".printf (checked ? "" : "un", item_id));
+
+		var msg = SpiceProtocol.ForeignMenu.Event ();
+		msg.base.size = (uint32)sizeof (SpiceProtocol.ForeignMenu.Event);
+		msg.base.id = SpiceProtocol.ForeignMenu.MsgId.ITEM_EVENT;
+		msg.id = item_id;
+		msg.action = checked ?
+			SpiceProtocol.ForeignMenu.EventType.CHECKED :
+			SpiceProtocol.ForeignMenu.EventType.UNCHECKED;
+
+		unowned uint8[] p = ((uint8[])(&msg))[0:msg.base.size];
+		send_msg.begin (p);
+	}
+
+	public void app_activated_msg (bool activated = true) {
+		var msg = SpiceProtocol.ForeignMenu.Msg ();
+		msg.size = (uint32)sizeof (SpiceProtocol.ForeignMenu.Event);
+		msg.id = activated ?
+			SpiceProtocol.ForeignMenu.MsgId.APP_ACTIVATED :
+			SpiceProtocol.ForeignMenu.MsgId.APP_DEACTIVATED;
+
+		unowned uint8[] p = ((uint8[])(&msg))[0:msg.size];
+		send_msg.begin (p);
+	}
+
+	public async bool send_msg (owned uint8[] p) throws GLib.Error {
+		// vala FIXME: pass Controller.Msg instead
+		// vala doesn't keep reference on the struct in async methods
+		// it copies only base, which is not enough to transmit the whole
+		// message.
+		try {
+			foreach (var c in clients) {
+				yield output_stream_write (c.output_stream, p);
+			}
+		} catch (GLib.Error e) {
+			warning (e.message);
+		}
+
+		return true;
+	}
+
+	SpiceProtocol.Controller.MenuFlags get_menu_flags (uint32 type) {
+		SpiceProtocol.Controller.MenuFlags flags = 0;
+
+		if ((SpiceProtocol.ForeignMenu.MenuFlags.CHECKED & type) != 0)
+			flags |= SpiceProtocol.Controller.MenuFlags.CHECKED;
+		if ((SpiceProtocol.ForeignMenu.MenuFlags.DIM & type) != 0)
+			flags |= SpiceProtocol.Controller.MenuFlags.GRAYED;
+
+		return flags;
+	}
+
+	private bool handle_message (SpiceProtocol.ForeignMenu.Msg* msg) {
+		switch (msg.id) {
+		case SpiceProtocol.ForeignMenu.MsgId.SET_TITLE:
+			var t = (SpiceProtocol.ForeignMenu.SetTitle*)(msg);
+			title = t.string;
+			break;
+		case SpiceProtocol.ForeignMenu.MsgId.ADD_ITEM:
+			var i = (SpiceProtocol.ForeignMenu.AddItem*)(msg);
+			debug ("add id:%u type:%u position:%u title:%s", i.id, i.type, i.position, i.string);
+			menu.items.append (new MenuItem ((int)i.id, i.string, get_menu_flags (i.type)));
+			notify_property ("menu");
+			break;
+		case SpiceProtocol.ForeignMenu.MsgId.MODIFY_ITEM:
+			debug ("deprecated: modify item");
+			break;
+		case SpiceProtocol.ForeignMenu.MsgId.REMOVE_ITEM:
+			var i = (SpiceProtocol.ForeignMenu.RmItem*)(msg);
+			debug ("not implemented: remove id:%u".printf (i.id));
+			break;
+		case SpiceProtocol.ForeignMenu.MsgId.CLEAR:
+			menu = new Menu ();
+			break;
+		default:
+			warn_if_reached ();
+			return false;
+		}
+		return true;
+	}
+
+	private async void handle_client (IOStream c) throws GLib.Error {
+		debug ("new socket client, reading init header");
+
+		var p = new uint8[sizeof(SpiceProtocol.ForeignMenu.InitHeader)];
+		var header = (SpiceProtocol.ForeignMenu.InitHeader*)p;
+		yield input_stream_read (c.input_stream, p);
+		if (warn_if (header.magic != SpiceProtocol.ForeignMenu.MAGIC))
+			return;
+		if (warn_if (header.version != SpiceProtocol.ForeignMenu.VERSION))
+			return;
+		if (warn_if (header.size < sizeof (SpiceProtocol.ForeignMenu.Init)))
+			return;
+
+		var cp = new uint8[sizeof(uint64)];
+		yield input_stream_read (c.input_stream, cp);
+		uint64 credentials = *(uint64*)cp;
+		if (warn_if (credentials != 0))
+			return;
+
+		var title_size = header.size - sizeof(SpiceProtocol.ForeignMenu.Init);
+		var title = new uint8[title_size + 1];
+		yield c.input_stream.read_async (title[0:title_size]);
+		this.title = (string)title;
+
+		client_connected ();
+
+		for (;;) {
+			var t = new uint8[sizeof(SpiceProtocol.ForeignMenu.Msg)];
+			yield input_stream_read (c.input_stream, t);
+			var msg = (SpiceProtocol.ForeignMenu.Msg*)t;
+			debug ("new message " + msg.id.to_string () + "size " + msg.size.to_string ());
+
+			if (warn_if (msg.size < sizeof (SpiceProtocol.ForeignMenu.Msg)))
+				break;
+
+			if (msg.size > sizeof (SpiceProtocol.ForeignMenu.Msg)) {
+				t.resize ((int)msg.size);
+				msg = (SpiceProtocol.ForeignMenu.Msg*)t;
+
+				yield input_stream_read (c.input_stream, t[sizeof(SpiceProtocol.ForeignMenu.Msg):msg.size]);
+			}
+
+			handle_message (msg);
+		}
+
+	}
+
+	public async void listen (string? addr = null) throws GLib.Error, SpiceCtrl.Error
+	{
+		var listener = Spice.ForeignMenuListener.new_listener (addr);
+
+		for (;;) {
+			var c = yield listener.accept_async ();
+			nclients += 1;
+			clients.append (c);
+			try {
+				yield handle_client (c);
+			} catch (GLib.Error e) {
+				warning (e.message);
+			}
+			c.close ();
+			clients.remove (c);
+			nclients -= 1;
+		}
+	}
+
+}
+
+} // SpiceCtrl
diff --git a/src/controller/gio-windows-2.0.vapi b/src/controller/gio-windows-2.0.vapi
new file mode 100644
index 0000000..a09cfe8
--- /dev/null
+++ b/src/controller/gio-windows-2.0.vapi
@@ -0,0 +1,30 @@
+/* gio-windows-2.0.vapi generated by vapigen. */
+/* NOT YET UPSTREAM: https://bugzilla.gnome.org/show_bug.cgi?id=650052 */
+
+[CCode (cprefix = "GLib", lower_case_cprefix = "glib_")]
+namespace GLib {
+	[CCode (cheader_filename = "gio/gwin32inputstream.h")]
+	public class Win32InputStream : GLib.InputStream {
+		public weak GLib.InputStream parent_instance;
+		[CCode (cname = "g_win32_input_stream_new", type = "GInputStream*", has_construct_function = false)]
+		public Win32InputStream (void* handle, bool close_handle);
+		[CCode (cname = "g_win32_input_stream_get_close_handle")]
+		public static bool get_close_handle (GLib.Win32InputStream stream);
+		[CCode (cname = "g_win32_input_stream_get_handle")]
+		public static void* get_handle (GLib.Win32InputStream stream);
+		[CCode (cname = "g_win32_input_stream_set_close_handle")]
+		public static void set_close_handle (GLib.Win32InputStream stream, bool close_handle);
+	}
+	[CCode (cheader_filename = "gio/gwin32inputstream.h")]
+	public class Win32OutputStream : GLib.OutputStream {
+		public weak GLib.OutputStream parent_instance;
+		[CCode (cname = "g_win32_output_stream_new", type = "GOutputStream*", has_construct_function = false)]
+		public Win32OutputStream (void* handle, bool close_handle);
+		[CCode (cname = "g_win32_output_stream_get_close_handle")]
+		public static bool get_close_handle (GLib.Win32OutputStream stream);
+		[CCode (cname = "g_win32_output_stream_get_handle")]
+		public static void* get_handle (GLib.Win32OutputStream stream);
+		[CCode (cname = "g_win32_output_stream_set_close_handle")]
+		public static void set_close_handle (GLib.Win32OutputStream stream, bool close_handle);
+	}
+}
diff --git a/src/controller/menu.vala b/src/controller/menu.vala
new file mode 100644
index 0000000..7e8fc16
--- /dev/null
+++ b/src/controller/menu.vala
@@ -0,0 +1,108 @@
+// Copyright (C) 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/>.
+
+using GLib;
+using Custom;
+using SpiceProtocol.Controller;
+
+namespace SpiceCtrl {
+
+public class MenuItem: Object {
+
+	public Menu submenu;
+	public int parent_id;
+	public int id;
+	public string text;
+	public string accel;
+	public SpiceProtocol.Controller.MenuFlags flags;
+
+	public MenuItem (int id, string text, SpiceProtocol.Controller.MenuFlags flags) {
+		this.id = id;
+		this.text = text;
+		this.flags = flags;
+	}
+
+	public MenuItem.from_string (string str) throws SpiceCtrl.Error {
+		var params = str.split (SpiceProtocol.Controller.MENU_PARAM_DELIMITER);
+		if (warn_if (params.length != 5))
+			throw new SpiceCtrl.Error.VALUE(""); /* Vala: why is it mandatory to give a string? */
+		parent_id = int.parse (params[0]);
+		id = int.parse (params[1]);
+		var textaccel = params[2].split ("\t");
+		text = textaccel[0];
+		if (textaccel.length > 1)
+			accel = textaccel[1];
+		flags = (SpiceProtocol.Controller.MenuFlags)int.parse (params[3]);
+
+		submenu = new Menu ();
+	}
+
+	public string to_string () {
+		var sub = submenu.to_string ();
+		var str = @"pid: $parent_id, id: $id, text: \"$text\", flags: $flags";
+		foreach (var l in sub.to_string ().split ("\n")) {
+			if (l == "")
+				continue;
+			str += @"\n    $l";
+		}
+		return str;
+	}
+}
+
+public class Menu: Object {
+
+	public List<MenuItem> items;
+
+	public Menu? find_id (int id) {
+		if (id == 0)
+			return this;
+
+		foreach (var item in items) {
+			if (item.id == id)
+				return item.submenu;
+
+			var menu = item.submenu.find_id (id);
+			if (menu != null)
+				return menu;
+		}
+
+		return null;
+	}
+
+	public Menu.from_string (string str) {
+		foreach (var itemstr  in str.split (SpiceProtocol.Controller.MENU_ITEM_DELIMITER)) {
+			try {
+				if (itemstr.length == 0)
+					continue;
+				var item = new MenuItem.from_string (itemstr);
+				var parent = find_id (item.parent_id);
+				if (parent == null)
+					throw new SpiceCtrl.Error.VALUE("Invalid parent menu id");
+				parent.items.append (item);
+			} catch (SpiceCtrl.Error e) {
+				warning (e.message);
+			}
+		}
+	}
+
+	public string to_string () {
+		var str = "";
+		foreach (var i in items)
+			str += @"\n$i";
+		return str;
+	}
+}
+
+} // SpiceCtrl
diff --git a/src/controller/namedpipe.c b/src/controller/namedpipe.c
new file mode 100644
index 0000000..5312218
--- /dev/null
+++ b/src/controller/namedpipe.c
@@ -0,0 +1,270 @@
+/*
+   Copyright (C) 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 "namedpipe.h"
+
+#include <windows.h>
+#include <stdio.h>
+#include <conio.h>
+#include <tchar.h>
+
+static void     spice_named_pipe_initable_iface_init (GInitableIface  *iface);
+static gboolean spice_named_pipe_initable_init       (GInitable       *initable,
+                                                      GCancellable    *cancellable,
+                                                      GError         **error);
+
+G_DEFINE_TYPE_WITH_CODE (SpiceNamedPipe, spice_named_pipe, G_TYPE_OBJECT,
+			 G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
+						spice_named_pipe_initable_iface_init));
+
+enum
+{
+  PROP_0,
+  PROP_NAME,
+  PROP_HANDLE,
+};
+
+struct _SpiceNamedPipePrivate
+{
+  gchar *               name;
+  GError *              construct_error;
+  guint                 inited : 1;
+  HANDLE                handle;
+};
+
+static void
+spice_named_pipe_finalize (GObject *object)
+{
+  SpiceNamedPipe *np = SPICE_NAMED_PIPE (object);
+
+  g_clear_error (&np->priv->construct_error);
+
+  g_free (np->priv->name);
+  np->priv->name = NULL;
+
+  if (np->priv->handle)
+    {
+      CloseHandle (np->priv->handle);
+      np->priv->handle = NULL;
+    }
+
+  if (G_OBJECT_CLASS (spice_named_pipe_parent_class)->finalize)
+    G_OBJECT_CLASS (spice_named_pipe_parent_class)->finalize (object);
+}
+
+#define DEFAULT_PIPE_BUF_SIZE 4096
+
+static void
+spice_named_pipe_constructed (GObject *object)
+{
+  SpiceNamedPipe *np = SPICE_NAMED_PIPE (object);
+
+  if (np->priv->handle)
+    /* TODO: find a way to ensure user provided handle is a named
+       pipe, in overlapped mode */
+    goto end;
+
+  np->priv->handle = CreateNamedPipe (np->priv->name,
+      PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
+      PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
+      PIPE_UNLIMITED_INSTANCES,
+      DEFAULT_PIPE_BUF_SIZE, DEFAULT_PIPE_BUF_SIZE,
+      0, NULL);
+
+  if (np->priv->handle == INVALID_HANDLE_VALUE)
+    {
+      int errsv = GetLastError ();
+      gchar *emsg = g_win32_error_message (errsv);
+
+      g_set_error (&np->priv->construct_error,
+                   G_IO_ERROR,
+                   g_io_error_from_win32_error (errsv),
+                   "Error CreateNamedPipe(): %s",
+                   emsg);
+
+      g_free (emsg);
+      return;
+    }
+
+  /* TODO: we could have a client backlog by creating many pipes, the
+     maximum number of outstanding connections.. or we could just let
+     the named_pipe_listener take multiple NamedPipe instances */
+end:
+  g_assert (np->priv->handle != INVALID_HANDLE_VALUE);
+  return;
+}
+
+static void
+spice_named_pipe_get_property (GObject    *object,
+                               guint       prop_id,
+                               GValue     *value,
+                               GParamSpec *pspec)
+{
+  SpiceNamedPipe *np = SPICE_NAMED_PIPE (object);
+
+  switch (prop_id)
+    {
+      case PROP_NAME:
+        g_value_set_string (value, np->priv->name);
+        break;
+      case PROP_HANDLE:
+        g_value_set_pointer (value, np->priv->handle);
+        break;
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+spice_named_pipe_set_property (GObject      *object,
+                               guint         prop_id,
+                               const GValue *value,
+                               GParamSpec   *pspec)
+{
+  SpiceNamedPipe *np = SPICE_NAMED_PIPE (object);
+
+  switch (prop_id)
+    {
+      case PROP_NAME:
+        g_free (np->priv->name);
+        np->priv->name = g_value_dup_string (value);
+        break;
+      case PROP_HANDLE:
+        np->priv->handle = g_value_get_pointer (value);
+        break;
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+spice_named_pipe_class_init (SpiceNamedPipeClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  g_type_class_add_private (klass, sizeof (SpiceNamedPipePrivate));
+
+  gobject_class->set_property = spice_named_pipe_set_property;
+  gobject_class->get_property = spice_named_pipe_get_property;
+  gobject_class->finalize = spice_named_pipe_finalize;
+  gobject_class->constructed = spice_named_pipe_constructed;
+
+  g_object_class_install_property (gobject_class, PROP_NAME,
+				   g_param_spec_string ("name",
+                                                        "Pipe Name",
+                                                        "The NamedPipe name",
+                                                        NULL,
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_READWRITE |
+                                                        G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class, PROP_HANDLE,
+                                   g_param_spec_pointer ("handle",
+                                                         "Pipe handle",
+                                                         "The pipe handle",
+                                                         G_PARAM_CONSTRUCT_ONLY |
+                                                         G_PARAM_READWRITE |
+                                                         G_PARAM_STATIC_STRINGS));
+}
+
+static void
+spice_named_pipe_init (SpiceNamedPipe *np)
+{
+  np->priv = G_TYPE_INSTANCE_GET_PRIVATE (np,
+                                          SPICE_TYPE_NAMED_PIPE,
+                                          SpiceNamedPipePrivate);
+}
+
+static gboolean
+spice_named_pipe_initable_init (GInitable *initable,
+                                GCancellable *cancellable,
+                                GError  **error)
+{
+  SpiceNamedPipe  *np;
+
+  g_return_val_if_fail (SPICE_IS_NAMED_PIPE (initable), FALSE);
+
+  np = SPICE_NAMED_PIPE (initable);
+
+  if (cancellable != NULL)
+    {
+      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+                           "Cancellable initialization not supported");
+      return FALSE;
+    }
+
+  np->priv->inited = TRUE;
+
+  if (np->priv->construct_error)
+    {
+      if (error)
+	*error = g_error_copy (np->priv->construct_error);
+      return FALSE;
+    }
+
+
+  return TRUE;
+}
+
+static void
+spice_named_pipe_initable_iface_init (GInitableIface *iface)
+{
+  iface->init = spice_named_pipe_initable_init;
+}
+
+SpiceNamedPipe *
+spice_named_pipe_new (const gchar *name, GError **error)
+{
+  return SPICE_NAMED_PIPE (g_initable_new (SPICE_TYPE_NAMED_PIPE,
+                                           NULL, error,
+                                           "name", name,
+                                           NULL));
+}
+
+void *
+spice_named_pipe_get_handle (SpiceNamedPipe *namedpipe)
+{
+  g_return_val_if_fail (SPICE_IS_NAMED_PIPE (namedpipe), NULL);
+
+  return namedpipe->priv->handle;
+}
+
+gboolean
+spice_named_pipe_close (SpiceNamedPipe *np,
+                        GError **error)
+{
+  BOOL res;
+
+  g_return_val_if_fail (SPICE_IS_NAMED_PIPE (np), FALSE);
+
+  res = CloseHandle (np->priv->handle);
+  np->priv->handle = NULL;
+  if (!res)
+    {
+      int errsv = GetLastError ();
+      gchar *emsg = g_win32_error_message (errsv);
+
+      g_set_error (error, G_IO_ERROR,
+		   g_io_error_from_win32_error (errsv),
+		   "Error closing handle: %s",
+		   emsg);
+      g_free (emsg);
+      return FALSE;
+    }
+
+  return TRUE;
+}
diff --git a/src/controller/namedpipe.h b/src/controller/namedpipe.h
new file mode 100644
index 0000000..e0e873b
--- /dev/null
+++ b/src/controller/namedpipe.h
@@ -0,0 +1,59 @@
+/*
+   Copyright (C) 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/>.
+*/
+#ifndef __NAMED_PIPE_H__
+#define __NAMED_PIPE_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_NAMED_PIPE                       (spice_named_pipe_get_type ())
+#define SPICE_NAMED_PIPE(inst)                      (G_TYPE_CHECK_INSTANCE_CAST ((inst),                     \
+                                                     SPICE_TYPE_NAMED_PIPE, SpiceNamedPipe))
+#define SPICE_NAMED_PIPE_CLASS(class)               (G_TYPE_CHECK_CLASS_CAST ((class),                       \
+                                                     SPICE_TYPE_NAMED_PIPE, SpiceNamedPipeClass))
+#define SPICE_IS_NAMED_PIPE(inst)                   (G_TYPE_CHECK_INSTANCE_TYPE ((inst),                     \
+                                                     SPICE_TYPE_NAMED_PIPE))
+#define SPICE_IS_NAMED_PIPE_CLASS(class)            (G_TYPE_CHECK_CLASS_TYPE ((class),                       \
+                                                     SPICE_TYPE_NAMED_PIPE))
+#define SPICE_NAMED_PIPE_GET_CLASS(inst)            (G_TYPE_INSTANCE_GET_CLASS ((inst),                      \
+                                                     SPICE_TYPE_NAMED_PIPE, SpiceNamedPipeClass))
+
+typedef struct _SpiceNamedPipe                       SpiceNamedPipe;
+typedef struct _SpiceNamedPipePrivate                SpiceNamedPipePrivate;
+typedef struct _SpiceNamedPipeClass                  SpiceNamedPipeClass;
+
+struct _SpiceNamedPipeClass
+{
+  GObjectClass parent_class;
+};
+
+struct _SpiceNamedPipe
+{
+  GObject parent_instance;
+  SpiceNamedPipePrivate *priv;
+};
+
+GType            spice_named_pipe_get_type  (void) G_GNUC_CONST;
+
+SpiceNamedPipe * spice_named_pipe_new       (const gchar *name, GError **error);
+void *           spice_named_pipe_get_handle(SpiceNamedPipe *namedpipe);
+gboolean         spice_named_pipe_close     (SpiceNamedPipe *namedpipe,
+                                             GError **error);
+G_END_DECLS
+
+#endif /* __NAMED_PIPE_H__ */
diff --git a/src/controller/namedpipeconnection.c b/src/controller/namedpipeconnection.c
new file mode 100644
index 0000000..3173b61
--- /dev/null
+++ b/src/controller/namedpipeconnection.c
@@ -0,0 +1,245 @@
+/*
+   Copyright (C) 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 "namedpipeconnection.h"
+
+#include <windows.h>
+#include <stdio.h>
+#include <conio.h>
+#include <tchar.h>
+
+#include <gio/gwin32inputstream.h>
+#include <gio/gwin32outputstream.h>
+
+G_DEFINE_TYPE (SpiceNamedPipeConnection, spice_named_pipe_connection,
+               G_TYPE_IO_STREAM)
+
+enum
+{
+  PROP_0,
+  PROP_NAMED_PIPE,
+};
+
+struct _SpiceNamedPipeConnectionPrivate
+{
+  GInputStream   *input_stream;
+  GOutputStream  *output_stream;
+  SpiceNamedPipe *namedpipe;
+  gboolean       in_dispose;
+};
+
+static void
+spice_named_pipe_connection_init (SpiceNamedPipeConnection *connection)
+{
+  connection->priv = G_TYPE_INSTANCE_GET_PRIVATE (connection,
+                                                  SPICE_TYPE_NAMED_PIPE_CONNECTION,
+                                                  SpiceNamedPipeConnectionPrivate);
+}
+
+static void
+spice_named_pipe_connection_get_property (GObject    *object,
+                                          guint       prop_id,
+                                          GValue     *value,
+                                          GParamSpec *pspec)
+{
+  SpiceNamedPipeConnection *c = SPICE_NAMED_PIPE_CONNECTION (object);
+
+  switch (prop_id)
+    {
+      case PROP_NAMED_PIPE:
+        g_return_if_fail (c->priv->namedpipe == NULL);
+        g_value_set_object (value, c->priv->namedpipe);
+        break;
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+spice_named_pipe_connection_set_property (GObject      *object,
+                                          guint         prop_id,
+                                          const GValue *value,
+                                          GParamSpec   *pspec)
+{
+  SpiceNamedPipeConnection *c = SPICE_NAMED_PIPE_CONNECTION (object);
+
+  switch (prop_id)
+    {
+      case PROP_NAMED_PIPE:
+        c->priv->namedpipe = g_value_get_object (value);
+        break;
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static GInputStream *
+spice_named_pipe_connection_get_input_stream (GIOStream *io_stream)
+{
+  SpiceNamedPipeConnection *c = SPICE_NAMED_PIPE_CONNECTION (io_stream);
+  HANDLE h = spice_named_pipe_get_handle (c->priv->namedpipe);
+
+  g_return_val_if_fail (h != NULL, NULL);
+
+  if (c->priv->input_stream == NULL)
+    c->priv->input_stream = g_win32_input_stream_new (h, FALSE);
+
+  return c->priv->input_stream;
+}
+
+static GOutputStream *
+spice_named_pipe_connection_get_output_stream (GIOStream *io_stream)
+{
+  SpiceNamedPipeConnection *c = SPICE_NAMED_PIPE_CONNECTION (io_stream);
+  HANDLE h = spice_named_pipe_get_handle (c->priv->namedpipe);
+
+  g_return_val_if_fail (h != NULL, NULL);
+
+  if (c->priv->output_stream == NULL)
+    c->priv->output_stream = g_win32_output_stream_new (h, FALSE);
+
+  return c->priv->output_stream;
+}
+
+static void
+spice_named_pipe_connection_dispose (GObject *object)
+{
+  SpiceNamedPipeConnection *c = SPICE_NAMED_PIPE_CONNECTION (object);
+
+  c->priv->in_dispose = TRUE;
+
+  if (G_OBJECT_CLASS (spice_named_pipe_connection_parent_class)->dispose)
+    G_OBJECT_CLASS (spice_named_pipe_connection_parent_class)->dispose (object);
+
+  c->priv->in_dispose = FALSE;
+}
+
+static void
+spice_named_pipe_connection_finalize (GObject *object)
+{
+  SpiceNamedPipeConnection *c = SPICE_NAMED_PIPE_CONNECTION (object);
+
+  if (c->priv->output_stream)
+    {
+      g_object_unref (c->priv->output_stream);
+      c->priv->output_stream = NULL;
+    }
+
+  if (c->priv->input_stream)
+    {
+      g_object_unref (c->priv->input_stream);
+      c->priv->input_stream = NULL;
+    }
+
+  g_object_unref (c->priv->namedpipe);
+
+  if (G_OBJECT_CLASS (spice_named_pipe_connection_parent_class)->finalize)
+    G_OBJECT_CLASS (spice_named_pipe_connection_parent_class)->finalize (object);
+}
+
+static gboolean
+spice_named_pipe_connection_close (GIOStream     *stream,
+                                   GCancellable  *cancellable,
+                                   GError       **error)
+{
+  SpiceNamedPipeConnection *c = SPICE_NAMED_PIPE_CONNECTION (stream);
+
+  if (c->priv->output_stream)
+    g_output_stream_close (c->priv->output_stream, cancellable, NULL);
+  if (c->priv->input_stream)
+    g_input_stream_close (c->priv->input_stream, cancellable, NULL);
+
+  /* Don't close the underlying socket if this is being called
+   * as part of dispose(); when destroying the GSocketConnection,
+   * we only want to close the socket if we're holding the last
+   * reference on it, and in that case it will close itself when
+   * we unref namedpipe in finalize().
+   */
+  if (c->priv->in_dispose)
+    return TRUE;
+
+  return spice_named_pipe_close (c->priv->namedpipe, error);
+}
+
+static void
+spice_named_pipe_connection_close_async (GIOStream           *stream,
+                                         int                  io_priority,
+                                         GCancellable        *cancellable,
+                                         GAsyncReadyCallback  callback,
+                                         gpointer             user_data)
+{
+  GSimpleAsyncResult *res;
+  GIOStreamClass *class;
+  GError *error;
+
+  class = G_IO_STREAM_GET_CLASS (stream);
+
+  /* namedpipe close is not blocking, just do it! */
+  error = NULL;
+  if (class->close_fn &&
+      !class->close_fn (stream, cancellable, &error))
+    {
+      g_simple_async_report_take_gerror_in_idle (G_OBJECT (stream),
+                                                 callback, user_data,
+                                                 error);
+      return;
+    }
+
+  res = g_simple_async_result_new (G_OBJECT (stream),
+				   callback,
+				   user_data,
+				   spice_named_pipe_connection_close_async);
+  g_simple_async_result_complete_in_idle (res);
+  g_object_unref (res);
+}
+
+static gboolean
+spice_named_pipe_connection_close_finish (GIOStream     *stream,
+                                          GAsyncResult  *result,
+                                          GError       **error)
+{
+  return TRUE;
+}
+
+static void
+spice_named_pipe_connection_class_init (SpiceNamedPipeConnectionClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  GIOStreamClass *stream_class = G_IO_STREAM_CLASS (klass);
+
+  g_type_class_add_private (klass, sizeof (SpiceNamedPipeConnectionPrivate));
+
+  gobject_class->set_property = spice_named_pipe_connection_set_property;
+  gobject_class->get_property = spice_named_pipe_connection_get_property;
+  gobject_class->dispose = spice_named_pipe_connection_dispose;
+  gobject_class->finalize = spice_named_pipe_connection_finalize;
+
+  stream_class->get_input_stream = spice_named_pipe_connection_get_input_stream;
+  stream_class->get_output_stream = spice_named_pipe_connection_get_output_stream;
+  stream_class->close_fn = spice_named_pipe_connection_close;
+  stream_class->close_async = spice_named_pipe_connection_close_async;
+  stream_class->close_finish = spice_named_pipe_connection_close_finish;
+
+  g_object_class_install_property (gobject_class, PROP_NAMED_PIPE,
+                                   g_param_spec_object ("namedpipe",
+                                                        "NamedPipe",
+                                                        "The associated NamedPipe",
+                                                        SPICE_TYPE_NAMED_PIPE,
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_READWRITE |
+                                                        G_PARAM_STATIC_STRINGS));
+}
diff --git a/src/controller/namedpipeconnection.h b/src/controller/namedpipeconnection.h
new file mode 100644
index 0000000..86f0be6
--- /dev/null
+++ b/src/controller/namedpipeconnection.h
@@ -0,0 +1,56 @@
+/*
+   Copyright (C) 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/>.
+*/
+#ifndef __NAMED_PIPE_CONNECTION_H__
+#define __NAMED_PIPE_CONNECTION_H__
+
+#include <gio/gio.h>
+#include "namedpipe.h"
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_NAMED_PIPE_CONNECTION                     (spice_named_pipe_connection_get_type ())
+#define SPICE_NAMED_PIPE_CONNECTION(inst)                    (G_TYPE_CHECK_INSTANCE_CAST ((inst), \
+                                                              SPICE_TYPE_NAMED_PIPE_CONNECTION, SpiceNamedPipeConnection))
+#define SPICE_NAMED_PIPE_CONNECTION_CLASS(class)             (G_TYPE_CHECK_CLASS_CAST ((class),                       \
+                                                              SPICE_TYPE_NAMED_PIPE_CONNECTION, SpiceNamedPipeConnectionClass))
+#define SPICE_IS_NAMED_PIPE_CONNECTION(inst)                 (G_TYPE_CHECK_INSTANCE_TYPE ((inst),                     \
+                                                              SPICE_TYPE_NAMED_PIPE_CONNECTION))
+#define SPICE_IS_NAMED_PIPE_CONNECTION_CLASS(class)          (G_TYPE_CHECK_CLASS_TYPE ((class),                       \
+                                                              SPICE_TYPE_NAMED_PIPE_CONNECTION))
+#define SPICE_NAMED_PIPE_CONNECTION_GET_CLASS(inst)          (G_TYPE_INSTANCE_GET_CLASS ((inst),                      \
+                                                              SPICE_TYPE_NAMED_PIPE_CONNECTION, SpiceNamedPipeConnectionClass))
+
+typedef struct _SpiceNamedPipeConnection                     SpiceNamedPipeConnection;
+typedef struct _SpiceNamedPipeConnectionPrivate              SpiceNamedPipeConnectionPrivate;
+typedef struct _SpiceNamedPipeConnectionClass                SpiceNamedPipeConnectionClass;
+
+struct _SpiceNamedPipeConnectionClass
+{
+  GIOStreamClass parent_class;
+};
+
+struct _SpiceNamedPipeConnection
+{
+  GIOStream parent_instance;
+  SpiceNamedPipeConnectionPrivate *priv;
+};
+
+GType    spice_named_pipe_connection_get_type                (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* __NAMED_PIPE_CONNECTION_H__ */
diff --git a/src/controller/namedpipelistener.c b/src/controller/namedpipelistener.c
new file mode 100644
index 0000000..820c606
--- /dev/null
+++ b/src/controller/namedpipelistener.c
@@ -0,0 +1,329 @@
+/*
+   Copyright (C) 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 "namedpipelistener.h"
+
+#include <windows.h>
+#include <stdio.h>
+#include <conio.h>
+#include <tchar.h>
+
+static GSource *g_win32_handle_source_add (HANDLE      handle,
+                                           GSourceFunc callback,
+                                           gpointer    user_data);
+
+G_DEFINE_TYPE (SpiceNamedPipeListener, spice_named_pipe_listener, G_TYPE_OBJECT);
+
+struct _SpiceNamedPipeListenerPrivate
+{
+  GQueue             namedpipes;
+};
+
+static void
+spice_named_pipe_listener_dispose (GObject *object)
+{
+  SpiceNamedPipeListener *listener = SPICE_NAMED_PIPE_LISTENER (object);
+  SpiceNamedPipe *p;
+
+  while ((p = g_queue_pop_head (&listener->priv->namedpipes)) != NULL)
+    g_object_unref (p);
+
+  g_return_if_fail (g_queue_get_length (&listener->priv->namedpipes) == 0);
+  g_queue_clear (&listener->priv->namedpipes);
+
+  if (G_OBJECT_CLASS (spice_named_pipe_listener_parent_class)->dispose)
+    G_OBJECT_CLASS (spice_named_pipe_listener_parent_class)->dispose (object);
+}
+
+static void
+spice_named_pipe_listener_class_init (SpiceNamedPipeListenerClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  g_type_class_add_private (klass, sizeof (SpiceNamedPipeListenerPrivate));
+
+  gobject_class->dispose = spice_named_pipe_listener_dispose;
+}
+
+static void
+spice_named_pipe_listener_init (SpiceNamedPipeListener *listener)
+{
+  listener->priv = G_TYPE_INSTANCE_GET_PRIVATE (listener,
+                                                SPICE_TYPE_NAMED_PIPE_LISTENER,
+                                                SpiceNamedPipeListenerPrivate);
+
+  g_queue_init (&listener->priv->namedpipes);
+}
+
+void
+spice_named_pipe_listener_add_named_pipe (SpiceNamedPipeListener *listener,
+                                          SpiceNamedPipe         *namedpipe)
+{
+  g_return_if_fail (SPICE_IS_NAMED_PIPE_LISTENER (listener));
+  g_return_if_fail (SPICE_IS_NAMED_PIPE (namedpipe));
+
+  g_queue_push_head (&listener->priv->namedpipes, g_object_ref (namedpipe));
+}
+
+typedef struct {
+  GCancellable *cancellable;
+  GSource *source;
+  GSimpleAsyncResult *async_result;
+  SpiceNamedPipe *np;
+  OVERLAPPED overlapped;
+} ConnectData;
+
+static void
+connect_cancelled (GCancellable *cancellable,
+                   gpointer      user_data)
+{
+  ConnectData *c = user_data;
+  GError *error = NULL;
+
+  g_source_destroy (c->source);
+  c->source = NULL;
+
+  g_cancellable_set_error_if_cancelled (cancellable, &error);
+  g_simple_async_result_set_from_error (c->async_result, error);
+  g_error_free (error);
+
+  g_simple_async_result_complete (c->async_result);
+  g_object_unref (c->async_result);
+}
+
+static gboolean
+connect_ready (gpointer user_data)
+{
+  ConnectData *c = user_data;
+  gulong cbret;
+  gboolean success;
+
+  /* Now complete the result (assuming it wasn't already completed) */
+  g_return_val_if_fail (c->async_result != NULL, FALSE);
+
+  success = GetOverlappedResult (c->np, &c->overlapped, &cbret, FALSE);
+  if (!success)
+    {
+      int errsv = GetLastError ();
+      gchar *emsg = g_win32_error_message (errsv);
+
+      g_simple_async_result_set_error (c->async_result,
+                                       G_IO_ERROR,
+                                       G_IO_ERROR_INVALID_ARGUMENT,
+                                       "GetOverlappedResult(): %s %d",
+                                       emsg, errsv);
+    }
+
+  g_simple_async_result_complete (c->async_result);
+  g_object_unref (c->async_result); /* TODO: that sould free c? */
+
+  return FALSE;
+}
+
+static void
+connect_data_free (gpointer data)
+{
+  ConnectData *c = data;
+
+  if (c->source)
+    {
+      g_source_destroy (c->source);
+      g_source_unref (c->source);
+      c->source = NULL;
+    }
+  if (c->cancellable)
+    {
+      g_signal_handlers_disconnect_by_func (c->cancellable, connect_cancelled, c);
+      g_object_unref (c->cancellable);
+      c->cancellable = NULL;
+    }
+
+  if (c->async_result) /* this is only a weak reference */
+      c->async_result = NULL;
+
+  if (c->overlapped.hEvent != NULL)
+    {
+      CloseHandle (c->overlapped.hEvent);
+      c->overlapped.hEvent = NULL;
+    }
+
+  if (c->np != NULL)
+    {
+      g_object_unref (c->np);
+      c->np = NULL;
+    }
+
+  g_free (c);
+}
+
+void
+spice_named_pipe_listener_accept_async (SpiceNamedPipeListener  *listener,
+                                        GCancellable            *cancellable,
+                                        GAsyncReadyCallback      callback,
+                                        gpointer                 user_data)
+{
+  ConnectData *c;
+  SpiceNamedPipe *namedpipe;
+
+  g_return_if_fail (SPICE_IS_NAMED_PIPE_LISTENER (listener));
+
+  namedpipe = SPICE_NAMED_PIPE (g_queue_pop_head (&listener->priv->namedpipes));
+  /* do not unref, we keep that ref */
+  g_return_if_fail (namedpipe != NULL);
+
+  c = g_new0 (ConnectData, 1);
+  c->np = namedpipe; /* transfer what used to be the avail_namedpipes ref */
+  c->async_result = g_simple_async_result_new (G_OBJECT (listener), callback, user_data,
+                                               spice_named_pipe_listener_accept_async);
+  c->overlapped.hEvent = CreateEvent (NULL, /* default security attribute */
+                                      TRUE, /* manual-reset event */
+                                      TRUE, /* initial state = signaled */
+                                      NULL); /* unnamed event object */
+  g_simple_async_result_set_op_res_gpointer (c->async_result, c, connect_data_free);
+
+  if (ConnectNamedPipe (spice_named_pipe_get_handle (namedpipe), &c->overlapped) != 0)
+    {
+      /* we shouldn't get there if the listener is in non-blocking */
+      g_warn_if_reached ();
+    }
+
+  switch (GetLastError ())
+    {
+      case ERROR_SUCCESS:
+      case ERROR_IO_PENDING:
+        break;
+      case ERROR_PIPE_CONNECTED:
+        g_simple_async_result_complete_in_idle (c->async_result);
+        g_object_unref (c->async_result);
+        return;
+      default:
+        g_simple_async_report_error_in_idle (G_OBJECT (listener),
+            callback, user_data,
+            G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
+            "ConnectNamedPipe() failed %ld", GetLastError ());
+        g_object_unref (c->async_result);
+        return;
+    }
+
+  c->source = g_win32_handle_source_add (c->overlapped.hEvent,
+                                         connect_ready, c);
+
+  if (cancellable)
+    {
+      c->cancellable = g_object_ref (cancellable);
+      g_signal_connect (cancellable, "cancelled",
+                        G_CALLBACK (connect_cancelled), c);
+    }
+}
+
+SpiceNamedPipeConnection *
+spice_named_pipe_listener_accept_finish (SpiceNamedPipeListener *listener,
+                                         GAsyncResult           *result,
+                                         GObject               **source_object,
+                                         GError                **error)
+{
+  GSimpleAsyncResult *simple;
+  ConnectData *c;
+  SpiceNamedPipeConnection *connection;
+
+  g_return_val_if_fail (SPICE_IS_NAMED_PIPE_LISTENER (listener), NULL);
+  g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), NULL);
+  g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (listener),
+                                                        spice_named_pipe_listener_accept_async),
+                        NULL);
+
+  simple = G_SIMPLE_ASYNC_RESULT (result);
+  if (g_simple_async_result_propagate_error (simple, error))
+      return NULL;
+
+  c = g_simple_async_result_get_op_res_gpointer (simple);
+
+  connection = g_object_new (SPICE_TYPE_NAMED_PIPE_CONNECTION,
+                             "namedpipe", c->np,
+                             NULL);
+  return connection;
+}
+
+SpiceNamedPipeListener *
+spice_named_pipe_listener_new (void)
+{
+  return g_object_new (SPICE_TYPE_NAMED_PIPE_LISTENER, NULL);
+}
+
+/* Windows HANDLE GSource - from gio/gwin32resolver.c */
+
+typedef struct {
+  GSource source;
+  GPollFD pollfd;
+} GWin32HandleSource;
+
+static gboolean
+g_win32_handle_source_prepare (GSource *source,
+                               gint    *timeout)
+{
+  *timeout = -1;
+  return FALSE;
+}
+
+static gboolean
+g_win32_handle_source_check (GSource *source)
+{
+  GWin32HandleSource *hsource = (GWin32HandleSource *)source;
+
+  return hsource->pollfd.revents;
+}
+
+static gboolean
+g_win32_handle_source_dispatch (GSource     *source,
+                                GSourceFunc  callback,
+                                gpointer     user_data)
+{
+  return (*callback) (user_data);
+}
+
+static void
+g_win32_handle_source_finalize (GSource *source)
+{
+  ;
+}
+
+GSourceFuncs g_win32_handle_source_funcs = {
+  g_win32_handle_source_prepare,
+  g_win32_handle_source_check,
+  g_win32_handle_source_dispatch,
+  g_win32_handle_source_finalize
+};
+
+static GSource *
+g_win32_handle_source_add (HANDLE      handle,
+                           GSourceFunc callback,
+                           gpointer    user_data)
+{
+  GWin32HandleSource *hsource;
+  GSource *source;
+
+  source = g_source_new (&g_win32_handle_source_funcs, sizeof (GWin32HandleSource));
+  hsource = (GWin32HandleSource *)source;
+  hsource->pollfd.fd = (gint)handle;
+  hsource->pollfd.events = G_IO_IN;
+  hsource->pollfd.revents = 0;
+  g_source_add_poll (source, &hsource->pollfd);
+
+  g_source_set_callback (source, callback, user_data, NULL);
+  g_source_attach (source, g_main_context_get_thread_default ());
+  return source;
+}
diff --git a/src/controller/namedpipelistener.h b/src/controller/namedpipelistener.h
new file mode 100644
index 0000000..c2dbd0a
--- /dev/null
+++ b/src/controller/namedpipelistener.h
@@ -0,0 +1,70 @@
+/*
+   Copyright (C) 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/>.
+*/
+#ifndef __NAMED_PIPE_LISTENER_H__
+#define __NAMED_PIPE_LISTENER_H__
+
+#include <gio/gio.h>
+
+#include "namedpipe.h"
+#include "namedpipeconnection.h"
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_NAMED_PIPE_LISTENER                       (spice_named_pipe_listener_get_type ())
+#define SPICE_NAMED_PIPE_LISTENER(inst)                      (G_TYPE_CHECK_INSTANCE_CAST ((inst),                     \
+                                                              SPICE_TYPE_NAMED_PIPE_LISTENER, SpiceNamedPipeListener))
+#define SPICE_NAMED_PIPE_LISTENER_CLASS(class)               (G_TYPE_CHECK_CLASS_CAST ((class),                       \
+                                                              SPICE_TYPE_NAMED_PIPE_LISTENER, SpiceNamedPipeListenerClass))
+#define SPICE_IS_NAMED_PIPE_LISTENER(inst)                   (G_TYPE_CHECK_INSTANCE_TYPE ((inst),                     \
+                                                              SPICE_TYPE_NAMED_PIPE_LISTENER))
+#define SPICE_IS_NAMED_PIPE_LISTENER_CLASS(class)            (G_TYPE_CHECK_CLASS_TYPE ((class),                       \
+                                                              SPICE_TYPE_NAMED_PIPE_LISTENER))
+#define SPICE_NAMED_PIPE_LISTENER_GET_CLASS(inst)            (G_TYPE_INSTANCE_GET_CLASS ((inst),                      \
+                                                              SPICE_TYPE_NAMED_PIPE_LISTENER, SpiceNamedPipeListenerClass))
+
+typedef struct _SpiceNamedPipeListener                       SpiceNamedPipeListener;
+typedef struct _SpiceNamedPipeListenerPrivate                SpiceNamedPipeListenerPrivate;
+typedef struct _SpiceNamedPipeListenerClass                  SpiceNamedPipeListenerClass;
+
+struct _SpiceNamedPipeListenerClass
+{
+  GObjectClass parent_class;
+};
+
+struct _SpiceNamedPipeListener
+{
+  GObject parent_instance;
+  SpiceNamedPipeListenerPrivate *priv;
+};
+
+GType                       spice_named_pipe_listener_get_type       (void) G_GNUC_CONST;
+
+SpiceNamedPipeListener *    spice_named_pipe_listener_new            (void);
+void                        spice_named_pipe_listener_add_named_pipe (SpiceNamedPipeListener  *listener,
+                                                                      SpiceNamedPipe          *namedpipe);
+void                        spice_named_pipe_listener_accept_async   (SpiceNamedPipeListener *listener,
+                                                                      GCancellable           *cancellable,
+                                                                      GAsyncReadyCallback     callback,
+                                                                      gpointer                user_data);
+SpiceNamedPipeConnection *  spice_named_pipe_listener_accept_finish  (SpiceNamedPipeListener *listener,
+                                                                      GAsyncResult           *result,
+                                                                      GObject               **source_object,
+                                                                      GError                **error);
+
+G_END_DECLS
+
+#endif /* __NAMED_PIPE_LISTENER_H__ */
diff --git a/src/controller/spice-controller-listener.c b/src/controller/spice-controller-listener.c
new file mode 100644
index 0000000..98baf33
--- /dev/null
+++ b/src/controller/spice-controller-listener.c
@@ -0,0 +1,159 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2012 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 <glib/gstdio.h>
+
+#include "spice-controller-listener.h"
+
+#ifdef G_OS_WIN32
+#include <windows.h>
+#include "namedpipe.h"
+#include "namedpipelistener.h"
+#include "win32-util.h"
+#endif
+
+#ifdef G_OS_UNIX
+#include <gio/gunixsocketaddress.h>
+#endif
+
+/**
+ * SpiceControllerListenerError:
+ * @SPICE_CONTROLLER_LISTENER_ERROR_VALUE: invalid value.
+ *
+ * Possible errors of controller listener related functions.
+ **/
+
+/**
+ * SPICE_CONTROLLER_LISTENER_ERROR:
+ *
+ * The error domain of the controller listener subsystem.
+ **/
+GQuark
+spice_controller_listener_error_quark (void)
+{
+  return g_quark_from_static_string ("spice-controller-listener-error");
+}
+
+GObject*
+spice_controller_listener_new (const gchar *address, GError **error)
+{
+    GObject *listener = NULL;
+    gchar *addr = NULL;
+
+    g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+    addr = g_strdup (address);
+
+#ifdef G_OS_WIN32
+    if (addr == NULL)
+        addr = g_strdup (g_getenv ("SPICE_XPI_NAMEDPIPE"));
+    if (addr == NULL)
+        addr = g_strdup_printf ("\\\\.\\pipe\\SpiceController-%" G_GUINT64_FORMAT, (guint64)GetCurrentProcessId ());
+#else
+    if (addr == NULL)
+        addr = g_strdup (g_getenv ("SPICE_XPI_SOCKET"));
+#endif
+    if (addr == NULL) {
+        g_set_error (error,
+                     SPICE_CONTROLLER_LISTENER_ERROR,
+                     SPICE_CONTROLLER_LISTENER_ERROR_VALUE,
+#ifdef G_OS_WIN32
+                     "Missing namedpipe address"
+#else
+                     "Missing socket address"
+#endif
+                     );
+        goto end;
+    }
+
+    g_unlink (addr);
+
+#ifdef G_OS_WIN32
+    {
+        SpiceNamedPipe *np;
+
+        listener = G_OBJECT (spice_named_pipe_listener_new ());
+
+        np = spice_win32_user_pipe_new (addr, error);
+        if (!np) {
+            g_object_unref (listener);
+            listener = NULL;
+            goto end;
+        }
+
+        spice_named_pipe_listener_add_named_pipe (SPICE_NAMED_PIPE_LISTENER (listener), np);
+    }
+#else
+    {
+        listener = G_OBJECT (g_socket_listener_new ());
+
+        if (!g_socket_listener_add_address (G_SOCKET_LISTENER (listener),
+                                            G_SOCKET_ADDRESS (g_unix_socket_address_new (addr)),
+                                            G_SOCKET_TYPE_STREAM,
+                                            G_SOCKET_PROTOCOL_DEFAULT,
+                                            NULL,
+                                            NULL,
+                                            error))
+            g_warning ("failed to add address");
+    }
+#endif
+
+end:
+    g_free (addr);
+    return listener;
+}
+
+void
+spice_controller_listener_accept_async (GObject *listener,
+                                        GCancellable *cancellable,
+                                        GAsyncReadyCallback callback,
+                                        gpointer user_data)
+{
+    g_return_if_fail(G_IS_OBJECT(listener));
+
+#ifdef G_OS_WIN32
+    spice_named_pipe_listener_accept_async (SPICE_NAMED_PIPE_LISTENER (listener), cancellable, callback, user_data);
+#else
+    g_socket_listener_accept_async (G_SOCKET_LISTENER (listener), cancellable, callback, user_data);
+#endif
+}
+
+GIOStream*
+spice_controller_listener_accept_finish (GObject *listener,
+                                         GAsyncResult *result,
+                                         GObject **source_object,
+                                         GError **error)
+{
+    g_return_val_if_fail(G_IS_OBJECT(listener), NULL);
+
+#ifdef G_OS_WIN32
+    SpiceNamedPipeConnection *np;
+    np = spice_named_pipe_listener_accept_finish (SPICE_NAMED_PIPE_LISTENER (listener), result, source_object, error);
+    if (np)
+        return G_IO_STREAM (np);
+#else
+    GSocketConnection *socket;
+    socket = g_socket_listener_accept_finish (G_SOCKET_LISTENER (listener), result, source_object, error);
+    if (socket)
+        return G_IO_STREAM (socket);
+#endif
+
+    return NULL;
+}
diff --git a/src/controller/spice-controller-listener.h b/src/controller/spice-controller-listener.h
new file mode 100644
index 0000000..a50bdea
--- /dev/null
+++ b/src/controller/spice-controller-listener.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2012 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_CONTROLLER_LISTENER_H__
+#define __SPICE_CONTROLLER_LISTENER_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define SPICE_CONTROLLER_LISTENER_ERROR spice_controller_listener_error_quark ()
+GQuark spice_controller_listener_error_quark (void);
+
+typedef enum
+{
+    SPICE_CONTROLLER_LISTENER_ERROR_VALUE /* incorrect value */
+} SpiceControllerListenerError;
+
+
+GObject* spice_controller_listener_new (const gchar *address, GError **error);
+
+void spice_controller_listener_accept_async (GObject *listener,
+                                             GCancellable *cancellable,
+                                             GAsyncReadyCallback callback,
+                                             gpointer user_data);
+
+GIOStream* spice_controller_listener_accept_finish (GObject *listener,
+                                                    GAsyncResult *result,
+                                                    GObject **source_object,
+                                                    GError **error);
+G_END_DECLS
+
+#endif /* __SPICE_CONTROLLER_LISTENER_H__ */
diff --git a/src/controller/spice-foreign-menu-listener.c b/src/controller/spice-foreign-menu-listener.c
new file mode 100644
index 0000000..5e62606
--- /dev/null
+++ b/src/controller/spice-foreign-menu-listener.c
@@ -0,0 +1,161 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2012 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 <glib/gstdio.h>
+
+#include "spice-foreign-menu-listener.h"
+
+#ifdef G_OS_WIN32
+#include <windows.h>
+#include "namedpipe.h"
+#include "namedpipelistener.h"
+#include "win32-util.h"
+#endif
+
+#ifdef G_OS_UNIX
+#include <gio/gunixsocketaddress.h>
+#endif
+
+/**
+ * SpiceForeignMenuListenerError:
+ * @SPICE_FOREIGN_MENU_LISTENER_ERROR_VALUE: invalid value.
+ *
+ * Possible errors of foreign menu listener related functions.
+ **/
+
+/**
+ * SPICE_FOREIGN_MENU_LISTENER_ERROR:
+ *
+ * The error domain of the foreign menu listener subsystem.
+ **/
+GQuark
+spice_foreign_menu_listener_error_quark (void)
+{
+  return g_quark_from_static_string ("spice-foreign-menu-listener-error");
+}
+
+GObject*
+spice_foreign_menu_listener_new (const gchar *address, GError **error)
+{
+    GObject *listener = NULL;
+    gchar *addr = NULL;
+
+    g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+    addr = g_strdup (address);
+
+#ifdef G_OS_WIN32
+    if (addr == NULL)
+        addr = g_strdup (g_getenv ("SPICE_FOREIGN_MENU_NAMEDPIPE"));
+    if (addr == NULL)
+        addr = g_strdup_printf ("\\\\.\\pipe\\SpiceForeignMenu-%" G_GUINT64_FORMAT, (guint64)GetCurrentProcessId ());
+#else
+    if (addr == NULL)
+        addr = g_strdup (g_getenv ("SPICE_FOREIGN_MENU_SOCKET"));
+    if (addr == NULL)
+        addr = g_strdup_printf ("/tmp/SpiceForeignMenu-%" G_GUINT64_FORMAT ".uds", (guint64)getpid ());
+#endif
+    if (addr == NULL) {
+        g_set_error (error,
+                     SPICE_FOREIGN_MENU_LISTENER_ERROR,
+                     SPICE_FOREIGN_MENU_LISTENER_ERROR_VALUE,
+#ifdef G_OS_WIN32
+                     "Missing namedpipe address"
+#else
+                     "Missing socket address"
+#endif
+                     );
+        goto end;
+    }
+
+    g_unlink (addr);
+
+#ifdef G_OS_WIN32
+    {
+        SpiceNamedPipe *np;
+
+        listener = G_OBJECT (spice_named_pipe_listener_new ());
+
+        np = spice_win32_user_pipe_new (addr, error);
+        if (!np) {
+            g_object_unref (listener);
+            listener = NULL;
+            goto end;
+        }
+
+        spice_named_pipe_listener_add_named_pipe (SPICE_NAMED_PIPE_LISTENER (listener), np);
+    }
+#else
+    {
+        listener = G_OBJECT (g_socket_listener_new ());
+
+        if (!g_socket_listener_add_address (G_SOCKET_LISTENER (listener),
+                                            G_SOCKET_ADDRESS (g_unix_socket_address_new (addr)),
+                                            G_SOCKET_TYPE_STREAM,
+                                            G_SOCKET_PROTOCOL_DEFAULT,
+                                            NULL,
+                                            NULL,
+                                            error))
+            g_warning ("failed to add address");
+    }
+#endif
+
+end:
+    g_free (addr);
+    return listener;
+}
+
+void
+spice_foreign_menu_listener_accept_async (GObject *listener,
+                                          GCancellable *cancellable,
+                                          GAsyncReadyCallback callback,
+                                          gpointer user_data)
+{
+    g_return_if_fail(G_IS_OBJECT(listener));
+
+#ifdef G_OS_WIN32
+    spice_named_pipe_listener_accept_async (SPICE_NAMED_PIPE_LISTENER (listener), cancellable, callback, user_data);
+#else
+    g_socket_listener_accept_async (G_SOCKET_LISTENER (listener), cancellable, callback, user_data);
+#endif
+}
+
+GIOStream*
+spice_foreign_menu_listener_accept_finish (GObject *listener,
+                                           GAsyncResult *result,
+                                           GObject **source_object,
+                                           GError **error)
+{
+    g_return_val_if_fail(G_IS_OBJECT(listener), NULL);
+
+#ifdef G_OS_WIN32
+    SpiceNamedPipeConnection *np;
+    np = spice_named_pipe_listener_accept_finish (SPICE_NAMED_PIPE_LISTENER (listener), result, source_object, error);
+    if (np)
+        return G_IO_STREAM (np);
+#else
+    GSocketConnection *socket;
+    socket = g_socket_listener_accept_finish (G_SOCKET_LISTENER (listener), result, source_object, error);
+    if (socket)
+        return G_IO_STREAM (socket);
+#endif
+
+    return NULL;
+}
diff --git a/src/controller/spice-foreign-menu-listener.h b/src/controller/spice-foreign-menu-listener.h
new file mode 100644
index 0000000..1071528
--- /dev/null
+++ b/src/controller/spice-foreign-menu-listener.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2012 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_FOREIGN_MENU_LISTENER_H__
+#define __SPICE_FOREIGN_MENU_LISTENER_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define SPICE_FOREIGN_MENU_LISTENER_ERROR spice_foreign_menu_listener_error_quark ()
+GQuark spice_foreign_menu_listener_error_quark (void);
+
+typedef enum
+{
+    SPICE_FOREIGN_MENU_LISTENER_ERROR_VALUE /* incorrect value */
+} SpiceForeignMenuListenerError;
+
+
+GObject* spice_foreign_menu_listener_new (const gchar *address, GError **error);
+
+void spice_foreign_menu_listener_accept_async (GObject *listener,
+                                             GCancellable *cancellable,
+                                             GAsyncReadyCallback callback,
+                                             gpointer user_data);
+
+GIOStream* spice_foreign_menu_listener_accept_finish (GObject *listener,
+                                                    GAsyncResult *result,
+                                                    GObject **source_object,
+                                                    GError **error);
+G_END_DECLS
+
+#endif /* __SPICE_FOREIGN_MENU_LISTENER_H__ */
diff --git a/src/controller/test.c b/src/controller/test.c
new file mode 100644
index 0000000..c08fe21
--- /dev/null
+++ b/src/controller/test.c
@@ -0,0 +1,292 @@
+/* Copyright (C) 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 <stdio.h>
+#include <stdint.h>
+#include <spice/controller_prot.h>
+
+#include "spice-controller.h"
+
+#ifdef WIN32
+#include <windows.h>
+#define PIPE_NAME TEXT("\\\\.\\pipe\\SpiceController-%lu")
+static HANDLE pipe = INVALID_HANDLE_VALUE;
+#else
+
+#include <sys/socket.h>
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+#include <sys/un.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#define PIPE_NAME "/tmp/test"
+static int sock = -1;
+
+#endif
+
+#define PIPE_NAME_MAX_LEN 256
+
+void write_to_pipe (const void* data, size_t len)
+{
+#ifdef WIN32
+    DWORD written;
+    if (!WriteFile (pipe, data, len, &written, NULL) || written != len) {
+        printf("Write to pipe failed %u\n", GetLastError());
+    }
+#else
+    if (send (sock, data, len, 0) != len) {
+        printf ("send failed, (%d) %s\n", errno, strerror(errno));
+    }
+#endif
+}
+
+gboolean send_init (void)
+{
+    ControllerInit msg = {
+      { CONTROLLER_MAGIC, CONTROLLER_VERSION, sizeof (msg) },
+      0,
+      CONTROLLER_FLAG_EXCLUSIVE
+    };
+
+    write_to_pipe(&msg, sizeof (msg));
+    return FALSE;
+}
+
+void send_msg (uint32_t id)
+{
+    ControllerMsg msg = {
+      id, sizeof (msg)
+    };
+
+    write_to_pipe (&msg, sizeof (msg));
+}
+
+void send_value (uint32_t id, uint32_t value)
+{
+    ControllerValue msg = {
+      { id, sizeof(msg) },
+      value
+    };
+
+    write_to_pipe (&msg, sizeof (msg));
+}
+
+void send_data (uint32_t id, uint8_t* data, size_t data_size)
+{
+    size_t size = sizeof (ControllerData) + data_size;
+    ControllerData* msg = (ControllerData*)g_malloc0 (size);
+
+    msg->base.id = id;
+    msg->base.size = (uint32_t)size;
+    memcpy (msg->data, data, data_size);
+    write_to_pipe (msg, size);
+    g_free (msg);
+}
+
+ssize_t read_from_pipe (void* data, size_t size)
+{
+    ssize_t read;
+#ifdef WIN32
+    DWORD bytes;
+    if (!ReadFile (pipe, data, size, &bytes, NULL)) {
+        printf ("Read from pipe failed %u\n", GetLastError());
+    }
+    read = bytes;
+#else
+    read = recv (sock, data, size, 0);
+    if ((read == -1 || read == 0)) {
+        printf ("recv failed, (%d) %s\n", errno, strerror (errno));
+    }
+#endif
+    return read;
+}
+
+#define HOST "localhost"
+#define PORT 5931
+#define SPORT 0
+#define PWD "P at s5w0rd"
+#define SECURE_CHANNELS "main,inputs,playback"
+#define DISABLED_CHANNELS "playback,record"
+#define TITLE "Hello from controller"
+#define HOTKEYS "toggle-fullscreen=shift+f1,release-cursor=shift+f2"
+#define MENU "0\r4864\rS&end Ctrl+Alt+Del\tCtrl+Alt+End\r0\r\n" \
+    "0\r5120\r&Toggle full screen\tShift+F11\r0\r\n" \
+    "0\r1\r&Special keys\r4\r\n" \
+    "1\r5376\r&Send Shift+F11\r0\r\n" \
+    "1\r5632\r&Send Shift+F12\r0\r\n" \
+    "1\r5888\r&Send Ctrl+Alt+End\r0\r\n" \
+    "0\r1\r-\r1\r\n" \
+    "0\r2\rChange CD\r4\r\n" \
+    "2\r3\rNo CDs\r0\r\n" \
+    "2\r4\r[Eject]\r0\r\n" \
+    "0\r5\r-\r1\r\n" \
+    "0\r6\rPlay\r0\r\n" \
+    "0\r7\rSuspend\r0\r\n" \
+    "0\r8\rStop\r0\r\n"
+
+#define TLS_CIPHERS "TLS_C1PHERS"
+#define CA_FILE "C at _FILE"
+#define HOST_SUBJECT "Host_SUBJ3CT"
+
+SpiceCtrlController *ctrl;
+GMainLoop *loop;
+
+void signaled (GObject    *gobject, const gchar *signal_name)
+{
+    g_message ("signaled: %s", signal_name);
+    if (g_str_equal (signal_name, "hide")) {
+      spice_ctrl_controller_menu_item_click_msg (ctrl, 42);
+      g_timeout_add (1000, (GSourceFunc)g_main_loop_quit, loop);
+    }
+}
+
+void notified (GObject    *gobject, GParamSpec *pspec,
+               gpointer    user_data)
+{
+    GValue value = { 0, };
+    GValue strvalue = { 0, };
+
+    g_return_if_fail (gobject != NULL);
+    g_return_if_fail (pspec != NULL);
+
+    g_value_init (&value, pspec->value_type);
+    g_value_init (&strvalue, G_TYPE_STRING);
+    g_object_get_property (gobject, pspec->name, &value);
+
+    if (pspec->value_type == G_TYPE_STRV) {
+      gchar** p = (gchar **)g_value_get_boxed (&value);
+      g_message ("notify::%s == ", pspec->name);
+      while (*p)
+        g_message ("%s", *p++);
+    } else if (G_TYPE_IS_OBJECT(pspec->value_type)) {
+      GObject *o = g_value_get_object (&value);
+      g_message ("notify::%s == %s", pspec->name, o ? G_OBJECT_TYPE_NAME (o) : "null");
+    } else {
+      g_value_transform (&value, &strvalue);
+      g_message ("notify::%s  = %s", pspec->name, g_value_get_string (&strvalue));
+    }
+
+    g_value_unset (&value);
+    g_value_unset (&strvalue);
+}
+
+void connect_signals (gpointer obj)
+{
+    guint i, n_ids = 0;
+    guint *ids = NULL;
+    GType type = G_OBJECT_TYPE (obj);
+
+    ids = g_signal_list_ids (type, &n_ids);
+    for (i = 0; i < n_ids; i++) {
+        const gchar *name = g_signal_name (ids[i]);
+        g_signal_connect (obj, name, G_CALLBACK (signaled), (gpointer)name);
+    }
+}
+
+int main (int argc, char *argv[])
+{
+#ifdef WIN32
+    int spicec_pid = (argc > 1 ? atoi (argv[1]) : 0);
+#endif
+    char* host = (argc > 2 ? argv[2] : (char*)HOST);
+    int port = (argc > 3 ? atoi (argv[3]) : PORT);
+    char pipe_name[PIPE_NAME_MAX_LEN];
+    ControllerValue msg;
+    ssize_t read;
+
+#if !GLIB_CHECK_VERSION(2,36,0)
+    g_type_init ();
+#endif
+    ctrl = spice_ctrl_controller_new ();
+    loop = g_main_loop_new (NULL, FALSE);
+    g_signal_connect (ctrl, "notify", G_CALLBACK (notified), NULL);
+    connect_signals (ctrl);
+
+#ifdef WIN32
+    snprintf (pipe_name, PIPE_NAME_MAX_LEN, PIPE_NAME, spicec_pid);
+    spice_ctrl_controller_listen (ctrl, pipe_name, NULL, NULL);
+
+    printf ("Creating Spice controller connection %s\n", pipe_name);
+    pipe = CreateFile (pipe_name, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
+    if (pipe == INVALID_HANDLE_VALUE) {
+        printf ("Could not open pipe %u\n", GetLastError());
+        return -1;
+    }
+#else
+    spice_ctrl_controller_listen (ctrl, PIPE_NAME, NULL, NULL);
+
+    snprintf (pipe_name, PIPE_NAME_MAX_LEN, PIPE_NAME);
+    printf ("Creating a controller connection %s\n", pipe_name);
+    struct sockaddr_un remote;
+    if ((sock = socket (AF_UNIX, SOCK_STREAM, 0)) == -1) {
+        printf ("Could not open socket, (%d) %s\n", errno, strerror(errno));
+        return -1;
+    }
+    remote.sun_family = AF_UNIX;
+    strcpy (remote.sun_path, pipe_name);
+    if (connect (sock, (struct sockaddr *)&remote,
+                strlen (remote.sun_path) + sizeof(remote.sun_family)) == -1) {
+        printf ("Socket connect failed, (%d) %s\n", errno, strerror(errno));
+        close (sock);
+        return -1;
+    }
+#endif
+
+    /* TODO: we rely on socket / pipe buffer... which is lame :) */
+    send_init ();
+
+    send_data (CONTROLLER_HOST, (uint8_t*)host, strlen(host) + 1);
+    send_value (CONTROLLER_PORT, port);
+    send_value (CONTROLLER_SPORT, SPORT);
+    send_data (CONTROLLER_PASSWORD, (uint8_t*)PWD, strlen(PWD) + 1);
+    send_data (CONTROLLER_SECURE_CHANNELS, (uint8_t*)SECURE_CHANNELS, strlen(SECURE_CHANNELS) + 1);
+    send_data (CONTROLLER_DISABLE_CHANNELS, (uint8_t*)DISABLED_CHANNELS, strlen(DISABLED_CHANNELS) + 1);
+    send_data (CONTROLLER_TLS_CIPHERS, (uint8_t*)TLS_CIPHERS, sizeof(TLS_CIPHERS) + 1);
+    send_data (CONTROLLER_CA_FILE, (uint8_t*)CA_FILE, strlen(CA_FILE) + 1);
+    send_data (CONTROLLER_HOST_SUBJECT, (uint8_t*)HOST_SUBJECT, strlen(HOST_SUBJECT) + 1);
+    send_data (CONTROLLER_SET_TITLE, (uint8_t*)TITLE, strlen(TITLE) + 1);
+    send_data (CONTROLLER_HOTKEYS, (uint8_t*)HOTKEYS, strlen(HOTKEYS) + 1);
+    send_data (CONTROLLER_CREATE_MENU, (uint8_t*)MENU, strlen(MENU));
+
+    send_value (CONTROLLER_FULL_SCREEN, /*CONTROLLER_SET_FULL_SCREEN |*/ CONTROLLER_AUTO_DISPLAY_RES);
+
+    send_msg (CONTROLLER_SHOW);
+    send_msg (CONTROLLER_CONNECT);
+    send_msg (CONTROLLER_SHOW);
+    send_msg (CONTROLLER_DELETE_MENU);
+    send_msg (CONTROLLER_HIDE);
+
+    g_main_loop_run (loop);
+
+    while ((read = read_from_pipe (&msg, sizeof(msg))) == sizeof(msg)) {
+        printf ("Received id %u, size %u, value %u\n", msg.base.id, msg.base.size, msg.value);
+        if (msg.value == 42)
+          break;
+    }
+
+#ifdef WIN32
+    CloseHandle (pipe);
+#else
+    close (sock);
+#endif
+    g_object_unref (ctrl);
+    return 0;
+}
diff --git a/src/controller/util.vala b/src/controller/util.vala
new file mode 100644
index 0000000..acd677e
--- /dev/null
+++ b/src/controller/util.vala
@@ -0,0 +1,42 @@
+// Copyright (C) 2012 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/>.
+
+namespace SpiceCtrl {
+
+	public async void input_stream_read (InputStream stream, uint8[] buffer) throws GLib.IOError {
+		var length = buffer.length;
+		ssize_t i = 0;
+
+		while (i < length) {
+			var n = yield stream.read_async (buffer[i:length]);
+			if (n == 0)
+				throw new GLib.IOError.CLOSED ("closed stream") ;
+			i += n;
+		}
+	}
+
+	public async void output_stream_write (OutputStream stream, owned uint8[] buffer) throws GLib.IOError {
+		var length = buffer.length;
+		ssize_t i = 0;
+
+		while (i < length) {
+			var n = yield stream.write_async (buffer[i:length]);
+			if (n == 0)
+				throw new GLib.IOError.CLOSED ("closed stream") ;
+			i += n;
+		}
+	}
+
+}
diff --git a/src/controller/win32-util.c b/src/controller/win32-util.c
new file mode 100644
index 0000000..c3e0400
--- /dev/null
+++ b/src/controller/win32-util.c
@@ -0,0 +1,161 @@
+/*
+   Copyright (C) 2012 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 "win32-util.h"
+#include <windows.h>
+#include <sddl.h>
+#include <aclapi.h>
+
+gboolean
+spice_win32_set_low_integrity (void* handle, GError **error)
+{
+    g_return_val_if_fail (handle != NULL, FALSE);
+    g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+    /* see also http://msdn.microsoft.com/en-us/library/bb625960.aspx */
+    PSECURITY_DESCRIPTOR psd = NULL;
+    PACL psacl = NULL;
+    BOOL sacl_present = FALSE;
+    BOOL sacl_defaulted = FALSE;
+    char *emsg;
+    int errsv;
+    gboolean success = FALSE;
+
+    if (!ConvertStringSecurityDescriptorToSecurityDescriptor ("S:(ML;;NW;;;LW)",
+                                                              SDDL_REVISION_1, &psd, NULL))
+        goto failed;
+
+    if (!GetSecurityDescriptorSacl (psd, &sacl_present, &psacl, &sacl_defaulted))
+        goto failed;
+
+    if (SetSecurityInfo (handle, SE_KERNEL_OBJECT, LABEL_SECURITY_INFORMATION,
+                         NULL, NULL, NULL, psacl) != ERROR_SUCCESS)
+        goto failed;
+
+    success = TRUE;
+    goto end;
+
+failed:
+    errsv = GetLastError ();
+    emsg = g_win32_error_message (errsv);
+    g_set_error (error, G_IO_ERROR,
+                 g_io_error_from_win32_error (errsv),
+                 "Error setting integrity: %s",
+                 emsg);
+    g_free (emsg);
+
+end:
+    if (psd != NULL)
+        LocalFree (psd);
+
+    return success;
+}
+
+static gboolean
+get_user_security_attributes (SECURITY_ATTRIBUTES* psa, SECURITY_DESCRIPTOR* psd, PACL* ppdacl)
+{
+    EXPLICIT_ACCESS ea;
+    TRUSTEE trst;
+    DWORD ret = 0;
+
+    ZeroMemory (psa, sizeof (*psa));
+    ZeroMemory (psd, sizeof (*psd));
+    psa->nLength = sizeof (*psa);
+    psa->bInheritHandle = FALSE;
+    psa->lpSecurityDescriptor = psd;
+
+    ZeroMemory (&trst, sizeof (trst));
+    trst.pMultipleTrustee = NULL;
+    trst.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE;
+    trst.TrusteeForm = TRUSTEE_IS_NAME;
+    trst.TrusteeType = TRUSTEE_IS_USER;
+    trst.ptstrName = "CURRENT_USER";
+
+    ZeroMemory (&ea, sizeof (ea));
+    ea.grfAccessPermissions = GENERIC_WRITE | GENERIC_READ;
+    ea.grfAccessMode = SET_ACCESS;
+    ea.grfInheritance = NO_INHERITANCE;
+    ea.Trustee = trst;
+
+    ret = SetEntriesInAcl (1, &ea, NULL, ppdacl);
+    if (ret != ERROR_SUCCESS)
+        return FALSE;
+
+   if (!InitializeSecurityDescriptor (psd, SECURITY_DESCRIPTOR_REVISION))
+       return FALSE;
+
+   if (!SetSecurityDescriptorDacl (psd, TRUE, *ppdacl, FALSE))
+       return FALSE;
+
+   return TRUE;
+}
+
+#define DEFAULT_PIPE_BUF_SIZE 4096
+
+SpiceNamedPipe*
+spice_win32_user_pipe_new (gchar *name, GError **error)
+{
+    SECURITY_ATTRIBUTES sa;
+    SECURITY_DESCRIPTOR sd;
+    PACL dacl = NULL;
+    HANDLE pipe;
+    SpiceNamedPipe *np = NULL;
+
+    g_return_val_if_fail (name != NULL, NULL);
+    g_return_val_if_fail (error != NULL, NULL);
+
+    if (!get_user_security_attributes (&sa, &sd, &dacl))
+        return NULL;
+
+    pipe = CreateNamedPipe (name,
+        PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED |
+    /* FIXME: why is FILE_FLAG_FIRST_PIPE_INSTANCE needed for WRITE_DAC
+     * (apparently needed by SetSecurityInfo). This will prevent
+     * multiple pipe listener....?! */
+        FILE_FLAG_FIRST_PIPE_INSTANCE | WRITE_DAC,
+        PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
+        PIPE_UNLIMITED_INSTANCES,
+        DEFAULT_PIPE_BUF_SIZE, DEFAULT_PIPE_BUF_SIZE,
+        0, &sa);
+
+    if (pipe == INVALID_HANDLE_VALUE) {
+        int errsv = GetLastError ();
+        gchar *emsg = g_win32_error_message (errsv);
+
+        g_set_error (error,
+                     G_IO_ERROR,
+                     g_io_error_from_win32_error (errsv),
+                     "Error CreateNamedPipe(): %s",
+                     emsg);
+
+        g_free (emsg);
+        goto end;
+    }
+
+    /* lower integrity on Vista/Win7+ */
+    if ((LOBYTE (g_win32_get_windows_version()) > 0x05) &&
+        !spice_win32_set_low_integrity (pipe, error))
+        goto end;
+
+    np = SPICE_NAMED_PIPE (g_initable_new (SPICE_TYPE_NAMED_PIPE,
+                                           NULL, error, "handle", pipe, NULL));
+
+end:
+    LocalFree (dacl);
+
+    return np;
+}
diff --git a/src/controller/win32-util.h b/src/controller/win32-util.h
new file mode 100644
index 0000000..b24ac77
--- /dev/null
+++ b/src/controller/win32-util.h
@@ -0,0 +1,30 @@
+/*
+   Copyright (C) 2012 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 __WIN32_UTIL_H__
+#define __WIN32_UTIL_H__
+
+#include <gio/gio.h>
+#include "namedpipe.h"
+
+G_BEGIN_DECLS
+
+gboolean        spice_win32_set_low_integrity (void* handle, GError **error);
+SpiceNamedPipe* spice_win32_user_pipe_new (gchar *name, GError **error);
+
+G_END_DECLS
+
+#endif /* __WIN32_UTIL_H__ */
diff --git a/src/coroutine.h b/src/coroutine.h
new file mode 100644
index 0000000..78dc467
--- /dev/null
+++ b/src/coroutine.h
@@ -0,0 +1,83 @@
+/*
+ * GTK VNC Widget
+ *
+ * Copyright (C) 2006  Anthony Liguori <anthony at codemonkey.ws>
+ *
+ * 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.0 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+#ifndef _COROUTINE_H_
+#define _COROUTINE_H_
+
+#include "config.h"
+
+#if WITH_UCONTEXT
+#include "continuation.h"
+#elif WITH_WINFIBER
+#include <windows.h>
+#else
+#include <glib.h>
+#endif
+
+struct coroutine
+{
+	size_t stack_size;
+	void *(*entry)(void *);
+	int (*release)(struct coroutine *);
+
+	/* read-only */
+	int exited;
+
+	/* private */
+	struct coroutine *caller;
+	void *data;
+
+#if WITH_UCONTEXT
+	struct continuation cc;
+#elif WITH_WINFIBER
+        LPVOID fiber;
+        int ret;
+#else
+	GThread *thread;
+	gboolean runnable;
+#endif
+};
+
+void coroutine_init(struct coroutine *co);
+
+int coroutine_release(struct coroutine *co);
+
+void *coroutine_swap(struct coroutine *from, struct coroutine *to, void *arg);
+
+struct coroutine *coroutine_self(void);
+
+void *coroutine_yieldto(struct coroutine *to, void *arg);
+
+void *coroutine_yield(void *arg);
+
+gboolean coroutine_is_main(struct coroutine *co);
+
+static inline gboolean coroutine_self_is_main(void) {
+	return coroutine_self() == NULL || coroutine_is_main(coroutine_self());
+}
+
+#endif
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ *  tab-width: 8
+ * End:
+ */
diff --git a/src/coroutine_gthread.c b/src/coroutine_gthread.c
new file mode 100644
index 0000000..b0098fa
--- /dev/null
+++ b/src/coroutine_gthread.c
@@ -0,0 +1,170 @@
+/*
+ * GTK VNC Widget
+ *
+ * Copyright (C) 2006  Anthony Liguori <anthony at codemonkey.ws>
+ *
+ * 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.0 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+#include "config.h"
+
+#include "coroutine.h"
+#include <stdio.h>
+#include <stdlib.h>
+
+static GCond *run_cond;
+static GMutex *run_lock;
+static struct coroutine *current;
+static struct coroutine leader;
+
+#if 0
+#define CO_DEBUG(OP) fprintf(stderr, "%s %p %s %d\n", OP, g_thread_self(), __FUNCTION__, __LINE__)
+#else
+#define CO_DEBUG(OP)
+#endif
+
+static void coroutine_system_init(void)
+{
+	if (!g_thread_supported()) {
+	        CO_DEBUG("INIT");
+		g_thread_init(NULL);
+	}
+
+
+	run_cond = g_cond_new();
+	run_lock = g_mutex_new();
+	CO_DEBUG("LOCK");
+	g_mutex_lock(run_lock);
+
+	/* The thread that creates the first coroutine is the system coroutine
+	 * so let's fill out a structure for it */
+	leader.entry = NULL;
+	leader.release = NULL;
+	leader.stack_size = 0;
+	leader.exited = 0;
+	leader.thread = g_thread_self();
+	leader.runnable = TRUE; /* we're the one running right now */
+	leader.caller = NULL;
+	leader.data = NULL;
+
+	current = &leader;
+}
+
+static gpointer coroutine_thread(gpointer opaque)
+{
+	struct coroutine *co = opaque;
+	CO_DEBUG("LOCK");
+	g_mutex_lock(run_lock);
+	while (!co->runnable) {
+		CO_DEBUG("WAIT");
+		g_cond_wait(run_cond, run_lock);
+	}
+
+	CO_DEBUG("RUNNABLE");
+	current = co;
+	co->caller->data = co->entry(co->data);
+	co->exited = 1;
+
+	co->caller->runnable = TRUE;
+	CO_DEBUG("BROADCAST");
+	g_cond_broadcast(run_cond);
+	CO_DEBUG("UNLOCK");
+	g_mutex_unlock(run_lock);
+
+	return NULL;
+}
+
+void coroutine_init(struct coroutine *co)
+{
+	GError *err = NULL;
+
+	if (run_cond == NULL)
+		coroutine_system_init();
+
+	CO_DEBUG("NEW");
+	co->thread = g_thread_create_full(coroutine_thread, co, co->stack_size,
+					  FALSE, TRUE,
+					  G_THREAD_PRIORITY_NORMAL,
+					  &err);
+	if (err != NULL)
+		g_error("g_thread_create_full() failed: %s", err->message);
+
+	co->exited = 0;
+	co->runnable = FALSE;
+	co->caller = NULL;
+}
+
+int coroutine_release(struct coroutine *co G_GNUC_UNUSED)
+{
+	return 0;
+}
+
+void *coroutine_swap(struct coroutine *from, struct coroutine *to, void *arg)
+{
+	from->runnable = FALSE;
+	to->runnable = TRUE;
+	to->data = arg;
+	to->caller = from;
+	CO_DEBUG("BROADCAST");
+	g_cond_broadcast(run_cond);
+	CO_DEBUG("UNLOCK");
+	g_mutex_unlock(run_lock);
+	CO_DEBUG("LOCK");
+	g_mutex_lock(run_lock);
+	while (!from->runnable) {
+	        CO_DEBUG("WAIT");
+		g_cond_wait(run_cond, run_lock);
+	}
+	current = from;
+	to->caller = NULL;
+
+	CO_DEBUG("SWAPPED");
+	return from->data;
+}
+
+struct coroutine *coroutine_self(void)
+{
+	if (run_cond == NULL)
+		coroutine_system_init();
+
+	return current;
+}
+
+void *coroutine_yieldto(struct coroutine *to, void *arg)
+{
+	g_return_val_if_fail(!to->caller, NULL);
+	g_return_val_if_fail(!to->exited, NULL);
+
+	CO_DEBUG("SWAP");
+	return coroutine_swap(coroutine_self(), to, arg);
+}
+
+void *coroutine_yield(void *arg)
+{
+	struct coroutine *to = coroutine_self()->caller;
+	if (!to) {
+		fprintf(stderr, "Co-routine is yielding to no one\n");
+		abort();
+	}
+
+	CO_DEBUG("SWAP");
+	coroutine_self()->caller = NULL;
+	return coroutine_swap(coroutine_self(), to, arg);
+}
+
+gboolean coroutine_is_main(struct coroutine *co)
+{
+    return (co == &leader);
+}
diff --git a/src/coroutine_ucontext.c b/src/coroutine_ucontext.c
new file mode 100644
index 0000000..d709a33
--- /dev/null
+++ b/src/coroutine_ucontext.c
@@ -0,0 +1,150 @@
+/*
+ * GTK VNC Widget
+ *
+ * Copyright (C) 2006  Anthony Liguori <anthony at codemonkey.ws>
+ *
+ * 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.0 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+#include "config.h"
+#include <glib.h>
+
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+#include <sys/mman.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "coroutine.h"
+
+#ifndef MAP_ANONYMOUS
+# define MAP_ANONYMOUS MAP_ANON
+#endif
+
+int coroutine_release(struct coroutine *co)
+{
+	return cc_release(&co->cc);
+}
+
+static int _coroutine_release(struct continuation *cc)
+{
+	struct coroutine *co = container_of(cc, struct coroutine, cc);
+
+	if (co->release) {
+		int ret = co->release(co);
+		if (ret < 0)
+			return ret;
+	}
+
+	munmap(co->cc.stack, co->cc.stack_size);
+
+	co->caller = NULL;
+
+	return 0;
+}
+
+static void coroutine_trampoline(struct continuation *cc)
+{
+	struct coroutine *co = container_of(cc, struct coroutine, cc);
+	co->data = co->entry(co->data);
+}
+
+void coroutine_init(struct coroutine *co)
+{
+	if (co->stack_size == 0)
+		co->stack_size = 16 << 20;
+
+	co->cc.stack_size = co->stack_size;
+	co->cc.stack = mmap(0, co->stack_size,
+			    PROT_READ | PROT_WRITE,
+			    MAP_PRIVATE | MAP_ANONYMOUS,
+			    -1, 0);
+	if (co->cc.stack == MAP_FAILED)
+		g_error("mmap(%" G_GSIZE_FORMAT ") failed: %s",
+			co->stack_size, g_strerror(errno));
+
+	co->cc.entry = coroutine_trampoline;
+	co->cc.release = _coroutine_release;
+	co->exited = 0;
+
+	cc_init(&co->cc);
+}
+
+#if 0
+static __thread struct coroutine leader;
+static __thread struct coroutine *current;
+#else
+static struct coroutine leader;
+static struct coroutine *current;
+#endif
+
+struct coroutine *coroutine_self(void)
+{
+	if (current == NULL)
+		current = &leader;
+	return current;
+}
+
+void *coroutine_swap(struct coroutine *from, struct coroutine *to, void *arg)
+{
+	int ret;
+	to->data = arg;
+	current = to;
+	ret = cc_swap(&from->cc, &to->cc);
+	if (ret == 0)
+		return from->data;
+	else if (ret == 1) {
+		coroutine_release(to);
+		current = from;
+		to->exited = 1;
+		return to->data;
+	}
+
+	return NULL;
+}
+
+void *coroutine_yieldto(struct coroutine *to, void *arg)
+{
+	g_return_val_if_fail(!to->caller, NULL);
+	g_return_val_if_fail(!to->exited, NULL);
+
+	to->caller = coroutine_self();
+	return coroutine_swap(coroutine_self(), to, arg);
+}
+
+void *coroutine_yield(void *arg)
+{
+	struct coroutine *to = coroutine_self()->caller;
+	if (!to) {
+		fprintf(stderr, "Co-routine is yielding to no one\n");
+		abort();
+	}
+	coroutine_self()->caller = NULL;
+	return coroutine_swap(coroutine_self(), to, arg);
+}
+
+gboolean coroutine_is_main(struct coroutine *co)
+{
+    return (co == &leader);
+}
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ *  tab-width: 8
+ * End:
+ */
diff --git a/src/coroutine_winfibers.c b/src/coroutine_winfibers.c
new file mode 100644
index 0000000..a56d33d
--- /dev/null
+++ b/src/coroutine_winfibers.c
@@ -0,0 +1,126 @@
+/*
+ * SpiceGtk coroutine with Windows fibers
+ *
+ * Copyright (C) 2011  Marc-André Lureau <marcandre.lureau at redhat.com>
+ *
+ * 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.0 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+#include "config.h"
+#include <stdio.h>
+#include <glib.h>
+
+#include "coroutine.h"
+
+static struct coroutine leader = { 0, };
+static struct coroutine *current = NULL;
+static struct coroutine *caller = NULL;
+
+int coroutine_release(struct coroutine *co)
+{
+	DeleteFiber(co->fiber);
+	return 0;
+}
+
+static void WINAPI coroutine_trampoline(LPVOID lpParameter)
+{
+	struct coroutine *co = (struct coroutine *)lpParameter;
+
+	co->data = co->entry(co->data);
+
+	if (co->release)
+		co->ret = co->release(co);
+	else
+		co->ret = 0;
+
+	co->caller = NULL;
+
+	// and switch back to caller
+	co->ret = 1;
+	SwitchToFiber(caller->fiber);
+}
+
+void coroutine_init(struct coroutine *co)
+{
+	if (leader.fiber == NULL) {
+		leader.fiber = ConvertThreadToFiber(&leader);
+		if (leader.fiber == NULL)
+			g_error("ConvertThreadToFiber() failed");
+	}
+
+	co->exited = 0;
+	co->fiber = CreateFiber(0, &coroutine_trampoline, co);
+	if (co->fiber == NULL)
+		g_error("CreateFiber() failed");
+
+	co->ret = 0;
+}
+
+struct coroutine *coroutine_self(void)
+{
+	if (current == NULL)
+		current = &leader;
+	return current;
+}
+
+void *coroutine_swap(struct coroutine *from, struct coroutine *to, void *arg)
+{
+	to->data = arg;
+	current = to;
+	caller = from;
+	SwitchToFiber(to->fiber);
+	if (to->ret == 0)
+		return from->data;
+	else if (to->ret == 1) {
+		coroutine_release(to);
+		current = &leader;
+		to->exited = 1;
+		return to->data;
+	}
+
+	return NULL;
+}
+
+void *coroutine_yieldto(struct coroutine *to, void *arg)
+{
+	g_return_val_if_fail(!to->caller, NULL);
+	g_return_val_if_fail(!to->exited, NULL);
+
+	to->caller = coroutine_self();
+	return coroutine_swap(coroutine_self(), to, arg);
+}
+
+void *coroutine_yield(void *arg)
+{
+	struct coroutine *to = coroutine_self()->caller;
+	if (!to) {
+		fprintf(stderr, "Co-routine is yielding to no one\n");
+		abort();
+	}
+	coroutine_self()->caller = NULL;
+	return coroutine_swap(coroutine_self(), to, arg);
+}
+
+gboolean coroutine_is_main(struct coroutine *co)
+{
+    return (co == &leader);
+}
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ *  tab-width: 8
+ * End:
+ */
diff --git a/src/decode-glz-tmpl.c b/src/decode-glz-tmpl.c
new file mode 100644
index 0000000..b337a8b
--- /dev/null
+++ b/src/decode-glz-tmpl.c
@@ -0,0 +1,336 @@
+/*
+   Copyright (C) 2009 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/>.
+*/
+
+// External defines: PLT, RGBX/PLTXX/ALPHA, TO_RGB32.
+// If PLT4/1 and TO_RGB32 are defined, we need CAST_PLT_DISTANCE (
+// because then the number of pixels differ from the units used in the compression)
+
+/*
+    For each output pixel type the following macros are defined:
+    OUT_PIXEL                      - the output pixel type
+    COPY_PIXEL(p, out)              - assigns the pixel to the place pointed by out and
+                                      increases out. Used in RLE.
+                                      Need special handling because in alpha we copy only
+                                      the pad byte.
+    COPY_REF_PIXEL(ref, out)      - copies the pixel pointed by ref to the pixel pointed by out.
+                                    Increases ref and out.
+    COPY_COMP_PIXEL(encoder, out) - copies pixel from the compressed buffer to the decompressed
+                                    buffer. Increases out.
+*/
+
+#if !defined(LZ_RGB_ALPHA)
+#define COPY_PIXEL(p, out) (*(out++) = p)
+#define COPY_REF_PIXEL(ref, out) (*(out++) = *(ref++))
+#endif
+
+// decompressing plt to plt
+#ifdef LZ_PLT
+#ifndef TO_RGB32
+#define OUT_PIXEL one_byte_pixel_t
+#define FNAME(name) glz_plt_##name
+#define COPY_COMP_PIXEL(in, out) {(out)->a = *(in++); out++;}
+#else // TO_RGB32
+#define OUT_PIXEL rgb32_pixel_t
+#define COPY_PLT_ENTRY(ent, out) {\
+    (out)->b = ent; (out)->g = (ent >> 8); (out)->r = (ent >> 16); (out)->pad = 0;}
+#ifdef PLT8
+#define FNAME(name) glz_plt8_to_rgb32_##name
+#define COPY_COMP_PIXEL(in, out, palette) {         \
+    uint32_t rgb = palette->ents[*(in++)];          \
+    COPY_PLT_ENTRY(rgb, out);                       \
+    out++;                                          \
+}
+#elif defined(PLT4_BE)
+#define FNAME(name) glz_plt4_be_to_rgb32_##name
+#define COPY_COMP_PIXEL(in, out, palette){                                    \
+    uint8_t byte = *(in++);                                                   \
+    uint32_t rgb = palette->ents[((byte >> 4) & 0x0f) % (palette->num_ents)]; \
+    COPY_PLT_ENTRY(rgb, out);                                                 \
+    out++;                                                                    \
+    rgb = palette->ents[(byte & 0x0f) % (palette->num_ents)];                 \
+    COPY_PLT_ENTRY(rgb, out);                                                 \
+    out++;                                                                    \
+}
+#define CAST_PLT_DISTANCE(dist) (dist*2)
+#elif  defined(PLT4_LE)
+#define FNAME(name) glz_plt4_le_to_rgb32_##name
+#define COPY_COMP_PIXEL(in, out, palette){                                \
+    uint8_t byte = *(in++);                                               \
+    uint32_t rgb = palette->ents[(byte & 0x0f) % (palette->num_ents)];    \
+    COPY_PLT_ENTRY(rgb, out);                                             \
+    out++;                                                                \
+    rgb = palette->ents[((byte >> 4) & 0x0f) % (palette->num_ents)];      \
+    COPY_PLT_ENTRY(rgb, out);                                             \
+    out++;                                                                \
+}
+#define CAST_PLT_DISTANCE(dist) (dist*2)
+#elif defined(PLT1_BE) // TODO store palette entries for direct access
+#define FNAME(name) glz_plt1_be_to_rgb32_##name
+#define COPY_COMP_PIXEL(in, out, palette){                                \
+    uint8_t byte = *(in++);                                               \
+    int i;                                                                \
+    uint32_t fore = palette->ents[1];                                     \
+    uint32_t back = palette->ents[0];                                     \
+    for (i = 7; i >= 0; i--)                                              \
+    {                                                                     \
+        if ((byte >> i) & 1) {                                            \
+            COPY_PLT_ENTRY(fore, out);                                    \
+        } else {                                                          \
+            COPY_PLT_ENTRY(back, out);                                    \
+        }                                                                 \
+        out++;                                                            \
+    }                                                                     \
+}
+#define CAST_PLT_DISTANCE(dist) (dist*8)
+#elif defined(PLT1_LE)
+#define FNAME(name) glz_plt1_le_to_rgb32_##name
+#define COPY_COMP_PIXEL(in, out, palette){                                \
+    uint8_t byte = *(in++);                                               \
+    int i;                                                                \
+    uint32_t fore = palette->ents[1];                                     \
+    uint32_t back = palette->ents[0];                                     \
+    for (i = 0; i < 8; i++)                                               \
+    {                                                                     \
+        if ((byte >> i) & 1) {                                            \
+            COPY_PLT_ENTRY(fore, out);                                    \
+        } else {                                                          \
+            COPY_PLT_ENTRY(back, out);                                    \
+        }                                                                 \
+        out++;                                                            \
+    }                                                                     \
+}
+#define CAST_PLT_DISTANCE(dist) (dist*8)
+#endif // PLT Type
+#endif // TO_RGB32
+#endif
+
+#ifdef LZ_RGB16
+#ifndef TO_RGB32
+#define OUT_PIXEL rgb16_pixel_t
+#define FNAME(name) glz_rgb16_##name
+#define COPY_COMP_PIXEL(in, out) {*out = (*(in++)) << 8; *out |= *(in++); out++;}
+#else
+#define OUT_PIXEL rgb32_pixel_t
+#define FNAME(name) glz_rgb16_to_rgb32_##name
+#define COPY_COMP_PIXEL(in, out) {out->r = *(in++); out->b= *(in++);       \
+    out->g = (((out->r) << 6) | ((out->b) >> 2)) & ~0x07;                  \
+    out->g |= (out->g >> 5);                                               \
+    out->r = ((out->r << 1) & ~0x07) | ((out->r >> 4) & 0x07) ;            \
+    out->b = (out->b << 3) | ((out->b >> 2) & 0x07);                       \
+    out->pad = 0;                                                          \
+    out++;                                                                 \
+}
+#endif
+#endif
+
+#ifdef LZ_RGB24
+#define OUT_PIXEL rgb24_pixel_t
+#define FNAME(name) glz_rgb24_##name
+#define COPY_COMP_PIXEL(in, out) {  \
+    out->b = *(in++);               \
+    out->g = *(in++);               \
+    out->r = *(in++);               \
+    out++;                          \
+}
+#endif
+
+#ifdef LZ_RGB32
+#define OUT_PIXEL rgb32_pixel_t
+#define FNAME(name) glz_rgb32_##name
+#define COPY_COMP_PIXEL(in, out) {  \
+    out->b = *(in++);               \
+    out->g = *(in++);               \
+    out->r = *(in++);               \
+    out->pad = 0;                   \
+    out++;                          \
+}
+#endif
+
+#ifdef LZ_RGB_ALPHA
+#define OUT_PIXEL rgb32_pixel_t
+#define FNAME(name) glz_rgb_alpha_##name
+#define COPY_PIXEL(p, out) {out->pad = p.pad; out++;}
+#define COPY_REF_PIXEL(ref, out) {out->pad = ref->pad; out++; ref++;}
+#define COPY_COMP_PIXEL(in, out) {out->pad = *(in++); out++;}
+#endif
+
+// TODO: separate into routines that decode to dist,len. and to a routine that
+// actually copies the data.
+
+/* returns num of bytes read from in buf.
+   size should be in PIXEL */
+static size_t FNAME(decode)(SpiceGlzDecoderWindow *window,
+                            uint8_t* in_buf, uint8_t *out_buf, int size,
+                            uint64_t image_id, SpicePalette *plt)
+{
+    uint8_t      *ip = in_buf;
+    OUT_PIXEL    *out_pix_buf = (OUT_PIXEL *)out_buf;
+    OUT_PIXEL    *op = out_pix_buf;
+    OUT_PIXEL    *op_limit = out_pix_buf + size;
+
+    uint32_t ctrl = *(ip++);
+    int loop = true;
+
+    do {
+        if (ctrl >= MAX_COPY) { // reference (dictionary/RLE)
+            OUT_PIXEL *ref = op;
+            uint32_t len = ctrl >> 5;
+            uint8_t pixel_flag = (ctrl >> 4) & 0x01;
+            uint32_t pixel_ofs = (ctrl & 0x0f);
+            uint8_t image_flag;
+            uint32_t image_dist;
+
+            /* retrieving the referenced images, the offset of the first pixel,
+               and the match length */
+
+            uint8_t code;
+            //len--; // TODO: why do we do this?
+
+            if (len == 7) { // match length is bigger than 7
+                do {
+                    code = *(ip++);
+                    len += code;
+                } while (code == 255); // remaining of len
+            }
+            code = *(ip++);
+            pixel_ofs += (code << 4);
+
+            code = *(ip++);
+            image_flag = (code >> 6) & 0x03;
+            if (!pixel_flag) { // short pixel offset
+                int i;
+                image_dist = code & 0x3f;
+                for (i = 0; i < image_flag; i++) {
+                    code = *(ip++);
+                    image_dist += (code << (6 + (8 * i)));
+                }
+            } else {
+                int i;
+                pixel_flag = (code >> 5) & 0x01;
+                pixel_ofs += (code & 0x1f) << 12;
+                image_dist = 0;
+                for (i = 0; i < image_flag; i++) {
+                    code = *(ip++);
+                    image_dist += (code << 8 * i);
+                }
+
+
+                if (pixel_flag) { // very long pixel offset
+                    code = *(ip++);
+                    pixel_ofs += code << 17;
+                }
+            }
+
+#if defined(LZ_PLT) || defined(LZ_RGB_ALPHA)
+            len += 2; // length is biased by 2 (fixing bias)
+#elif defined(LZ_RGB16)
+            len += 1; // length is biased by 1  (fixing bias)
+#endif
+            if (!image_dist) {
+                pixel_ofs += 1; // offset is biased by 1 (fixing bias)
+            }
+
+#if defined(TO_RGB32)
+#if defined(PLT4_BE) || defined(PLT4_LE) || defined(PLT1_BE) || defined(PLT1_LE)
+            pixel_ofs = CAST_PLT_DISTANCE(pixel_ofs);
+            len = CAST_PLT_DISTANCE(len);
+#endif
+#endif
+
+            if (!image_dist) { // reference is inside the same image
+                ref -= pixel_ofs;
+                g_return_val_if_fail(ref + len <= op_limit, 0);
+                g_return_val_if_fail(ref >= out_pix_buf, 0);
+            } else {
+                ref = glz_decoder_window_bits(window, image_id,
+                                              image_dist, pixel_ofs);
+            }
+
+            g_return_val_if_fail(ref != NULL, 0);
+            g_return_val_if_fail(op + len <= op_limit, 0);
+
+            /* copying the match*/
+
+            if (ref == (op - 1)) { // run (this will never be called in PLT4/1_TO_RGB because the
+                                  // number of pixel copied is larger then one...
+                /* optimize copy for a run */
+                OUT_PIXEL b = *ref;
+                for (; len; --len) {
+                    COPY_PIXEL(b, op);
+                    g_return_val_if_fail(op <= op_limit, 0);
+                }
+            } else {
+                for (; len; --len) {
+                    COPY_REF_PIXEL(ref, op);
+                    g_return_val_if_fail(op <= op_limit, 0);
+                }
+            }
+        } else { // copy
+            ctrl++; // copy count is biased by 1
+#if defined(TO_RGB32) && (defined(PLT4_BE) || defined(PLT4_LE) || defined(PLT1_BE) || \
+                                                                                   defined(PLT1_LE))
+            g_return_val_if_fail(op + CAST_PLT_DISTANCE(ctrl) <= op_limit, 0);
+#else
+            g_return_val_if_fail(op + ctrl <= op_limit, 0);
+#endif
+
+#if defined(TO_RGB32) && defined(LZ_PLT)
+            g_return_val_if_fail(plt, 0);
+            COPY_COMP_PIXEL(ip, op, plt);
+#else
+            COPY_COMP_PIXEL(ip, op);
+#endif
+            g_return_val_if_fail(op <= op_limit, 0);
+
+            for (--ctrl; ctrl; ctrl--) {
+#if defined(TO_RGB32) && defined(LZ_PLT)
+                g_return_val_if_fail(plt, 0);
+                COPY_COMP_PIXEL(ip, op, plt);
+#else
+                COPY_COMP_PIXEL(ip, op);
+#endif
+                g_return_val_if_fail(op <= op_limit, 0);
+            }
+        } // END REF/COPY
+
+        if (LZ_EXPECT_CONDITIONAL(op < op_limit)) {
+            ctrl = *(ip++);
+        } else {
+            loop = false;
+        }
+    } while (LZ_EXPECT_CONDITIONAL(loop));
+
+    return (ip - in_buf);
+}
+#undef LZ_PLT
+#undef PLT8
+#undef PLT4_BE
+#undef PLT4_LE
+#undef PLT1_BE
+#undef PLT1_LE
+#undef LZ_RGB16
+#undef LZ_RGB24
+#undef LZ_RGB32
+#undef LZ_RGB_ALPHA
+#undef TO_RGB32
+#undef OUT_PIXEL
+#undef FNAME
+#undef COPY_PIXEL
+#undef COPY_REF_PIXEL
+#undef COPY_COMP_PIXEL
+#undef COPY_PLT_ENTRY
+#undef CAST_PLT_DISTANCE
diff --git a/src/decode-glz.c b/src/decode-glz.c
new file mode 100644
index 0000000..34a7185
--- /dev/null
+++ b/src/decode-glz.c
@@ -0,0 +1,475 @@
+/* -*- 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 <stdio.h>
+#include <stdbool.h>
+#include <inttypes.h>
+
+#include <glib.h>
+
+#include "gio-coroutine.h"
+#include "spice-util.h"
+#include "decode.h"
+
+#include "common/canvas_utils.h"
+
+struct glz_image_hdr {
+    uint64_t                id;
+    LzImageType             type;
+    uint32_t                width;
+    uint32_t                height;
+    uint32_t                gross_pixels;
+    bool                    top_down;
+    uint32_t                win_head_dist;
+};
+
+struct glz_image {
+    struct glz_image_hdr    hdr;
+    pixman_image_t          *surface;
+    uint8_t                 *data;
+};
+
+static struct glz_image *glz_image_new(struct glz_image_hdr *hdr,
+                                       int type, void *opaque)
+{
+    struct glz_image *img;
+
+    g_return_val_if_fail(type == LZ_IMAGE_TYPE_RGB32 || type == LZ_IMAGE_TYPE_RGBA, NULL);
+
+    img = g_new0(struct glz_image, 1);
+    img->hdr = *hdr;
+    img->surface = alloc_lz_image_surface
+        (opaque, type == LZ_IMAGE_TYPE_RGBA ? PIXMAN_a8r8g8b8 : PIXMAN_x8r8g8b8,
+         img->hdr.width, img->hdr.height, img->hdr.gross_pixels, img->hdr.top_down);
+    pixman_image_ref(img->surface);
+    img->data = (uint8_t *)pixman_image_get_data(img->surface);
+    if (!img->hdr.top_down) {
+        img->data = img->data - img->hdr.width * (img->hdr.height - 1) * 4;
+    }
+    return img;
+}
+
+static void glz_image_destroy(struct glz_image *img)
+{
+    if (img == NULL)
+        return;
+
+    pixman_image_unref(img->surface);
+    free(img);
+}
+
+/* ------------------------------------------------------------------ */
+
+#define INIT_IMAGES_CAPACITY 100
+#define WIN_OVERFLOW_FACTOR 1.5
+#define WIN_REALLOC_FACTOR 1.5
+
+struct SpiceGlzDecoderWindow {
+    struct glz_image        **images;
+    uint32_t                nimages;
+    uint64_t                oldest;
+    uint64_t                tail_gap;
+};
+
+static void glz_decoder_window_resize(SpiceGlzDecoderWindow *w)
+{
+    struct glz_image  **new_images;
+    int i, new_slot;
+
+    SPICE_DEBUG("%s: array resize %d -> %d", __FUNCTION__,
+                w->nimages, w->nimages * 2);
+    new_images = g_new0(struct glz_image*, w->nimages * 2);
+    for (i = 0; i < w->nimages; i++) {
+        if (w->images[i] == NULL) {
+            /*
+             * We can have empty slots when images come in out of order, this
+             * can happen when a vm has multiple displays, since each display
+             * uses its own socket there is no guarantee that images
+             * originating from different displays are received in id order.
+             */
+            continue;
+        }
+        new_slot = w->images[i]->hdr.id % (w->nimages * 2);
+        new_images[new_slot] = w->images[i];
+    }
+    free(w->images);
+    w->images = new_images;
+    w->nimages *= 2;
+}
+
+static void glz_decoder_window_add(SpiceGlzDecoderWindow *w,
+                                   struct glz_image *img)
+{
+    int slot = img->hdr.id % w->nimages;
+
+    if (w->images[slot]) {
+        /* need more space */
+        glz_decoder_window_resize(w);
+        slot = img->hdr.id % w->nimages;
+    }
+
+    w->images[slot] = img;
+
+    /* close the gap */
+    while (w->tail_gap <= img->hdr.id && w->images[w->tail_gap % w->nimages] != NULL)
+        w->tail_gap++;
+}
+
+struct wait_for_image_data {
+    SpiceGlzDecoderWindow     *window;
+    uint64_t                   id;
+};
+
+static gboolean wait_for_image(gpointer data)
+{
+    struct wait_for_image_data *wait = data;
+    int slot = wait->id % wait->window->nimages;
+    struct glz_image *image = wait->window->images[slot];
+    gboolean ready = image && image->hdr.id == wait->id;
+
+    return ready;
+}
+
+static void *glz_decoder_window_bits(SpiceGlzDecoderWindow *w, uint64_t id,
+                                     uint32_t dist, uint32_t offset)
+{
+    struct wait_for_image_data data = {
+        .window = w,
+        .id = id - dist,
+    };
+
+    if (!g_coroutine_condition_wait(g_coroutine_self(), wait_for_image, &data))
+        SPICE_DEBUG("wait for image cancelled");
+
+    int slot = (id - dist) % w->nimages;
+
+    g_return_val_if_fail(w->images[slot] != NULL, NULL);
+    g_return_val_if_fail(w->images[slot]->hdr.id == id - dist, NULL);
+    g_return_val_if_fail(w->images[slot]->hdr.gross_pixels >= offset, NULL);
+
+    return w->images[slot]->data + offset * 4;
+}
+
+static void glz_decoder_window_release(SpiceGlzDecoderWindow *w,
+                                       uint64_t oldest)
+{
+    int slot;
+
+    while (w->oldest < oldest) {
+        slot = w->oldest % w->nimages;
+        glz_image_destroy(w->images[slot]);
+        w->images[slot] = NULL;
+        w->oldest++;
+    }
+}
+
+/* ------------------------------------------------------------------ */
+
+typedef struct GlibGlzDecoder {
+    SpiceGlzDecoder         base;
+    uint8_t                 *in_start;
+    uint8_t                 *in_now;
+    SpiceGlzDecoderWindow   *window;
+    struct glz_image_hdr    image;
+} GlibGlzDecoder;
+
+/*
+ * Give hints to the compiler for branch prediction optimization.
+ */
+#if defined(__GNUC__) && (__GNUC__ > 2)
+#define LZ_EXPECT_CONDITIONAL(c) (__builtin_expect((c), 1))
+#define LZ_UNEXPECT_CONDITIONAL(c) (__builtin_expect((c), 0))
+#else
+#define LZ_EXPECT_CONDITIONAL(c) (c)
+#define LZ_UNEXPECT_CONDITIONAL(c) (c)
+#endif
+
+
+#ifdef __GNUC__
+#define ATTR_PACKED __attribute__ ((__packed__))
+#else
+#define ATTR_PACKED
+#pragma pack(push)
+#pragma pack(1)
+#endif
+
+/*
+ * the palette images will be treated as one byte pixels. Their width
+ * should be transformed accordingly.
+ */
+typedef struct ATTR_PACKED one_byte_pixel_t {
+    uint8_t a;
+} one_byte_pixel_t;
+
+typedef struct ATTR_PACKED rgb32_pixel_t {
+    uint8_t b;
+    uint8_t g;
+    uint8_t r;
+    uint8_t pad;
+} rgb32_pixel_t;
+
+typedef struct ATTR_PACKED rgb24_pixel_t {
+    uint8_t b;
+    uint8_t g;
+    uint8_t r;
+} rgb24_pixel_t;
+
+typedef uint16_t rgb16_pixel_t;
+
+#ifndef __GNUC__
+#pragma pack(pop)
+#endif
+
+#undef ATTR_PACKED
+
+#define LZ_PLT
+#include "decode-glz-tmpl.c"
+
+#define LZ_PLT
+#define PLT8
+#define TO_RGB32
+#include "decode-glz-tmpl.c"
+
+#define LZ_PLT
+#define PLT4_BE
+#define TO_RGB32
+#include "decode-glz-tmpl.c"
+
+#define LZ_PLT
+#define PLT4_LE
+#define TO_RGB32
+#include "decode-glz-tmpl.c"
+
+#define LZ_PLT
+#define PLT1_BE
+#define TO_RGB32
+#include "decode-glz-tmpl.c"
+
+#define LZ_PLT
+#define PLT1_LE
+#define TO_RGB32
+#include "decode-glz-tmpl.c"
+
+
+#define LZ_RGB16
+#include "decode-glz-tmpl.c"
+#define LZ_RGB16
+#define TO_RGB32
+#include "decode-glz-tmpl.c"
+
+#define LZ_RGB24
+#include "decode-glz-tmpl.c"
+
+#define LZ_RGB32
+#include "decode-glz-tmpl.c"
+
+#define LZ_RGB_ALPHA
+#include "decode-glz-tmpl.c"
+
+#undef LZ_UNEXPECT_CONDITIONAL
+#undef LZ_EXPECT_CONDITIONAL
+
+typedef size_t (*decode_function)(SpiceGlzDecoderWindow *window,
+                                  uint8_t* in_buf, uint8_t *out_buf, int size,
+                                  uint64_t id, SpicePalette *plt);
+
+// ordered according to LZ_IMAGE_TYPE
+const decode_function DECODE_TO_RGB32[] = {
+    NULL,
+    glz_plt1_le_to_rgb32_decode,
+    glz_plt1_be_to_rgb32_decode,
+    glz_plt4_le_to_rgb32_decode,
+    glz_plt4_be_to_rgb32_decode,
+    glz_plt8_to_rgb32_decode,
+    glz_rgb16_to_rgb32_decode,
+    glz_rgb32_decode,
+    glz_rgb32_decode,
+    glz_rgb32_decode
+};
+
+const decode_function DECODE_TO_SAME[] = {
+    NULL,
+    glz_plt_decode,
+    glz_plt_decode,
+    glz_plt_decode,
+    glz_plt_decode,
+    glz_plt_decode,
+    glz_rgb16_decode,
+    glz_rgb24_decode,
+    glz_rgb32_decode,
+    glz_rgb32_decode
+};
+
+static uint32_t decode_32(GlibGlzDecoder *d)
+{
+    uint32_t word = 0;
+    word |= *(d->in_now++);
+    word <<= 8;
+    word |= *(d->in_now++);
+    word <<= 8;
+    word |= *(d->in_now++);
+    word <<= 8;
+    word |= *(d->in_now++);
+    return word;
+}
+
+static uint64_t decode_64(GlibGlzDecoder *d)
+{
+    uint64_t long_word = decode_32(d);
+    long_word <<= 32;
+    long_word |= decode_32(d);
+    return long_word;
+}
+
+static void decode_header(GlibGlzDecoder *d)
+{
+    uint32_t magic;
+    uint32_t version;
+    uint32_t stride;
+    uint8_t tmp;
+
+    magic = decode_32(d);
+    g_return_if_fail(magic == LZ_MAGIC);
+
+    version = decode_32(d);
+    g_return_if_fail(version == LZ_VERSION);
+
+    tmp = *(d->in_now++);
+
+    d->image.type = (LzImageType)(tmp & LZ_IMAGE_TYPE_MASK);
+    d->image.top_down = (tmp >> LZ_IMAGE_TYPE_LOG) ? true : false;
+    d->image.width = decode_32(d);
+    d->image.height = decode_32(d);
+    stride = decode_32(d);
+
+    if (IS_IMAGE_TYPE_PLT[d->image.type]) {
+        d->image.gross_pixels = stride * PLT_PIXELS_PER_BYTE[d->image.type]
+            * d->image.height;
+    } else {
+        d->image.gross_pixels = d->image.width * d->image.height;
+    }
+
+    d->image.id = decode_64(d);
+    d->image.win_head_dist = decode_32(d);
+
+    SPICE_DEBUG("%s: %dx%d, id %" PRId64 ", ref %" PRId64,
+            __FUNCTION__,
+            d->image.width, d->image.height, d->image.id,
+            d->image.id - d->image.win_head_dist);
+}
+
+static void decode(SpiceGlzDecoder *decoder,
+                   uint8_t *data, SpicePalette *palette,
+                   void *usr_data)
+{
+    GlibGlzDecoder *d = SPICE_CONTAINEROF(decoder, GlibGlzDecoder, base);
+    LzImageType decoded_type;
+    struct glz_image *decoded_image;
+    size_t n_in_bytes_decoded;
+
+    d->in_start = data;
+    d->in_now = data;
+
+    decode_header(d);
+
+    if (d->image.type == LZ_IMAGE_TYPE_RGBA) {
+        decoded_type = LZ_IMAGE_TYPE_RGBA;
+    } else {
+        decoded_type = LZ_IMAGE_TYPE_RGB32;
+    }
+
+    decoded_image = glz_image_new(&d->image, decoded_type, usr_data);
+
+    n_in_bytes_decoded = DECODE_TO_RGB32[d->image.type]
+        (d->window, d->in_now, decoded_image->data,
+         d->image.gross_pixels, d->image.id, palette);
+
+    d->in_now += n_in_bytes_decoded;
+
+    if (d->image.type == LZ_IMAGE_TYPE_RGBA) {
+        glz_rgb_alpha_decode(d->window, d->in_now, decoded_image->data,
+                             d->image.gross_pixels, d->image.id, palette);
+    }
+
+    glz_decoder_window_add(d->window, decoded_image);
+
+    { /* release old images from last tail_gap, only if the gap is closed  */
+        uint64_t oldest;
+        struct glz_image *image = d->window->images[(d->window->tail_gap - 1) % d->window->nimages];
+
+        g_return_if_fail(image != NULL);
+
+        oldest = image->hdr.id - image->hdr.win_head_dist;
+        glz_decoder_window_release(d->window, oldest);
+    }
+}
+
+/* ------------------------------------------------------------------ */
+
+static SpiceGlzDecoderOps glz_decoder_ops = {
+    .decode = decode,
+};
+
+void glz_decoder_window_clear(SpiceGlzDecoderWindow *w)
+{
+    int i;
+
+    g_return_if_fail(w->nimages == 0 || w->images != NULL);
+
+    for (i = 0; i < w->nimages; i++) {
+        if (w->images[i]) {
+            glz_image_destroy(w->images[i]);
+        }
+    }
+
+    w->nimages = 16;
+    g_free(w->images);
+    w->images = g_new0(struct glz_image*, w->nimages);
+    w->tail_gap = 0;
+}
+
+SpiceGlzDecoderWindow *glz_decoder_window_new(void)
+{
+    SpiceGlzDecoderWindow *w = g_new0(SpiceGlzDecoderWindow, 1);
+    glz_decoder_window_clear(w);
+    return w;
+}
+
+void glz_decoder_window_destroy(SpiceGlzDecoderWindow *w)
+{
+    if (w == NULL)
+        return;
+
+    glz_decoder_window_clear(w);
+    free(w->images);
+    free(w);
+}
+
+SpiceGlzDecoder *glz_decoder_new(SpiceGlzDecoderWindow *w)
+{
+    GlibGlzDecoder *d = g_new0(GlibGlzDecoder, 1);
+    d->base.ops = &glz_decoder_ops;
+    d->window = w;
+    return &d->base;
+}
+
+void glz_decoder_destroy(SpiceGlzDecoder *d)
+{
+    free(d);
+}
diff --git a/src/decode-jpeg.c b/src/decode-jpeg.c
new file mode 100644
index 0000000..697d0de
--- /dev/null
+++ b/src/decode-jpeg.c
@@ -0,0 +1,191 @@
+/* -*- 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 "decode.h"
+
+#ifdef G_OS_WIN32
+/* We need some hacks to avoid warnings from the jpeg headers, ex: */
+/* #define HAVE_BOOLEAN */
+#define XMD_H
+/* #undef FAR */
+/* but they are not compatible: uchar vs int........!@@(#$$??!@! */
+/* fix this with UGLY HACK! */
+/* #define boolean spice_jpeg_boolean */
+/* #define INT32 spice_jpeg_int32 */
+#endif
+
+#include <stdio.h>
+#include <jpeglib.h>
+
+typedef struct GlibJpegDecoder
+{
+    SpiceJpegDecoder              base;
+    struct jpeg_decompress_struct _cinfo;
+    struct jpeg_error_mgr         _jerr;
+    struct jpeg_source_mgr        _jsrc;
+
+    uint8_t* _data;
+    int      _data_size;
+    int      _width;
+    int      _height;
+} GlibJpegDecoder;
+
+static void begin_decode(SpiceJpegDecoder *decoder,
+                         uint8_t* data, int data_size,
+                         int* out_width, int* out_height)
+{
+    GlibJpegDecoder *d = SPICE_CONTAINEROF(decoder, GlibJpegDecoder, base);
+
+    g_return_if_fail(data != NULL);
+    g_return_if_fail(data_size != 0);
+
+    if (d->_data)
+        jpeg_abort_decompress(&d->_cinfo);
+
+    d->_data = data;
+    d->_data_size = data_size;
+
+    d->_cinfo.src->next_input_byte = d->_data;
+    d->_cinfo.src->bytes_in_buffer = d->_data_size;
+
+    jpeg_read_header(&d->_cinfo, TRUE);
+
+    d->_cinfo.out_color_space = JCS_RGB;
+    d->_width = d->_cinfo.image_width;
+    d->_height = d->_cinfo.image_height;
+
+    *out_width = d->_width;
+    *out_height = d->_height;
+}
+
+/* TODO: move it elsewhere and reuse it in get_pixbuf(), optimize? */
+typedef void (*converter_rgb_t)(uint8_t* src, uint8_t* dest, int width);
+
+static void convert_rgb_to_bgr(uint8_t* src, uint8_t* dest, int width)
+{
+    int x;
+
+    for (x = 0; x < width; x++) {
+        *dest++ = src[2];
+        *dest++ = src[1];
+        *dest++ = src[0];
+        src += 3;
+    }
+}
+
+static void convert_rgb_to_bgrx(uint8_t* src, uint8_t* dest, int width)
+{
+    int x;
+
+    for (x = 0; x < width; x++) {
+        *dest++ = src[2];
+        *dest++ = src[1];
+        *dest++ = src[0];
+        *dest++ = 0;
+        src += 3;
+    }
+}
+
+static void decode(SpiceJpegDecoder *decoder,
+                   uint8_t* dest, int stride, int format)
+{
+    GlibJpegDecoder *d = SPICE_CONTAINEROF(decoder, GlibJpegDecoder, base);
+    uint8_t* scan_line = g_alloca(d->_width * 3);
+    converter_rgb_t converter = NULL;
+    int row;
+
+    switch (format) {
+    case SPICE_BITMAP_FMT_24BIT:
+        converter = convert_rgb_to_bgr;
+        break;
+    case SPICE_BITMAP_FMT_32BIT:
+        converter = convert_rgb_to_bgrx;
+        break;
+    default:
+        g_warning("bad bitmap format, %d", format);
+        return;
+    }
+
+    g_return_if_fail(converter != NULL);
+
+    jpeg_start_decompress(&d->_cinfo);
+
+    for (row = 0; row < d->_height; row++) {
+        jpeg_read_scanlines(&d->_cinfo, &scan_line, 1);
+        converter(scan_line, dest, d->_width);
+        dest += stride;
+    }
+
+    jpeg_finish_decompress(&d->_cinfo);
+}
+
+static SpiceJpegDecoderOps jpeg_decoder_ops = {
+    .begin_decode = begin_decode,
+    .decode = decode,
+};
+
+static void jpeg_decoder_init_source(j_decompress_ptr cinfo)
+{
+}
+
+static boolean jpeg_decoder_fill_input_buffer(j_decompress_ptr cinfo)
+{
+    g_warning("no more data for jpeg");
+    return FALSE;
+}
+
+static void jpeg_decoder_skip_input_data(j_decompress_ptr cinfo, long num_bytes)
+{
+    g_return_if_fail(num_bytes < (long)cinfo->src->bytes_in_buffer);
+
+    cinfo->src->next_input_byte += num_bytes;
+    cinfo->src->bytes_in_buffer -= num_bytes;
+}
+
+static void jpeg_decoder_term_source (j_decompress_ptr cinfo)
+{
+    return;
+}
+
+SpiceJpegDecoder *jpeg_decoder_new(void)
+{
+    GlibJpegDecoder *d = g_new0(GlibJpegDecoder, 1);
+
+    d->_cinfo.err = jpeg_std_error(&d->_jerr);
+    jpeg_create_decompress(&d->_cinfo);
+
+    d->_cinfo.src = &d->_jsrc;
+    d->_cinfo.src->init_source = jpeg_decoder_init_source;
+    d->_cinfo.src->fill_input_buffer = jpeg_decoder_fill_input_buffer;
+    d->_cinfo.src->skip_input_data = jpeg_decoder_skip_input_data;
+    d->_cinfo.src->resync_to_restart = jpeg_resync_to_restart;
+    d->_cinfo.src->term_source = jpeg_decoder_term_source;
+
+    d->base.ops = &jpeg_decoder_ops;
+
+    return &d->base;
+}
+
+void jpeg_decoder_destroy(SpiceJpegDecoder *decoder)
+{
+    GlibJpegDecoder *d = SPICE_CONTAINEROF(decoder, GlibJpegDecoder, base);
+
+    jpeg_destroy_decompress(&d->_cinfo);
+    free(d);
+}
diff --git a/src/decode-zlib.c b/src/decode-zlib.c
new file mode 100644
index 0000000..a5325c0
--- /dev/null
+++ b/src/decode-zlib.c
@@ -0,0 +1,89 @@
+/* -*- 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 "decode.h"
+
+#ifndef __GNUC__
+#define ZLIB_WINAPI
+#endif
+
+#include <zlib.h>
+
+typedef struct GlibZlibDecoder
+{
+    SpiceZlibDecoder         base;
+    z_stream                 _z_strm;
+} GlibZlibDecoder;
+
+static void decode(SpiceZlibDecoder *decoder,
+                   uint8_t *data, int data_size,
+                   uint8_t *dest, int dest_size)
+{
+    GlibZlibDecoder *d = SPICE_CONTAINEROF(decoder, GlibZlibDecoder, base);
+    int z_ret;
+
+    inflateReset(&d->_z_strm);
+    d->_z_strm.next_in = data;
+    d->_z_strm.avail_in = data_size;
+    d->_z_strm.next_out = dest;
+    d->_z_strm.avail_out = dest_size;
+
+    z_ret = inflate(&d->_z_strm, Z_FINISH);
+
+    if (z_ret != Z_STREAM_END) {
+        g_warning("zlib inflate failed, error %d", z_ret);
+    }
+}
+
+static SpiceZlibDecoderOps zlib_decoder_ops = {
+    .decode = decode,
+};
+
+SpiceZlibDecoder *zlib_decoder_new(void)
+{
+    GlibZlibDecoder *d = g_new0(GlibZlibDecoder, 1);
+    int z_ret;
+
+    d->_z_strm.zalloc = Z_NULL;
+    d->_z_strm.zfree = Z_NULL;
+    d->_z_strm.opaque = Z_NULL;
+    d->_z_strm.next_in = Z_NULL;
+    d->_z_strm.avail_in = 0;
+    z_ret = inflateInit(&d->_z_strm);
+    if (z_ret != Z_OK) {
+        g_warning("zlib decoder init failed, error %d", z_ret);
+        goto fail;
+    }
+
+    d->base.ops = &zlib_decoder_ops;
+
+    return &d->base;
+
+fail:
+    free(d);
+    return NULL;
+}
+
+void zlib_decoder_destroy(SpiceZlibDecoder *decoder)
+{
+    GlibZlibDecoder *d = SPICE_CONTAINEROF(decoder, GlibZlibDecoder, base);
+
+    inflateEnd(&d->_z_strm);
+    free(d);
+}
diff --git a/src/decode.h b/src/decode.h
new file mode 100644
index 0000000..b274d67
--- /dev/null
+++ b/src/decode.h
@@ -0,0 +1,44 @@
+/* -*- 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 SPICEGTK_DECODE_H_
+# define SPICEGTK_DECODE_H_
+
+#include <glib.h>
+
+#include "client_sw_canvas.h"
+
+G_BEGIN_DECLS
+
+typedef struct SpiceGlzDecoderWindow SpiceGlzDecoderWindow;
+
+SpiceGlzDecoderWindow *glz_decoder_window_new(void);
+void glz_decoder_window_clear(SpiceGlzDecoderWindow *w);
+void glz_decoder_window_destroy(SpiceGlzDecoderWindow *w);
+
+SpiceGlzDecoder *glz_decoder_new(SpiceGlzDecoderWindow *w);
+void glz_decoder_destroy(SpiceGlzDecoder *d);
+
+SpiceZlibDecoder *zlib_decoder_new(void);
+void zlib_decoder_destroy(SpiceZlibDecoder *d);
+
+SpiceJpegDecoder *jpeg_decoder_new(void);
+void jpeg_decoder_destroy(SpiceJpegDecoder *d);
+
+G_END_DECLS
+
+#endif // SPICEGTK_DECODE_H_
diff --git a/src/desktop-integration.c b/src/desktop-integration.c
new file mode 100644
index 0000000..5868d48
--- /dev/null
+++ b/src/desktop-integration.c
@@ -0,0 +1,223 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2012 Red Hat, Inc.
+
+   Red Hat Authors:
+   Hans de Goede <hdegoede at redhat.com>
+
+   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-object.h>
+
+#include "glib-compat.h"
+#include "spice-session-priv.h"
+#include "desktop-integration.h"
+
+#include <glib/gi18n.h>
+
+#define GNOME_SESSION_INHIBIT_AUTOMOUNT 16
+
+/* ------------------------------------------------------------------ */
+/* gobject glue                                                       */
+
+#define SPICE_DESKTOP_INTEGRATION_GET_PRIVATE(obj)                                  \
+    (G_TYPE_INSTANCE_GET_PRIVATE ((obj), SPICE_TYPE_DESKTOP_INTEGRATION, SpiceDesktopIntegrationPrivate))
+
+struct _SpiceDesktopIntegrationPrivate {
+#if defined(USE_GDBUS)
+    GDBusProxy *gnome_session_proxy;
+#else
+    GObject *gnome_session_proxy; /* dummy */
+#endif
+    guint gnome_automount_inhibit_cookie;
+};
+
+G_DEFINE_TYPE(SpiceDesktopIntegration, spice_desktop_integration, G_TYPE_OBJECT);
+
+/* ------------------------------------------------------------------ */
+/* Gnome specific code                                                */
+
+static void handle_dbus_call_error(const char *call, GError **_error)
+{
+    GError *error = *_error;
+    const char *message = error->message;
+
+    g_warning("Error calling '%s': %s", call, message);
+    g_clear_error(_error);
+}
+
+static gboolean gnome_integration_init(SpiceDesktopIntegration *self)
+{
+    G_GNUC_UNUSED SpiceDesktopIntegrationPrivate *priv = self->priv;
+    GError *error = NULL;
+    gboolean success = TRUE;
+
+#if defined(USE_GDBUS)
+    gchar *name_owner = NULL;
+    priv->gnome_session_proxy =
+        g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SESSION,
+                                      G_DBUS_PROXY_FLAGS_NONE,
+                                      NULL,
+                                      "org.gnome.SessionManager",
+                                      "/org/gnome/SessionManager",
+                                      "org.gnome.SessionManager",
+                                      NULL,
+                                      &error);
+    if (!error &&
+        (name_owner = g_dbus_proxy_get_name_owner(priv->gnome_session_proxy)) == NULL) {
+        g_clear_object(&priv->gnome_session_proxy);
+        success = FALSE;
+    }
+    g_free(name_owner);
+#else
+    success = FALSE;
+#endif
+
+    if (error) {
+        g_warning("Could not create org.gnome.SessionManager dbus proxy: %s",
+                  error->message);
+        g_clear_error(&error);
+        return FALSE;
+    }
+
+    return success;
+}
+
+static void gnome_integration_inhibit_automount(SpiceDesktopIntegration *self)
+{
+    SpiceDesktopIntegrationPrivate *priv = self->priv;
+    GError *error = NULL;
+    G_GNUC_UNUSED const gchar *reason =
+        _("Automounting has been inhibited for USB auto-redirecting");
+
+    if (!priv->gnome_session_proxy)
+        return;
+
+    g_return_if_fail(priv->gnome_automount_inhibit_cookie == 0);
+
+#if defined(USE_GDBUS)
+    GVariant *v = g_dbus_proxy_call_sync(priv->gnome_session_proxy,
+                "Inhibit",
+                g_variant_new("(susu)",
+                              g_get_prgname(),
+                              0,
+                              reason,
+                              GNOME_SESSION_INHIBIT_AUTOMOUNT),
+                G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
+    if (v)
+        g_variant_get(v, "(u)", &priv->gnome_automount_inhibit_cookie);
+
+    g_clear_pointer(&v, g_variant_unref);
+#endif
+    if (error)
+        handle_dbus_call_error("org.gnome.SessionManager.Inhibit", &error);
+}
+
+static void gnome_integration_uninhibit_automount(SpiceDesktopIntegration *self)
+{
+    SpiceDesktopIntegrationPrivate *priv = self->priv;
+    GError *error = NULL;
+
+    if (!priv->gnome_session_proxy)
+        return;
+
+    /* Cookie is 0 when we failed to inhibit (and when called from dispose) */
+    if (priv->gnome_automount_inhibit_cookie == 0)
+        return;
+
+#if defined(USE_GDBUS)
+    GVariant *v = g_dbus_proxy_call_sync(priv->gnome_session_proxy,
+                "Uninhibit",
+                g_variant_new("(u)",
+                              priv->gnome_automount_inhibit_cookie),
+                G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
+    g_clear_pointer(&v, g_variant_unref);
+#endif
+    if (error)
+        handle_dbus_call_error("org.gnome.SessionManager.Uninhibit", &error);
+
+    priv->gnome_automount_inhibit_cookie = 0;
+}
+
+static void gnome_integration_dispose(SpiceDesktopIntegration *self)
+{
+    SpiceDesktopIntegrationPrivate *priv = self->priv;
+
+    g_clear_object(&priv->gnome_session_proxy);
+}
+
+/* ------------------------------------------------------------------ */
+/* gobject glue                                                       */
+
+static void spice_desktop_integration_init(SpiceDesktopIntegration *self)
+{
+    SpiceDesktopIntegrationPrivate *priv;
+
+    priv = SPICE_DESKTOP_INTEGRATION_GET_PRIVATE(self);
+    self->priv = priv;
+
+    if (!gnome_integration_init(self))
+       g_warning("Warning no automount-inhibiting implementation available");
+}
+
+static void spice_desktop_integration_dispose(GObject *gobject)
+{
+    SpiceDesktopIntegration *self = SPICE_DESKTOP_INTEGRATION(gobject);
+
+    gnome_integration_dispose(self);
+
+    /* Chain up to the parent class */
+    if (G_OBJECT_CLASS(spice_desktop_integration_parent_class)->dispose)
+        G_OBJECT_CLASS(spice_desktop_integration_parent_class)->dispose(gobject);
+}
+
+static void spice_desktop_integration_class_init(SpiceDesktopIntegrationClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+    gobject_class->dispose      = spice_desktop_integration_dispose;
+
+    g_type_class_add_private(klass, sizeof(SpiceDesktopIntegrationPrivate));
+}
+
+SpiceDesktopIntegration *spice_desktop_integration_get(SpiceSession *session)
+{
+    SpiceDesktopIntegration *self;
+    static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
+
+    g_return_val_if_fail(session != NULL, NULL);
+
+    g_static_mutex_lock(&mutex);
+    self = g_object_get_data(G_OBJECT(session), "spice-desktop");
+    if (self == NULL) {
+        self = g_object_new(SPICE_TYPE_DESKTOP_INTEGRATION, NULL);
+        g_object_set_data_full(G_OBJECT(session), "spice-desktop", self, g_object_unref);
+    }
+    g_static_mutex_unlock(&mutex);
+
+    return self;
+}
+
+void spice_desktop_integration_inhibit_automount(SpiceDesktopIntegration *self)
+{
+    gnome_integration_inhibit_automount(self);
+}
+
+void spice_desktop_integration_uninhibit_automount(SpiceDesktopIntegration *self)
+{
+    gnome_integration_uninhibit_automount(self);
+}
diff --git a/src/desktop-integration.h b/src/desktop-integration.h
new file mode 100644
index 0000000..3716089
--- /dev/null
+++ b/src/desktop-integration.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2012 Red Hat, Inc.
+
+   Red Hat Authors:
+   Hans de Goede <hdegoede at redhat.com>
+
+   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_DESKTOP_INTEGRATION_H__
+#define __SPICE_DESKTOP_INTEGRATION_H__
+
+#include "spice-client.h"
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_DESKTOP_INTEGRATION            (spice_desktop_integration_get_type ())
+#define SPICE_DESKTOP_INTEGRATION(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), SPICE_TYPE_DESKTOP_INTEGRATION, SpiceDesktopIntegration))
+#define SPICE_DESKTOP_INTEGRATION_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SPICE_TYPE_DESKTOP_INTEGRATION, SpiceDesktopIntegrationClass))
+#define SPICE_IS_DESKTOP_INTEGRATION(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SPICE_TYPE_DESKTOP_INTEGRATION))
+#define SPICE_IS_DESKTOP_INTEGRATION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SPICE_TYPE_DESKTOP_INTEGRATION))
+#define SPICE_DESKTOP_INTEGRATION_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SPICE_TYPE_DESKTOP_INTEGRATION, SpiceDesktopIntegrationClass))
+
+typedef struct _SpiceDesktopIntegration SpiceDesktopIntegration;
+typedef struct _SpiceDesktopIntegrationClass SpiceDesktopIntegrationClass;
+typedef struct _SpiceDesktopIntegrationPrivate SpiceDesktopIntegrationPrivate;
+
+/*
+ * SpiceDesktopIntegration offers helper-functions to do desktop environment
+ * and/or platform specific tasks like disabling automount, disabling the
+ * screen-saver, etc. SpiceDesktopIntegration is for internal spice-gtk usage
+ * only!
+ */
+struct _SpiceDesktopIntegration
+{
+    GObject parent;
+
+    SpiceDesktopIntegrationPrivate *priv;
+};
+
+struct _SpiceDesktopIntegrationClass
+{
+    GObjectClass parent_class;
+};
+
+GType spice_desktop_integration_get_type(void);
+SpiceDesktopIntegration *spice_desktop_integration_get(SpiceSession *session);
+void spice_desktop_integration_inhibit_automount(SpiceDesktopIntegration *self);
+void spice_desktop_integration_uninhibit_automount(SpiceDesktopIntegration *self);
+
+G_END_DECLS
+
+#endif /* __SPICE_DESKTOP_INTEGRATION_H__ */
diff --git a/src/gio-coroutine.c b/src/gio-coroutine.c
new file mode 100644
index 0000000..c866e15
--- /dev/null
+++ b/src/gio-coroutine.c
@@ -0,0 +1,275 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2010 Red Hat, Inc.
+   Copyright (C) 2006 Anthony Liguori <anthony at codemonkey.ws>
+   Copyright (C) 2009-2010 Daniel P. Berrange <dan at berrange.com>
+
+   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.0 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, write to the Free Software
+   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
+*/
+#include "config.h"
+
+#include "gio-coroutine.h"
+
+typedef struct _GConditionWaitSource
+{
+    GCoroutine *self;
+    GSource src;
+    GConditionWaitFunc func;
+    gpointer data;
+} GConditionWaitSource;
+
+GCoroutine* g_coroutine_self(void)
+{
+    return (GCoroutine*)coroutine_self();
+}
+
+/* Main loop helper functions */
+static gboolean g_io_wait_helper(GSocket *sock G_GNUC_UNUSED,
+				 GIOCondition cond,
+				 gpointer data)
+{
+    struct coroutine *to = data;
+    coroutine_yieldto(to, &cond);
+    return FALSE;
+}
+
+GIOCondition g_coroutine_socket_wait(GCoroutine *self,
+                                     GSocket *sock,
+                                     GIOCondition cond)
+{
+    GIOCondition *ret, val = 0;
+    GSource *src;
+
+    g_return_val_if_fail(self != NULL, 0);
+    g_return_val_if_fail(self->wait_id == 0, 0);
+    g_return_val_if_fail(sock != NULL, 0);
+
+    src = g_socket_create_source(sock, cond | G_IO_HUP | G_IO_ERR | G_IO_NVAL, NULL);
+    g_source_set_callback(src, (GSourceFunc)g_io_wait_helper, self, NULL);
+    self->wait_id = g_source_attach(src, NULL);
+    ret = coroutine_yield(NULL);
+    g_source_unref(src);
+
+    if (ret != NULL)
+        val = *ret;
+    else
+        g_source_remove(self->wait_id);
+
+    self->wait_id = 0;
+    return val;
+}
+
+void g_coroutine_condition_cancel(GCoroutine *coroutine)
+{
+    g_return_if_fail(coroutine != NULL);
+
+    if (coroutine->condition_id == 0)
+        return;
+
+    g_source_remove(coroutine->condition_id);
+    coroutine->condition_id = 0;
+}
+
+void g_coroutine_wakeup(GCoroutine *coroutine)
+{
+    g_return_if_fail(coroutine != NULL);
+    g_return_if_fail(coroutine != g_coroutine_self());
+
+    if (coroutine->wait_id)
+        coroutine_yieldto(&coroutine->coroutine, NULL);
+}
+
+/*
+ * Call immediately before the main loop does an iteration. Returns
+ * true if the condition we're checking is ready for dispatch
+ */
+static gboolean g_condition_wait_prepare(GSource *src,
+					 int *timeout) {
+    GConditionWaitSource *vsrc = (GConditionWaitSource *)src;
+    *timeout = -1;
+    return vsrc->func(vsrc->data);
+}
+
+/*
+ * Call immediately after the main loop does an iteration. Returns
+ * true if the condition we're checking is ready for dispatch
+ */
+static gboolean g_condition_wait_check(GSource *src)
+{
+    GConditionWaitSource *vsrc = (GConditionWaitSource *)src;
+    return vsrc->func(vsrc->data);
+}
+
+static gboolean g_condition_wait_dispatch(GSource *src G_GNUC_UNUSED,
+					  GSourceFunc cb,
+					  gpointer data) {
+    return cb(data);
+}
+
+GSourceFuncs waitFuncs = {
+    .prepare = g_condition_wait_prepare,
+    .check = g_condition_wait_check,
+    .dispatch = g_condition_wait_dispatch,
+};
+
+static gboolean g_condition_wait_helper(gpointer data)
+{
+    GCoroutine *self = (GCoroutine *)data;
+    coroutine_yieldto(&self->coroutine, NULL);
+    return FALSE;
+}
+
+/*
+ * g_coroutine_condition_wait:
+ * @coroutine: the coroutine to wait on
+ * @func: the condition callback
+ * @data: the user data passed to @func callback
+ *
+ * This function will wait on caller coroutine until @func returns %TRUE.
+ *
+ * @func is called when entering the main loop from the main context (coroutine).
+ *
+ * The condition can be cancelled by calling g_coroutine_wakeup()
+ *
+ * Returns: %TRUE if condition reached, %FALSE if not and cancelled
+ */
+gboolean g_coroutine_condition_wait(GCoroutine *self, GConditionWaitFunc func, gpointer data)
+{
+    GSource *src;
+    GConditionWaitSource *vsrc;
+
+    g_return_val_if_fail(self != NULL, FALSE);
+    g_return_val_if_fail(self->condition_id == 0, FALSE);
+    g_return_val_if_fail(func != NULL, FALSE);
+
+    /* Short-circuit check in case we've got it ahead of time */
+    if (func(data))
+        return TRUE;
+
+    /*
+     * Don't have it, so yield to the main loop, checking the condition
+     * on each iteration of the main loop
+     */
+    src = g_source_new(&waitFuncs, sizeof(GConditionWaitSource));
+    vsrc = (GConditionWaitSource *)src;
+
+    vsrc->func = func;
+    vsrc->data = data;
+    vsrc->self = self;
+
+    self->condition_id = g_source_attach(src, NULL);
+    g_source_set_callback(src, g_condition_wait_helper, self, NULL);
+    coroutine_yield(NULL);
+    g_source_unref(src);
+
+    /* it got woked up / cancelled? */
+    if (self->condition_id == 0)
+        return func(data);
+
+    self->condition_id = 0;
+    return TRUE;
+}
+
+struct signal_data
+{
+    gpointer instance;
+    struct coroutine *caller;
+    guint signal_id;
+    GQuark detail;
+    const gchar *propname;
+    gboolean notified;
+    va_list var_args;
+};
+
+static gboolean emit_main_context(gpointer opaque)
+{
+    struct signal_data *signal = opaque;
+
+    g_signal_emit_valist(signal->instance, signal->signal_id,
+                         signal->detail, signal->var_args);
+    signal->notified = TRUE;
+
+    coroutine_yieldto(signal->caller, NULL);
+
+    return FALSE;
+}
+
+void
+g_coroutine_signal_emit(gpointer instance, guint signal_id,
+                        GQuark detail, ...)
+{
+    struct signal_data data = {
+        .instance = instance,
+        .signal_id = signal_id,
+        .detail = detail,
+        .caller = coroutine_self(),
+    };
+
+    va_start (data.var_args, detail);
+
+    if (coroutine_self_is_main()) {
+        g_signal_emit_valist(instance, signal_id, detail, data.var_args);
+    } else {
+        g_object_ref(instance);
+        g_idle_add(emit_main_context, &data);
+        coroutine_yield(NULL);
+        g_warn_if_fail(data.notified);
+        g_object_unref(instance);
+    }
+
+    va_end (data.var_args);
+}
+
+
+static gboolean notify_main_context(gpointer opaque)
+{
+    struct signal_data *signal = opaque;
+
+    g_object_notify(signal->instance, signal->propname);
+    signal->notified = TRUE;
+
+    coroutine_yieldto(signal->caller, NULL);
+
+    return FALSE;
+}
+
+/* coroutine -> main context */
+void g_coroutine_object_notify(GObject *object,
+                               const gchar *property_name)
+{
+    struct signal_data data;
+
+    if (coroutine_self_is_main()) {
+        g_object_notify(object, property_name);
+    } else {
+
+        data.instance = g_object_ref(object);
+        data.caller = coroutine_self();
+        data.propname = (gpointer)property_name;
+        data.notified = FALSE;
+
+        g_idle_add(notify_main_context, &data);
+
+        /* This switches to the system coroutine context, lets
+         * the idle function run to dispatch the signal, and
+         * finally returns once complete. ie this is synchronous
+         * from the POV of the coroutine despite there being
+         * an idle function involved
+         */
+        coroutine_yield(NULL);
+        g_warn_if_fail(data.notified);
+        g_object_unref(object);
+    }
+}
diff --git a/src/gio-coroutine.h b/src/gio-coroutine.h
new file mode 100644
index 0000000..b3a6d78
--- /dev/null
+++ b/src/gio-coroutine.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2010 Red Hat, Inc.
+   Copyright (C) 2006 Anthony Liguori <anthony at codemonkey.ws>
+   Copyright (C) 2009-2010 Daniel P. Berrange <dan at berrange.com>
+
+   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.0 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, write to the Free Software
+   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
+*/
+#ifndef __GIO_COROUTINE_H__
+#define __GIO_COROUTINE_H__
+
+#include <gio/gio.h>
+#include "coroutine.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GCoroutine GCoroutine;
+
+struct _GCoroutine
+{
+    struct coroutine coroutine;
+    guint wait_id;
+    guint condition_id;
+};
+
+/*
+ * A special GSource impl which allows us to wait on a certain
+ * condition to be satisfied. This is effectively a boolean test
+ * run on each iteration of the main loop. So whenever a file has
+ * new I/O, or a timer occurs, etc we'll do the check. This is
+ * pretty efficient compared to a normal GLib Idle func which has
+ * to busy wait on a timeout, since our condition is only checked
+ * when some other source's state changes
+ */
+typedef gboolean (*GConditionWaitFunc)(gpointer);
+
+typedef void (*GSignalEmitMainFunc)(GObject *object, int signum, gpointer params);
+
+GCoroutine*  g_coroutine_self           (void);
+void         g_coroutine_wakeup         (GCoroutine *coroutine);
+GIOCondition g_coroutine_socket_wait    (GCoroutine *coroutine,
+                                         GSocket *sock, GIOCondition cond);
+gboolean     g_coroutine_condition_wait (GCoroutine *coroutine,
+                                         GConditionWaitFunc func, gpointer data);
+void         g_coroutine_condition_cancel(GCoroutine *coroutine);
+
+void         g_coroutine_signal_emit (gpointer instance, guint signal_id,
+                                      GQuark detail, ...);
+
+void         g_coroutine_object_notify(GObject *object, const gchar *property_name);
+
+G_END_DECLS
+
+#endif /* __GIO_COROUTINE_H__ */
diff --git a/src/giopipe.c b/src/giopipe.c
new file mode 100644
index 0000000..d91c4d9
--- /dev/null
+++ b/src/giopipe.c
@@ -0,0 +1,484 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+  Copyright (C) 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 <string.h>
+#include <errno.h>
+
+#include "giopipe.h"
+
+#define TYPE_PIPE_INPUT_STREAM         (pipe_input_stream_get_type ())
+#define PIPE_INPUT_STREAM(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), TYPE_PIPE_INPUT_STREAM, PipeInputStream))
+#define PIPE_INPUT_STREAM_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), TYPE_PIPE_INPUT_STREAM, PipeInputStreamClass))
+#define IS_PIPE_INPUT_STREAM(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), TYPE_PIPE_INPUT_STREAM))
+#define IS_PIPE_INPUT_STREAM_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), TYPE_PIPE_INPUT_STREAM))
+#define PIPE_INPUT_STREAM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), TYPE_PIPE_INPUT_STREAM, PipeInputStreamClass))
+
+typedef struct _PipeInputStreamClass                              PipeInputStreamClass;
+typedef struct _PipeInputStream                                   PipeInputStream;
+typedef struct _PipeOutputStream                                  PipeOutputStream;
+
+struct _PipeInputStream
+{
+    GInputStream parent_instance;
+
+    PipeOutputStream *peer;
+    gssize read;
+
+    /* GIOstream:closed is protected against pending operations, so we
+     * use an additional close flag to cancel those when the peer is
+     * closing.
+     */
+    gboolean peer_closed;
+    GList *sources;
+};
+
+struct _PipeInputStreamClass
+{
+    GInputStreamClass parent_class;
+};
+
+#define TYPE_PIPE_OUTPUT_STREAM         (pipe_output_stream_get_type ())
+#define PIPE_OUTPUT_STREAM(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), TYPE_PIPE_OUTPUT_STREAM, PipeOutputStream))
+#define PIPE_OUTPUT_STREAM_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), TYPE_PIPE_OUTPUT_STREAM, PipeOutputStreamClass))
+#define IS_PIPE_OUTPUT_STREAM(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), TYPE_PIPE_OUTPUT_STREAM))
+#define IS_PIPE_OUTPUT_STREAM_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), TYPE_PIPE_OUTPUT_STREAM))
+#define PIPE_OUTPUT_STREAM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), TYPE_PIPE_OUTPUT_STREAM, PipeOutputStreamClass))
+
+typedef struct _PipeOutputStreamClass                             PipeOutputStreamClass;
+
+struct _PipeOutputStream
+{
+    GOutputStream parent_instance;
+
+    PipeInputStream *peer;
+    const gchar *buffer;
+    gsize count;
+    gboolean peer_closed;
+    GList *sources;
+};
+
+struct _PipeOutputStreamClass
+{
+    GOutputStreamClass parent_class;
+};
+
+static void pipe_input_stream_pollable_iface_init (GPollableInputStreamInterface *iface);
+static void pipe_input_stream_check_source (PipeInputStream *self);
+static void pipe_output_stream_check_source (PipeOutputStream *self);
+
+G_DEFINE_TYPE_WITH_CODE (PipeInputStream, pipe_input_stream, G_TYPE_INPUT_STREAM,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_POLLABLE_INPUT_STREAM,
+                                                pipe_input_stream_pollable_iface_init))
+
+static gssize
+pipe_input_stream_read (GInputStream  *stream,
+                        void          *buffer,
+                        gsize          count,
+                        GCancellable  *cancellable,
+                        GError       **error)
+{
+    PipeInputStream *self = PIPE_INPUT_STREAM (stream);
+
+    g_return_val_if_fail(count > 0, -1);
+
+    if (g_input_stream_is_closed (stream) || self->peer_closed) {
+        g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_CLOSED,
+                             "Stream is already closed");
+        return -1;
+    }
+
+    if (!self->peer->buffer) {
+        g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK,
+                             g_strerror(EAGAIN));
+        return -1;
+    }
+
+    count = MIN(self->peer->count, count);
+    memcpy(buffer, self->peer->buffer, count);
+    self->read = count;
+    self->peer->buffer = NULL;
+
+    //g_debug("read %p :%"G_GSIZE_FORMAT, self->peer, count);
+    /* schedule peer source */
+    pipe_output_stream_check_source(self->peer);
+
+    return count;
+}
+
+static GList *
+set_all_sources_ready (GList *sources)
+{
+    GList *it = sources;
+    while (it != NULL) {
+        GSource *s = it->data;
+        GList *next = it->next;
+
+        if (s == NULL || g_source_is_destroyed(s)) {
+            /* remove */
+            sources = g_list_delete_link(sources, it);
+            g_source_unref(s);
+        } else {
+            /* dispatch */
+            g_source_set_ready_time(s, 0);
+        }
+        it = next;
+    }
+    return sources;
+}
+
+static void
+pipe_input_stream_check_source (PipeInputStream *self)
+{
+    if (g_pollable_input_stream_is_readable(G_POLLABLE_INPUT_STREAM(self)))
+        self->sources = set_all_sources_ready(self->sources);
+}
+
+static gboolean
+pipe_input_stream_close (GInputStream  *stream,
+                         GCancellable   *cancellable,
+                         GError        **error)
+{
+    PipeInputStream *self;
+
+    self = PIPE_INPUT_STREAM(stream);
+
+    if (self->peer) {
+        /* ignore any pending errors */
+        self->peer->peer_closed = TRUE;
+        g_output_stream_close(G_OUTPUT_STREAM(self->peer), cancellable, NULL);
+        pipe_output_stream_check_source(self->peer);
+    }
+
+    return TRUE;
+}
+
+static void
+pipe_input_stream_close_async (GInputStream       *stream,
+                               int                  io_priority,
+                               GCancellable        *cancellable,
+                               GAsyncReadyCallback  callback,
+                               gpointer             data)
+{
+    GTask *task;
+
+    task = g_task_new (stream, cancellable, callback, data);
+
+    /* will always return TRUE */
+    pipe_input_stream_close (stream, cancellable, NULL);
+
+    g_task_return_boolean (task, TRUE);
+    g_object_unref (task);
+}
+
+static gboolean
+pipe_input_stream_close_finish (GInputStream  *stream,
+                                GAsyncResult   *result,
+                                GError        **error)
+{
+    g_return_val_if_fail (g_task_is_valid (result, stream), FALSE);
+
+    return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+pipe_input_stream_init (PipeInputStream *self)
+{
+    self->read = -1;
+}
+
+static void
+pipe_input_stream_dispose(GObject *object)
+{
+    PipeInputStream *self;
+
+    self = PIPE_INPUT_STREAM(object);
+
+    if (self->peer) {
+        g_object_remove_weak_pointer(G_OBJECT(self->peer), (gpointer*)&self->peer);
+        self->peer = NULL;
+    }
+
+    g_list_free_full (self->sources, (GDestroyNotify) g_source_unref);
+    self->sources = NULL;
+
+    G_OBJECT_CLASS(pipe_input_stream_parent_class)->dispose (object);
+}
+
+static void
+pipe_input_stream_class_init (PipeInputStreamClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+    GInputStreamClass *istream_class = G_INPUT_STREAM_CLASS (klass);
+
+    istream_class->read_fn  = pipe_input_stream_read;
+    istream_class->close_fn = pipe_input_stream_close;
+    istream_class->close_async  = pipe_input_stream_close_async;
+    istream_class->close_finish = pipe_input_stream_close_finish;
+
+    gobject_class->dispose = pipe_input_stream_dispose;
+}
+
+static gboolean
+pipe_input_stream_is_readable (GPollableInputStream *stream)
+{
+    PipeInputStream *self = PIPE_INPUT_STREAM (stream);
+    gboolean readable;
+
+    readable = (self->peer && self->peer->buffer && self->read == -1) || self->peer_closed;
+    //g_debug("readable %p %d", self->peer, readable);
+
+    return readable;
+}
+
+static GSource *
+pipe_input_stream_create_source (GPollableInputStream *stream,
+                                 GCancellable         *cancellable)
+{
+    PipeInputStream *self = PIPE_INPUT_STREAM(stream);
+    GSource *pollable_source;
+
+    pollable_source = g_pollable_source_new_full (self, NULL, cancellable);
+    self->sources = g_list_prepend (self->sources, g_source_ref (pollable_source));
+
+    return pollable_source;
+}
+
+static void
+pipe_input_stream_pollable_iface_init (GPollableInputStreamInterface *iface)
+{
+    iface->is_readable   = pipe_input_stream_is_readable;
+    iface->create_source = pipe_input_stream_create_source;
+}
+
+static void pipe_output_stream_pollable_iface_init (GPollableOutputStreamInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (PipeOutputStream, pipe_output_stream, G_TYPE_OUTPUT_STREAM,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_POLLABLE_OUTPUT_STREAM,
+                                                pipe_output_stream_pollable_iface_init))
+
+static gssize
+pipe_output_stream_write (GOutputStream  *stream,
+                          const void     *buffer,
+                          gsize           count,
+                          GCancellable   *cancellable,
+                          GError        **error)
+{
+    PipeOutputStream *self = PIPE_OUTPUT_STREAM(stream);
+    PipeInputStream *peer = self->peer;
+
+    //g_debug("write %p :%"G_GSIZE_FORMAT, stream, count);
+    if (g_output_stream_is_closed (stream) || self->peer_closed) {
+        g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_CLOSED,
+                             "Stream is already closed");
+        return -1;
+    }
+
+    /* this abuses pollable stream, writing sync would likely lead to
+       crashes, since the buffer pointer would become invalid, a
+       generic solution would need a copy..
+    */
+    g_return_val_if_fail(self->buffer == buffer || self->buffer == NULL, -1);
+    self->buffer = buffer;
+    self->count = count;
+
+    pipe_input_stream_check_source(self->peer);
+
+    if (peer->read < 0) {
+        g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK,
+                             g_strerror (EAGAIN));
+        return -1;
+    }
+
+    g_assert(peer->read <= self->count);
+    count = peer->read;
+
+    self->buffer = NULL;
+    self->count = 0;
+    peer->read = -1;
+
+    return count;
+}
+
+static void
+pipe_output_stream_init (PipeOutputStream *stream)
+{
+}
+
+static void
+pipe_output_stream_dispose(GObject *object)
+{
+    PipeOutputStream *self;
+
+    self = PIPE_OUTPUT_STREAM(object);
+
+    if (self->peer) {
+        g_object_remove_weak_pointer(G_OBJECT(self->peer), (gpointer*)&self->peer);
+        self->peer = NULL;
+    }
+
+    g_list_free_full (self->sources, (GDestroyNotify) g_source_unref);
+    self->sources = NULL;
+
+    G_OBJECT_CLASS(pipe_output_stream_parent_class)->dispose (object);
+}
+
+static void
+pipe_output_stream_check_source (PipeOutputStream *self)
+{
+    if (g_pollable_output_stream_is_writable(G_POLLABLE_OUTPUT_STREAM(self)))
+        self->sources = set_all_sources_ready(self->sources);
+}
+
+static gboolean
+pipe_output_stream_close (GOutputStream  *stream,
+                          GCancellable   *cancellable,
+                          GError        **error)
+{
+    PipeOutputStream *self;
+
+    self = PIPE_OUTPUT_STREAM(stream);
+
+    if (self->peer) {
+        /* ignore any pending errors */
+        self->peer->peer_closed = TRUE;
+        g_input_stream_close(G_INPUT_STREAM(self->peer), cancellable, NULL);
+        pipe_input_stream_check_source(self->peer);
+    }
+
+    return TRUE;
+}
+
+static void
+pipe_output_stream_close_async (GOutputStream       *stream,
+                                int                  io_priority,
+                                GCancellable        *cancellable,
+                                GAsyncReadyCallback  callback,
+                                gpointer             data)
+{
+    GTask *task;
+
+    task = g_task_new (stream, cancellable, callback, data);
+
+    /* will always return TRUE */
+    pipe_output_stream_close (stream, cancellable, NULL);
+
+    g_task_return_boolean (task, TRUE);
+    g_object_unref (task);
+}
+
+static gboolean
+pipe_output_stream_close_finish (GOutputStream  *stream,
+                                 GAsyncResult   *result,
+                                 GError        **error)
+{
+    g_return_val_if_fail (g_task_is_valid (result, stream), FALSE);
+
+    return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+
+static void
+pipe_output_stream_class_init (PipeOutputStreamClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+    GOutputStreamClass *ostream_class = G_OUTPUT_STREAM_CLASS (klass);
+
+    ostream_class->write_fn = pipe_output_stream_write;
+    ostream_class->close_fn = pipe_output_stream_close;
+    ostream_class->close_async  = pipe_output_stream_close_async;
+    ostream_class->close_finish = pipe_output_stream_close_finish;
+
+    gobject_class->dispose = pipe_output_stream_dispose;
+}
+
+static gboolean
+pipe_output_stream_is_writable (GPollableOutputStream *stream)
+{
+    PipeOutputStream *self = PIPE_OUTPUT_STREAM(stream);
+    gboolean writable;
+
+    writable = self->buffer == NULL || self->peer->read >= 0;
+    //g_debug("writable %p %d", self, writable);
+
+    return writable;
+}
+
+static GSource *
+pipe_output_stream_create_source (GPollableOutputStream *stream,
+                                  GCancellable          *cancellable)
+{
+    PipeOutputStream *self = PIPE_OUTPUT_STREAM(stream);
+    GSource *pollable_source;
+
+    pollable_source = g_pollable_source_new_full (self, NULL, cancellable);
+    self->sources = g_list_prepend (self->sources, g_source_ref (pollable_source));
+
+    return pollable_source;
+}
+
+static void
+pipe_output_stream_pollable_iface_init (GPollableOutputStreamInterface *iface)
+{
+    iface->is_writable = pipe_output_stream_is_writable;
+    iface->create_source = pipe_output_stream_create_source;
+}
+
+G_GNUC_INTERNAL void
+make_gio_pipe(GInputStream **input, GOutputStream **output)
+{
+    PipeInputStream *in;
+    PipeOutputStream *out;
+
+    g_return_if_fail(input != NULL && *input == NULL);
+    g_return_if_fail(output != NULL && *output == NULL);
+
+    in = g_object_new(TYPE_PIPE_INPUT_STREAM, NULL);
+    out = g_object_new(TYPE_PIPE_OUTPUT_STREAM, NULL);
+
+    out->peer = in;
+    g_object_add_weak_pointer(G_OBJECT(in), (gpointer*)&out->peer);
+
+    in->peer = out;
+    g_object_add_weak_pointer(G_OBJECT(out), (gpointer*)&in->peer);
+
+    *input = G_INPUT_STREAM(in);
+    *output = G_OUTPUT_STREAM(out);
+}
+
+G_GNUC_INTERNAL void
+spice_make_pipe(GIOStream **p1, GIOStream **p2)
+{
+    GInputStream *in1 = NULL, *in2 = NULL;
+    GOutputStream *out1 = NULL, *out2 = NULL;
+
+    g_return_if_fail(p1 != NULL);
+    g_return_if_fail(p2 != NULL);
+    g_return_if_fail(*p1 == NULL);
+    g_return_if_fail(*p2 == NULL);
+
+    make_gio_pipe(&in1, &out2);
+    make_gio_pipe(&in2, &out1);
+
+    *p1 = g_simple_io_stream_new(in1, out1);
+    *p2 = g_simple_io_stream_new(in2, out2);
+
+    g_object_unref(in1);
+    g_object_unref(in2);
+    g_object_unref(out1);
+    g_object_unref(out2);
+}
diff --git a/src/giopipe.h b/src/giopipe.h
new file mode 100644
index 0000000..46c2c9c
--- /dev/null
+++ b/src/giopipe.h
@@ -0,0 +1,29 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+  Copyright (C) 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 __SPICE_GIO_PIPE_H__
+#define __SPICE_GIO_PIPE_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+void spice_make_pipe(GIOStream **p1, GIOStream **p2);
+
+G_END_DECLS
+
+#endif /* __SPICE_GIO_PIPE_H__ */
diff --git a/src/glib-compat.c b/src/glib-compat.c
new file mode 100644
index 0000000..49edf73
--- /dev/null
+++ b/src/glib-compat.c
@@ -0,0 +1,79 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2012-2014 Red Hat, Inc.
+   Copyright © 1998-2009 VLC authors and VideoLAN
+
+   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 <string.h>
+
+#include "glib-compat.h"
+
+#if !GLIB_CHECK_VERSION(2,30,0)
+G_DEFINE_BOXED_TYPE (GMainContext, spice_main_context, g_main_context_ref, g_main_context_unref)
+#endif
+
+
+#if !GLIB_CHECK_VERSION(2,32,0)
+/**
+ * g_queue_free_full:
+ * @queue: a pointer to a #GQueue
+ * @free_func: the function to be called to free each element's data
+ *
+ * Convenience method, which frees all the memory used by a #GQueue,
+ * and calls the specified destroy function on every element's data.
+ *
+ * Since: 2.32
+ */
+void
+g_queue_free_full (GQueue        *queue,
+                  GDestroyNotify  free_func)
+{
+  g_queue_foreach (queue, (GFunc) free_func, NULL);
+  g_queue_free (queue);
+}
+#endif
+
+
+#ifndef HAVE_STRTOK_R
+G_GNUC_INTERNAL
+char *strtok_r(char *s, const char *delim, char **save_ptr)
+{
+    char *token;
+
+    if (s == NULL)
+        s = *save_ptr;
+
+    /* Scan leading delimiters. */
+    s += strspn (s, delim);
+    if (*s == '\0')
+        return NULL;
+
+    /* Find the end of the token. */
+    token = s;
+    s = strpbrk (token, delim);
+    if (s == NULL)
+        /* This token finishes the string. */
+        *save_ptr = strchr (token, '\0');
+    else
+    {
+        /* Terminate the token and make *SAVE_PTR point past it. */
+        *s = '\0';
+        *save_ptr = s + 1;
+    }
+    return token;
+}
+#endif
diff --git a/src/glib-compat.h b/src/glib-compat.h
new file mode 100644
index 0000000..5491fe4
--- /dev/null
+++ b/src/glib-compat.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2012-2014 Red Hat, Inc.
+   Copyright © 1998-2009 VLC authors and VideoLAN
+
+   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 GLIB_COMPAT_H
+#define GLIB_COMPAT_H
+
+#include "config.h"
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+
+#if !GLIB_CHECK_VERSION(2,30,0)
+#define G_TYPE_MAIN_CONTEXT (spice_main_context_get_type ())
+GType spice_main_context_get_type (void) G_GNUC_CONST;
+#endif
+
+#if !GLIB_CHECK_VERSION(2,32,0)
+# define G_SIGNAL_DEPRECATED (1 << 9)
+
+#define G_SOURCE_CONTINUE   TRUE
+#define G_SOURCE_REMOVE     FALSE
+
+void
+g_queue_free_full (GQueue        *queue,
+                   GDestroyNotify  free_func);
+#endif
+
+#ifndef g_clear_pointer
+#define g_clear_pointer(pp, destroy) \
+  G_STMT_START {                                                               \
+    G_STATIC_ASSERT (sizeof *(pp) == sizeof (gpointer));                       \
+    /* Only one access, please */                                              \
+    gpointer *_pp = (gpointer *) (pp);                                         \
+    gpointer _p;                                                               \
+    /* This assignment is needed to avoid a gcc warning */                     \
+    GDestroyNotify _destroy = (GDestroyNotify) (destroy);                      \
+                                                                               \
+    (void) (0 ? (gpointer) *(pp) : 0);                                         \
+    do                                                                         \
+      _p = g_atomic_pointer_get (_pp);                                         \
+    while G_UNLIKELY (!g_atomic_pointer_compare_and_exchange (_pp, _p, NULL)); \
+                                                                               \
+    if (_p)                                                                    \
+      _destroy (_p);                                                           \
+  } G_STMT_END
+#endif
+
+#ifndef HAVE_STRTOK_R
+char* strtok_r(char *s, const char *delim, char **save_ptr);
+#endif
+
+#endif /* GLIB_COMPAT_H */
diff --git a/src/gtk-compat.h b/src/gtk-compat.h
new file mode 100644
index 0000000..be143b2
--- /dev/null
+++ b/src/gtk-compat.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2012-2014 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 GTK_COMPAT_H
+#define GTK_COMPAT_H
+
+#include "config.h"
+
+#include <gtk/gtk.h>
+
+#if !GTK_CHECK_VERSION (2, 91, 0)
+#define GDK_IS_X11_DISPLAY(D) TRUE
+#define gdk_window_get_display(W) gdk_drawable_get_display(GDK_DRAWABLE(W))
+#endif
+
+#if GTK_CHECK_VERSION (2, 91, 0)
+static inline void gdk_drawable_get_size(GdkWindow *w, gint *ww, gint *wh)
+{
+    *ww = gdk_window_get_width(w);
+    *wh = gdk_window_get_height(w);
+}
+#endif
+
+#if !GTK_CHECK_VERSION(2, 20, 0)
+static inline gboolean gtk_widget_get_realized(GtkWidget *widget)
+{
+    g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
+    return GTK_WIDGET_REALIZED(widget);
+}
+#endif
+
+#if !GTK_CHECK_VERSION (3, 0, 0)
+#define cairo_rectangle_int_t GdkRectangle
+#define cairo_region_t GdkRegion
+#define cairo_region_create_rectangle gdk_region_rectangle
+#define cairo_region_subtract_rectangle(_dest,_rect) { GdkRegion *_region = gdk_region_rectangle (_rect); gdk_region_subtract (_dest, _region); gdk_region_destroy (_region); }
+#define cairo_region_destroy gdk_region_destroy
+
+#define gdk_window_get_display(W) gdk_drawable_get_display(GDK_DRAWABLE(W))
+#endif
+
+#endif /* GTK_COMPAT_H */
diff --git a/src/keymap-gen.pl b/src/keymap-gen.pl
new file mode 100755
index 0000000..56953f8
--- /dev/null
+++ b/src/keymap-gen.pl
@@ -0,0 +1,214 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Text::CSV;
+
+my %names = (
+    linux => [],
+    osx => []
+);
+
+my %namecolumns = (
+    linux => 0,
+    osx => 2,
+    win32 => 10,
+    x11 => 14,
+    );
+
+# Base data sources:
+#
+#  linux:     Linux: linux/input.h                                  (master set)
+#    osx:      OS-X: Carbon/HIToolbox/Events.h                      (manually mapped)
+# atset1:  AT Set 1: linux/drivers/input/keyboard/atkbd.c           (atkbd_set2_keycode + atkbd_unxlate_table)
+# atset2:  AT Set 2: linux/drivers/input/keyboard/atkbd.c           (atkbd_set2_keycode)
+# atset3:  AT Set 3: linux/drivers/input/keyboard/atkbd.c           (atkbd_set3_keycode)
+#     xt:        XT: linux/drivers/input/keyboard/xt.c              (xtkbd_keycode)
+#  xtkbd: Linux RAW: linux/drivers/char/keyboard.c                  (x86_keycodes)
+#    usb:   USB HID: linux/drivers/hid/usbhid/usbkbd.c              (usb_kbd_keycode)
+#  win32:     Win32: mingw32/winuser.h                              (manually mapped)
+# xwinxt:   XWin XT: xorg-server/hw/xwin/{winkeybd.c,winkeynames.h} (xt + manually transcribed)
+# xkbdxt:   XKBD XT: xf86-input-keyboard/src/at_scancode.c
+#(xt + manually transcribed)
+#    x11: X11 keysyms: http://cgit.freedesktop.org/xorg/proto/x11proto/plain/keysymdef.h
+#
+# Derived data sources
+#
+#    xorgevdev: Xorg +  evdev: linux + an offset
+#      xorgkbd: Xorg +    kbd: xkbdxt + an offset
+#  xorgxquartz: Xorg +   OS-X: osx + an offset
+#     xorgxwin: Xorg + Cygwin: xwinxt + an offset
+#          rfb:   XT over RFB: xtkbd + special re-encoding of high bit
+
+my @basemaps = qw(linux osx atset1 atset2 atset3 xt xtkbd usb win32 xwinxt xkbdxt x11);
+my @derivedmaps = qw(xorgevdev xorgkbd xorgxquartz xorgxwin rfb);
+my @maps = (@basemaps, @derivedmaps);
+
+my %maps;
+
+foreach my $map (@maps) {
+    $maps{$map} = [ [], [] ];
+}
+my %mapcolumns = (
+    osx => 3,
+    atset1 => 4,
+    atset2 => 5,
+    atset3 => 6,
+    xt => 7,
+    xtkbd => 8,
+    usb => 9,
+    win32 => 11,
+    xwinxt => 12,
+    xkbdxt => 13,
+    x11 => 15
+    );
+
+sub help {
+    my $msg = shift;
+    print $msg;
+    print "\n";
+    print "Valid keymaps are:\n";
+    print "\n";
+    foreach my $name (sort { $a cmp $b } keys %maps) {
+	print "  $name\n";
+    }
+    print "\n";
+    exit (1);
+}
+
+if ($#ARGV != 2) {
+    help("syntax: $0 KEYMAPS SRCMAP DSTMAP\n");
+}
+
+my $keymaps = shift @ARGV;
+my $src = shift @ARGV;
+my $dst = shift @ARGV;
+
+help("$src is not a known keymap\n") unless exists $maps{$src};
+help("$dst is not a known keymap\n") unless exists $maps{$dst};
+
+
+open CSV, $keymaps
+    or die "cannot read $keymaps: $!";
+
+my $csv = Text::CSV->new();
+# Discard column headings
+$csv->getline(\*CSV);
+
+my $row;
+while ($row = $csv->getline(\*CSV)) {
+    my $linux = $row->[1];
+
+    $linux = hex($linux) if $linux =~ /0x/;
+
+    my $to = $maps{linux}->[0];
+    my $from = $maps{linux}->[1];
+    $to->[$linux] = $linux;
+    $from->[$linux] = $linux;
+
+    foreach my $name (keys %namecolumns) {
+	my $col = $namecolumns{$name};
+	my $val = $row->[$col];
+
+	$val = "" unless defined $val;
+
+	$names{$name}->[$linux] = $val;
+    }
+
+    foreach my $name (keys %mapcolumns) {
+	my $col = $mapcolumns{$name};
+	my $val = $row->[$col];
+
+	next unless defined $val && $val ne "";
+	$val = hex($val) if $val =~ /0x/;
+
+	$to = $maps{$name}->[0];
+        $from = $maps{$name}->[1];
+	$to->[$linux] = $val;
+	$from->[$val] = $linux;
+    }
+
+    # XXX there are some special cases in kbd to handle
+    # Xorg KBD driver is the Xorg KBD XT codes offset by +8
+    # The XKBD XT codes are the same as normal XT codes
+    # for values <= 83, and completely made up for extended
+    # scancodes :-(
+    ($to, $from) = @{$maps{xorgkbd}};
+    if (defined $maps{xkbdxt}->[0]->[$linux]) {
+	$to->[$linux] = $maps{xkbdxt}->[0]->[$linux] + 8;
+	$from->[$to->[$linux]] = $linux;
+    }
+
+    # Xorg evdev is simply Linux keycodes offset by +8
+    ($to, $from) = @{$maps{xorgevdev}};
+    $to->[$linux] = $linux + 8;
+    $from->[$to->[$linux]] = $linux;
+
+    # Xorg XQuartz is simply OS-X keycodes offset by +8
+    ($to, $from) = @{$maps{xorgxquartz}};
+    if (defined $maps{osx}->[0]->[$linux]) {
+	$to->[$linux] = $maps{osx}->[0]->[$linux] + 8;
+	$from->[$to->[$linux]] = $linux;
+    }
+
+    # RFB keycodes are XT kbd keycodes with a slightly
+    # different encoding of 0xe0 scan codes. RFB uses
+    # the high bit of the first byte, instead of the low
+    # bit of the second byte.
+    ($to, $from) = @{$maps{rfb}};
+    my $xtkbd = $maps{xtkbd}->[0]->[$linux];
+    if (defined $xtkbd) {
+	$to->[$linux] = $xtkbd ? (($xtkbd & 0x100)>>1) | ($xtkbd & 0x7f) : 0;
+	$from->[$to->[$linux]] = $linux;
+    }
+
+    # Xorg Cygwin is the Xorg Cygwin XT codes offset by +8
+    # The Cygwin XT codes are the same as normal XT codes
+    # for values <= 83, and completely made up for extended
+    # scancodes :-(
+    ($to, $from) = @{$maps{xorgxwin}};
+    if (defined $maps{xwinxt}->[0]->[$linux]) {
+	$to->[$linux] = $maps{xwinxt}->[0]->[$linux] + 8;
+	$from->[$to->[$linux]] = $linux;
+    }
+
+#    print $linux, "\n";
+}
+
+close CSV;
+
+my $srcmap = $maps{$src}->[1];
+my $dstmap = $maps{$dst}->[0];
+
+printf "static const guint16 keymap_%s2%s[] = {\n", $src, $dst;
+
+for (my $i = 0 ; $i <= $#{$srcmap} ; $i++) {
+    my $linux = $srcmap->[$i] || 0;
+    my $j = $dstmap->[$linux];
+    next unless $linux && $j;
+
+    my $srcname = $names{$src}->[$linux] if exists $names{$src};
+    my $dstname = $names{$dst}->[$linux] if exists $names{$dst};
+    my $vianame = $names{linux}->[$linux] unless $src eq "linux" || $dst eq "linux";
+
+    $srcname = "" unless $srcname;
+    $dstname = "" unless $dstname;
+    $vianame = "" unless $vianame;
+    $srcname = " ($srcname)" if $srcname;
+    $dstname = " ($dstname)" if $dstname;
+    $vianame = " ($vianame)" if $vianame;
+
+    my $comment;
+    if ($src ne "linux" && $dst ne "linux") {
+	$comment = sprintf "%d%s => %d%s via %d%s", $i, $srcname, $j, $dstname, $linux, $vianame;
+    } else {
+	$comment = sprintf "%d%s => %d%s", $i, $srcname, $j, $dstname;
+    }
+
+    my $data = sprintf "[0x%x] = 0x%x,", $i, $j;
+
+    printf "  %-20s /* %s */\n", $data, $comment;
+}
+
+print "};\n";
diff --git a/src/keymaps.csv b/src/keymaps.csv
new file mode 100644
index 0000000..9052e3b
--- /dev/null
+++ b/src/keymaps.csv
@@ -0,0 +1,490 @@
+"Linux Name","Linux Keycode","OS-X Name","OS-X Keycode","AT set1 keycode","AT set2 keycode","AT set3 keycode",XT,"XT KBD","USB Keycodes","Win32 Name","Win32 Keycode","Xwin XT","Xfree86 KBD XT","X11 keysym","X11 keycode"
+KEY_RESERVED,0,,,,,,,,,,,,,,
+KEY_ESC,1,Escape,0x35,1,118,8,1,1,41,VK_ESCAPE,0x1b,1,1,XK_Escape,0xff1b
+KEY_1,2,ANSI_1,0x12,2,22,22,2,2,30,VK_1,0x31,2,2,XK_1,0x0031
+KEY_2,3,ANSI_2,0x13,3,30,30,3,3,31,VK_2,0x32,3,3,XK_2,0x0032
+KEY_3,4,ANSI_3,0x14,4,38,38,4,4,32,VK_3,0x33,4,4,XK_3,0x0033
+KEY_4,5,ANSI_4,0x15,5,37,37,5,5,33,VK_4,0x34,5,5,XK_4,0x0034
+KEY_5,6,ANSI_5,0x17,6,46,46,6,6,34,VK_5,0x35,6,6,XK_5,0x0035
+KEY_6,7,ANSI_6,0x16,7,54,54,7,7,35,VK_6,0x36,7,7,XK_6,0x0036
+KEY_7,8,ANSI_7,0x1a,8,61,61,8,8,36,VK_7,0x37,8,8,XK_7,0x0037
+KEY_8,9,ANSI_8,0x1c,9,62,62,9,9,37,VK_8,0x38,9,9,XK_8,0x0038
+KEY_9,10,ANSI_9,0x19,10,70,70,10,10,38,VK_9,0x39,10,10,XK_9,0x0039
+KEY_0,11,ANSI_0,0x1d,11,69,69,11,11,39,VK_0,0x30,11,11,XK_0,0x0030
+KEY_MINUS,12,ANSI_Minus,0x1b,12,78,78,12,12,45,VK_OEM_MINUS,0xbd,12,12,XK_minus,0x002d
+KEY_EQUAL,13,ANSI_Equal,0x18,13,85,85,13,13,46,VK_OEM_PLUS,0xbb,13,13,XK_equal,0x003d
+KEY_BACKSPACE,14,Delete,0x33,14,102,102,14,14,42,VK_BACK,0x08,14,14,XK_BackSpace,0xff08
+KEY_TAB,15,Tab,0x30,15,13,13,15,15,43,VK_TAB,0x09,15,15,XK_Tab,0xff09
+KEY_Q,16,ANSI_Q,0xc,16,21,21,16,16,20,VK_Q,0x51,16,16,XK_Q,0x0051
+KEY_Q,16,ANSI_Q,0xc,16,21,21,16,16,20,VK_Q,0x51,16,16,XK_q,0x0071
+KEY_W,17,ANSI_W,0xd,17,29,29,17,17,26,VK_W,0x57,17,17,XK_W,0x0057
+KEY_W,17,ANSI_W,0xd,17,29,29,17,17,26,VK_W,0x57,17,17,XK_w,0x0077
+KEY_E,18,ANSI_E,0xe,18,36,36,18,18,8,VK_E,0x45,18,18,XK_E,0x0045
+KEY_E,18,ANSI_E,0xe,18,36,36,18,18,8,VK_E,0x45,18,18,XK_e,0x0065
+KEY_R,19,ANSI_R,0xf,19,45,45,19,19,21,VK_R,0x52,19,19,XK_R,0x0052
+KEY_R,19,ANSI_R,0xf,19,45,45,19,19,21,VK_R,0x52,19,19,XK_r,0x0072
+KEY_T,20,ANSI_T,0x11,20,44,44,20,20,23,VK_T,0x54,20,20,XK_T,0x0054
+KEY_T,20,ANSI_T,0x11,20,44,44,20,20,23,VK_T,0x54,20,20,XK_t,0x0074
+KEY_Y,21,ANSI_Y,0x10,21,53,53,21,21,28,VK_Y,0x59,21,21,XK_Y,0x0059
+KEY_Y,21,ANSI_Y,0x10,21,53,53,21,21,28,VK_Y,0x59,21,21,XK_y,0x0079
+KEY_U,22,ANSI_U,0x20,22,60,60,22,22,24,VK_U,0x55,22,22,XK_U,0x0055
+KEY_U,22,ANSI_U,0x20,22,60,60,22,22,24,VK_U,0x55,22,22,XK_u,0x0075
+KEY_I,23,ANSI_I,0x22,23,67,67,23,23,12,VK_I,0x49,23,23,XK_I,0x0049
+KEY_I,23,ANSI_I,0x22,23,67,67,23,23,12,VK_I,0x49,23,23,XK_i,0x0069
+KEY_O,24,ANSI_O,0x1f,24,68,68,24,24,18,VK_O,0x4f,24,24,XK_O,0x004f
+KEY_O,24,ANSI_O,0x1f,24,68,68,24,24,18,VK_O,0x4f,24,24,XK_o,0x006f
+KEY_P,25,ANSI_P,0x23,25,77,77,25,25,19,VK_P,0x50,25,25,XK_P,0x0050
+KEY_P,25,ANSI_P,0x23,25,77,77,25,25,19,VK_P,0x50,25,25,XK_p,0x0070
+KEY_LEFTBRACE,26,ANSI_LeftBracket,0x21,26,84,84,26,26,47,VK_OEM_4,0xdb,26,26,XK_bracketleft,0x005b
+KEY_RIGHTBRACE,27,ANSI_RightBracket,0x1e,27,91,91,27,27,48,VK_OEM_6,0xdd,27,27,XK_bracketright,0x005d
+KEY_ENTER,28,Return,0x24,28,90,90,28,28,40,VK_RETURN,0x0d,28,28,XK_Return,0xff0d
+KEY_LEFTCTRL,29,Control,0x3b,29,20,17,29,29,224,VK_LCONTROL,0xa2,29,29,XK_Control_L,0xffe3
+KEY_LEFTCTRL,29,Control,0x3b,29,20,17,29,29,224,VK_CONTROL,0x11,29,29,XK_Control_L,0xffe3
+KEY_A,30,ANSI_A,0x0,30,28,28,30,30,4,VK_A,0x41,30,30,XK_A,0x0041
+KEY_A,30,ANSI_A,0x0,30,28,28,30,30,4,VK_A,0x41,30,30,XK_a,0x0061
+KEY_S,31,ANSI_S,0x1,31,27,27,31,31,22,VK_S,0x53,31,31,XK_S,0x0053
+KEY_S,31,ANSI_S,0x1,31,27,27,31,31,22,VK_S,0x53,31,31,XK_s,0x0073
+KEY_D,32,ANSI_D,0x2,32,35,35,32,32,7,VK_D,0x44,32,32,XK_D,0x0044
+KEY_D,32,ANSI_D,0x2,32,35,35,32,32,7,VK_D,0x44,32,32,XK_d,0x0064
+KEY_F,33,ANSI_F,0x3,33,43,43,33,33,9,VK_F,0x46,33,33,XK_F,0x0046
+KEY_F,33,ANSI_F,0x3,33,43,43,33,33,9,VK_F,0x46,33,33,XK_f,0x0066
+KEY_G,34,ANSI_G,0x5,34,52,52,34,34,10,VK_G,0x47,34,34,XK_G,0x0047
+KEY_G,34,ANSI_G,0x5,34,52,52,34,34,10,VK_G,0x47,34,34,XK_g,0x0067
+KEY_H,35,ANSI_H,0x4,35,51,51,35,35,11,VK_H,0x48,35,35,XK_H,0x0048
+KEY_H,35,ANSI_H,0x4,35,51,51,35,35,11,VK_H,0x48,35,35,XK_h,0x0068
+KEY_J,36,ANSI_J,0x26,36,59,59,36,36,13,VK_J,0x4a,36,36,XK_J,0x004a
+KEY_J,36,ANSI_J,0x26,36,59,59,36,36,13,VK_J,0x4a,36,36,XK_j,0x006a
+KEY_K,37,ANSI_K,0x28,37,66,66,37,37,14,VK_K,0x4b,37,37,XK_K,0x004b
+KEY_K,37,ANSI_K,0x28,37,66,66,37,37,14,VK_K,0x4b,37,37,XK_K,0x006b
+KEY_L,38,ANSI_L,0x25,38,75,75,38,38,15,VK_L,0x4c,38,38,XK_L,0x004c
+KEY_L,38,ANSI_L,0x25,38,75,75,38,38,15,VK_L,0x4c,38,38,XK_l,0x006c
+KEY_SEMICOLON,39,ANSI_Semicolon,0x29,39,76,76,39,39,51,VK_OEM_1,0xba,39,39,XK_semicolon,0x003b
+KEY_APOSTROPHE,40,ANSI_Quote,0x27,40,82,82,40,40,52,VK_OEM_7,0xde,40,40,XK_apostrophe,0x0027
+KEY_GRAVE,41,ANSI_Grave,0x32,41,14,14,41,41,53,VK_OEM_3,0xc0,41,41,XK_grave,0x0060
+KEY_SHIFT,42,Shift,0x38,42,18,18,42,42,225,VK_SHIFT,0x10,42,42,XK_Shift_L,0xffe1
+KEY_LEFTSHIFT,42,Shift,0x38,42,18,18,42,42,225,VK_LSHIFT,0xa0,42,42,XK_Shift_L,0xffe1
+KEY_BACKSLASH,43,ANSI_Backslash,0x2a,43,93,93,43,43,50,VK_OEM_5,0xdc,43,43,XK_backslash,0x005c
+KEY_Z,44,ANSI_Z,0x6,44,26,26,44,44,29,VK_Z,0x5a,44,44,XK_Z,0x005a
+KEY_Z,44,ANSI_Z,0x6,44,26,26,44,44,29,VK_Z,0x5a,44,44,XK_z,0x007a
+KEY_X,45,ANSI_X,0x7,45,34,34,45,45,27,VK_X,0x58,45,45,XK_X,0x0058
+KEY_X,45,ANSI_X,0x7,45,34,34,45,45,27,VK_X,0x58,45,45,XK_x,0x0078
+KEY_C,46,ANSI_C,0x8,46,33,33,46,46,6,VK_C,0x43,46,46,XK_C,0x0043
+KEY_C,46,ANSI_C,0x8,46,33,33,46,46,6,VK_C,0x43,46,46,XK_c,0x0063
+KEY_V,47,ANSI_V,0x9,47,42,42,47,47,25,VK_V,0x56,47,47,XK_V,0x0056
+KEY_V,47,ANSI_V,0x9,47,42,42,47,47,25,VK_V,0x56,47,47,XK_v,0x0076
+KEY_B,48,ANSI_B,0xb,48,50,50,48,48,5,VK_B,0x42,48,48,XK_B,0x0042
+KEY_B,48,ANSI_B,0xb,48,50,50,48,48,5,VK_B,0x42,48,48,XK_b,0x0062
+KEY_N,49,ANSI_N,0x2d,49,49,49,49,49,17,VK_N,0x4e,49,49,XK_N,0x004e
+KEY_N,49,ANSI_N,0x2d,49,49,49,49,49,17,VK_N,0x4e,49,49,XK_n,0x006e
+KEY_M,50,ANSI_M,0x2e,50,58,58,50,50,16,VK_M,0x4d,50,50,XK_M,0x004d
+KEY_M,50,ANSI_M,0x2e,50,58,58,50,50,16,VK_M,0x4d,50,50,XK_m,0x006d
+KEY_COMMA,51,ANSI_Comma,0x2b,51,65,65,51,51,54,VK_OEM_COMMA,0xbc,51,51,XK_comma,0x002c
+KEY_DOT,52,ANSI_Period,0x2f,52,73,73,52,52,55,VK_OEM_PERIOD,0xbe,52,52,XK_period,0x002e
+KEY_SLASH,53,ANSI_Slash,0x2c,53,74,74,53,53,56,VK_OEM_2,0xbf,53,53,XK_slash,0x002f
+KEY_RIGHTSHIFT,54,RightShift,0x3c,54,89,89,54,54,229,VK_RSHIFT,0xa1,54,54,XK_Shift_R,0xffe2
+KEY_KPASTERISK,55,ANSI_KeypadMultiply,0x43,55,124,126,55,55,85,VK_MULTIPLY,0x6a,55,55,XK_multiply,0x00d7
+KEY_LEFTALT,56,Option,0x3a,56,17,25,56,56,226,VK_LMENU,0xa4,56,56,XK_Alt_L,0xffe9
+KEY_LEFTALT,56,Option,0x3a,56,17,25,56,56,226,VK_MENU,0x12,56,56,XK_Alt_L,0xffe9
+KEY_SPACE,57,Space,0x31,57,41,41,57,57,44,VK_SPACE,0x20,57,57,XK_space,0x0020
+KEY_CAPSLOCK,58,CapsLock,0x39,58,88,20,58,58,57,VK_CAPITAL,0x14,58,58,XK_Caps_Lock,0xffe5
+KEY_F1,59,F1,0x7a,59,5,7,59,59,58,VK_F1,0x70,59,59,XK_F1,0xffbe
+KEY_F2,60,F2,0x78,60,6,15,60,60,59,VK_F2,0x71,60,60,XK_F2,0xffbf
+KEY_F3,61,F3,0x63,61,4,23,61,61,60,VK_F3,0x72,61,61,XK_F3,0xffc0
+KEY_F4,62,F4,0x76,62,12,31,62,62,61,VK_F4,0x73,62,62,XK_F4,0xffc1
+KEY_F5,63,F5,0x60,63,3,39,63,63,62,VK_F5,0x74,63,63,XK_F5,0xffc2
+KEY_F6,64,F6,0x61,64,11,47,64,64,63,VK_F6,0x75,64,64,XK_F6,0xffc3
+KEY_F7,65,F7,0x62,65,259,55,65,65,64,VK_F7,0x76,65,65,XK_F7,0xffc4
+KEY_F8,66,F8,0x64,66,10,63,66,66,65,VK_F8,0x77,66,66,XK_F8,0xffc5
+KEY_F9,67,F9,0x65,67,1,71,67,67,66,VK_F9,0x78,67,67,XK_F9,0xffc6
+KEY_F10,68,F10,0x6d,68,9,79,68,68,67,VK_F10,0x79,68,68,XK_F10,0xffc7
+KEY_NUMLOCK,69,,,69,119,118,69,69,83,VK_NUMLOCK,0x90,69,69,XK_Num_Lock,0xff7f
+KEY_SCROLLLOCK,70,,,70,126,95,70,70,71,VK_SCROLL,0x91,70,70,XK_Scroll_Lock,0xff14
+KEY_KP7,71,ANSI_Keypad7,0x59,71,108,108,71,71,95,VK_NUMPAD7,0x67,71,71,XK_KP_7,0xffb7
+KEY_KP8,72,ANSI_Keypad8,0x5b,72,117,117,72,72,96,VK_NUMPAD8,0x68,72,72,XK_KP_8,0xffb8
+KEY_KP9,73,ANSI_Keypad9,0x5c,73,125,125,73,73,97,VK_NUMPAD9,0x69,73,73,XK_KP_9,0xffb9
+KEY_KPMINUS,74,ANSI_KeypadMinus,0x4e,74,123,132,74,74,86,VK_SUBTRACT,0x6d,74,74,XK_KP_Subtract,0xffad
+KEY_KP4,75,ANSI_Keypad4,0x56,75,107,107,75,75,92,VK_NUMPAD4,0x64,75,75,XK_KP_4,0xffb4
+KEY_KP5,76,ANSI_Keypad5,0x57,76,115,115,76,76,93,VK_NUMPAD5,0x65,76,76,XK_KP_5,0xffb5
+KEY_KP6,77,ANSI_Keypad6,0x58,77,116,116,77,77,94,VK_NUMPAD6,0x66,77,77,XK_KP_6,0xffb6
+KEY_KPPLUS,78,ANSI_KeypadPlus,0x45,78,121,124,78,78,87,VK_ADD,0x6b,78,78,XK_KP_Add,0xffab
+KEY_KP1,79,ANSI_Keypad1,0x53,79,105,105,79,79,89,VK_NUMPAD1,0x61,79,79,XK_KP_1,0xffb1
+KEY_KP2,80,ANSI_Keypad2,0x54,80,114,114,80,80,90,VK_NUMPAD2,0x62,80,80,XK_KP_2,0xffb2
+KEY_KP3,81,ANSI_Keypad3,0x55,81,122,122,81,81,91,VK_NUMPAD3,0x63,81,81,XK_KP_3,0xffb3
+KEY_KP0,82,ANSI_Keypad0,0x52,82,112,112,82,82,98,VK_NUMPAD0,0x60,82,82,XK_KP_0,0xffb0
+KEY_KPDOT,83,ANSI_KeypadDecimal,0x41,83,113,113,83,83,99,VK_DECIMAL,0x6e,83,83,XK_KP_Decimal,0xffae
+,84,,,,,,,84,,,,,
+KEY_ZENKAKUHANKAKU,85,,,118,95,,,118,148,,,,
+KEY_102ND,86,,,86,97,19,,86,100,VK_OEM_102,0xe1,,
+KEY_F11,87,F11,0x67,87,120,86,101,87,68,VK_F11,0x7a,,
+KEY_F12,88,F12,0x6f,88,7,94,102,88,69,VK_F12,0x7b,,
+KEY_RO,89,,,115,81,,,115,135,,,,
+KEY_KATAKANA,90,JIS_Kana????,0x68,120,99,,,120,146,VK_KANA,0x15,,
+KEY_HIRAGANA,91,,,119,98,,,119,147,,,,
+KEY_HENKAN,92,,,121,100,134,,121,138,,,,
+KEY_KATAKANAHIRAGANA,93,,,112,19,135,,112,136,,,0xc8,0xc8
+KEY_MUHENKAN,94,,,123,103,133,,123,139,,,,
+KEY_KPJPCOMMA,95,JIS_KeypadComma,0x5f,92,39,,,92,140,,,,,XK_KP_Separator,0xffac
+KEY_KPENTER,96,ANSI_KeypadEnter,0x4c,,158,121,,284,88,,,0x64,0x64,XK_KP_Enter,0xff8d
+KEY_RIGHTCTRL,97,RightControl,0x3e,,,88,,285,228,VK_RCONTROL,0xa3,0x65,0x65,XK_Control_R,0xffe4
+KEY_KPSLASH,98,ANSI_KeypadDivide,0x4b,,181,119,,309,84,VK_DIVIDE,0x6f,0x68,0x68,XK_KP_Divide,0xffaf
+KEY_SYSRQ,99,,,84,260,87,,84,70,"VK_SNAPSHOT ???",0x2c,0x67,0x67,XK_Sys_Req,0xff15
+KEY_RIGHTALT,100,RightOption,0x3d,,,57,,312,230,VK_RMENU,0xa5,0x69,0x69,XK_Alt_R,0xffea
+KEY_LINEFEED,101,,,,,,,91,,,,,
+KEY_HOME,102,Home,0x73,,224,110,,327,74,VK_HOME,0x24,0x59,0x59,XK_Home,0xff50
+KEY_UP,103,UpArrow,0x7e,,236,99,109,328,82,VK_UP,0x26,0x5a,0x5a,XK_Up,0xff52
+KEY_PAGEUP,104,PageUp,0x74,,201,111,,329,75,VK_PRIOR,0x21,0x5b,0x5b,XK_Page_Up,0xff55
+KEY_LEFT,105,LeftArrow,0x7b,,203,97,111,331,80,VK_LEFT,0x25,0x5c,0x5c,XK_Left,0xff51
+KEY_RIGHT,106,RightArrow,0x7c,,205,106,112,333,79,VK_RIGHT,0x27,0x5e,0x5e,XK_Right,0xff53
+KEY_END,107,End,0x77,,225,101,,335,77,VK_END,0x23,0x5f,0x5f,XK_End,0xff57
+KEY_DOWN,108,DownArrow,0x7d,,254,96,110,336,81,VK_DOWN,0x28,0x60,0x60,XK_Down,0xff54
+KEY_PAGEDOWN,109,PageDown,0x79,,243,109,,337,78,VK_NEXT,0x22,0x61,0x61,XK_Page_Down,0xff56
+KEY_INSERT,110,,,,210,103,107,338,73,VK_INSERT,0x2d,0x62,0x62,XK_Insert,0xff63
+KEY_DELETE,111,ForwardDelete,0x75,,244,100,108,339,76,VK_DELETE,0x2e,0x63,0x63,XK_Delete,0xffff
+KEY_MACRO,112,,,,239,142,,367,,,,,
+KEY_MUTE,113,Mute,0x4a,,251,156,,288,239,VK_VOLUME_MUTE,0xad,,,
+KEY_VOLUMEDOWN,114,VolumeDown,0x49,,,157,,302,238,VK_VOLUME_DOWN,0xae,,
+KEY_VOLUMEUP,115,VolumeUp,0x48,,233,149,,304,237,VK_VOLUME_UP,0xaf,,
+KEY_POWER,116,,,,,,,350,102,,,,
+KEY_KPEQUAL,117,ANSI_KeypadEquals,0x51,89,15,,,89,103,,,0x76,0x76,XK_KP_Equal,0xffbd
+KEY_KPPLUSMINUS,118,,,,206,,,334,,,,,
+KEY_PAUSE,119,,,,198,98,,326,72,VK_PAUSE,0x013,0x66,0x66,XK_Pause,0xff13
+KEY_SCALE,120,,,,,,,267,,,,,
+KEY_KPCOMMA,121,ANSI_KeypadClear????,0x47,126,109,,,126,133,VK_SEPARATOR??,0x6c,,
+KEY_HANGEUL,122,,,,,,,,144,VK_HANGEUL,0x15,,
+KEY_HANJA,123,,,,,,,269,145,VK_HANJA,0x19,,
+KEY_YEN,124,JIS_Yen,0x5d,125,106,,,125,137,,,0x7d,0x7d
+KEY_LEFTMETA,125,Command,0x37,,,139,,347,227,VK_LWIN,0x5b,0x6b,0x6b,XK_Meta_L,0xffe7
+KEY_RIGHTMETA,126,,,,,140,,348,231,VK_RWIN,0x5c,0x6c,0x6c,XK_Meta_R,0xffe8
+KEY_COMPOSE,127,Function,0x3f,,,141,,349,101,VK_APPS,0x5d,0x6d,0x6d
+KEY_STOP,128,,,,,10,,360,243,VK_BROWSER_STOP,0xa9,,
+KEY_AGAIN,129,,,,,11,,261,121,,,,
+KEY_PROPS,130,,,,,12,,262,118,,,,
+KEY_UNDO,131,,,,,16,,263,122,,,,
+KEY_FRONT,132,,,,,,,268,119,,,,
+KEY_COPY,133,,,,,24,,376,124,,,,
+KEY_OPEN,134,,,,,32,,100,116,,,,
+KEY_PASTE,135,,,,,40,,101,125,,,,
+KEY_FIND,136,,,,,48,,321,244,,,,
+KEY_CUT,137,,,,,56,,316,123,,,,
+KEY_HELP,138,,,,,9,,373,117,VK_HELP,0x2f,,,XK_Help,0xff6a
+KEY_MENU,139,,,,,145,,286,,,,,
+KEY_CALC,140,,,,174,163,,289,251,,,,
+KEY_SETUP,141,,,,,,,102,,,,,
+KEY_SLEEP,142,,,,,,,351,248,VK_SLEEP,0x5f,,
+KEY_WAKEUP,143,,,,,,,355,,,,,
+KEY_FILE,144,,,,,,,103,,,,,
+KEY_SENDFILE,145,,,,,,,104,,,,,
+KEY_DELETEFILE,146,,,,,,,105,,,,,
+KEY_XFER,147,,,,,162,,275,,,,,
+KEY_PROG1,148,,,,,160,,287,,,,,
+KEY_PROG2,149,,,,,161,,279,,,,,
+KEY_WWW,150,,,,,,,258,240,,,,
+KEY_MSDOS,151,,,,,,,106,,,,,
+KEY_SCREENLOCK,152,,,,,150,,274,249,,,,
+KEY_DIRECTION,153,,,,,,,107,,,,,
+KEY_CYCLEWINDOWS,154,,,,,155,,294,,,,,
+KEY_MAIL,155,,,,,,,364,,,,,
+KEY_BOOKMARKS,156,,,,,,,358,,,,,
+KEY_COMPUTER,157,,,,,,,363,,,,,
+KEY_BACK,158,,,,,,,362,241,VK_BROWSER_BACK,0xa6,,
+KEY_FORWARD,159,,,,,,,361,242,VK_BROWSER_FORWARD,0xa7,,
+KEY_CLOSECD,160,,,,,154,,291,,,,,
+KEY_EJECTCD,161,,,,,,,108,236,,,,
+KEY_EJECTCLOSECD,162,,,,,,,381,,,,,
+KEY_NEXTSONG,163,,,,241,147,,281,235,VK_MEDIA_NEXT_TRACK,0xb0,,
+KEY_PLAYPAUSE,164,,,,173,,,290,232,VK_MEDIA_PLAY_PAUSE,0xb3,,
+KEY_PREVIOUSSONG,165,,,,250,148,,272,234,VK_MEDIA_PREV_TRACK,0xb1,,
+KEY_STOPCD,166,,,,164,152,,292,233,VK_MEDIA_STOP,0xb2,,
+KEY_RECORD,167,,,,,158,,305,,,,,
+KEY_REWIND,168,,,,,159,,280,,,,,
+KEY_PHONE,169,,,,,,,99,,,,,
+KEY_ISO,170,ISO_Section,0xa,,,,,112,,,,,
+KEY_CONFIG,171,,,,,,,257,,,,,
+KEY_HOMEPAGE,172,,,,178,151,,306,,VK_BROWSER_HOME,0xac,,
+KEY_REFRESH,173,,,,,,,359,250,VK_BROWSER_REFRESH,0xa8,,
+KEY_EXIT,174,,,,,,,113,,,,,
+KEY_MOVE,175,,,,,,,114,,,,,
+KEY_EDIT,176,,,,,,,264,247,,,,
+KEY_SCROLLUP,177,,,,,,,117,245,,,,
+KEY_SCROLLDOWN,178,,,,,,,271,246,,,,
+KEY_KPLEFTPAREN,179,,,,,,,374,182,,,,
+KEY_KPRIGHTPAREN,180,,,,,,,379,183,,,,
+KEY_NEW,181,,,,,,,265,,,,,
+KEY_REDO,182,,,,,,,266,,,,,
+KEY_F13,183,F13,0x69,93,47,127,,93,104,VK_F13,0x7c,0x6e,0x6e
+KEY_F14,184,F14,0x6b,94,55,128,,94,105,VK_F14,0x7d,0x6f,0x6f
+KEY_F15,185,F15,0x71,95,63,129,,95,106,VK_F15,0x7e,0x70,0x70
+KEY_F16,186,F16,0x6a,,,130,,85,107,VK_F16,0x7f,0x71,0x71
+KEY_F17,187,F17,0x40,,,131,,259,108,VK_F17,0x80,0x72,0x72
+KEY_F18,188,F18,0x4f,,,,,375,109,VK_F18,0x81,,
+KEY_F19,189,F19,0x50,,,,,260,110,VK_F19,0x82,,
+KEY_F20,190,F20,0x5a,,,,,90,111,VK_F20,0x83,,
+KEY_F21,191,,,,,,,116,112,VK_F21,0x84,,
+KEY_F22,192,,,,,,,377,113,VK_F22,0x85,,
+KEY_F23,193,,,,,,,109,114,VK_F23,0x86,,
+KEY_F24,194,,,,,,,111,115,VK_F24,0x87,,
+,195,,,,,,,277,,,,,
+,196,,,,,,,278,,,,,
+,197,,,,,,,282,,,,,
+,198,,,,,,,283,,,,,
+,199,,,,,,,295,,,,,
+KEY_PLAYCD,200,,,,,,,296,,,,,
+KEY_PAUSECD,201,,,,,,,297,,,,,
+KEY_PROG3,202,,,,,,,299,,,,,
+KEY_PROG4,203,,,,,,,300,,,,,
+KEY_DASHBOARD,204,,,,,,,301,,,,,
+KEY_SUSPEND,205,,,,,,,293,,,,,
+KEY_CLOSE,206,,,,,,,303,,,,,
+KEY_PLAY,207,,,,,,,307,,VK_PLAY,0xfa,,
+KEY_FASTFORWARD,208,,,,,,,308,,,,,
+KEY_BASSBOOST,209,,,,,,,310,,,,,
+KEY_PRINT,210,,,,,,,313,,VK_PRINT,0x2a,,
+KEY_HP,211,,,,,,,314,,,,,
+KEY_CAMERA,212,,,,,,,315,,,,,
+KEY_SOUND,213,,,,,,,317,,,,,
+KEY_QUESTION,214,,,,,,,318,,,,,
+KEY_EMAIL,215,,,,,,,319,,VK_LAUNCH_MAIL,0xb4,,
+KEY_CHAT,216,,,,,,,320,,,,,
+KEY_SEARCH,217,,,,,,,357,,VK_BROWSER_SEARCH,0xaa,,
+KEY_CONNECT,218,,,,,,,322,,,,,
+KEY_FINANCE,219,,,,,,,323,,,,,
+KEY_SPORT,220,,,,,,,324,,,,,
+KEY_SHOP,221,,,,,,,325,,,,,
+KEY_ALTERASE,222,,,,,,,276,,,,,
+KEY_CANCEL,223,,,,,,,330,,,,,
+KEY_BRIGHTNESSDOWN,224,,,,,,,332,,,,,
+KEY_BRIGHTNESSUP,225,,,,,,,340,,,,,
+KEY_MEDIA,226,,,,,,,365,,,,,
+KEY_SWITCHVIDEOMODE,227,,,,,,,342,,,,,
+KEY_KBDILLUMTOGGLE,228,,,,,,,343,,,,,
+KEY_KBDILLUMDOWN,229,,,,,,,344,,,,,
+KEY_KBDILLUMUP,230,,,,,,,345,,,,,
+KEY_SEND,231,,,,,,,346,,,,,
+KEY_REPLY,232,,,,,,,356,,,,,
+KEY_FORWARDMAIL,233,,,,,,,270,,,,,
+KEY_SAVE,234,,,,,,,341,,,,,
+KEY_DOCUMENTS,235,,,,,,,368,,,,,
+KEY_BATTERY,236,,,,,,,369,,,,,
+KEY_BLUETOOTH,237,,,,,,,370,,,,,
+KEY_WLAN,238,,,,,,,371,,,,,
+KEY_UWB,239,,,,,,,372,,,,,
+KEY_UNKNOWN,240,,,,,,,,,,,,
+KEY_VIDEO_NEXT,241,,,,,,,,,,,,
+KEY_VIDEO_PREV,242,,,,,,,,,,,,
+KEY_BRIGHTNESS_CYCLE,243,,,,,,,,,,,,
+KEY_BRIGHTNESS_ZERO,244,,,,,,,,,,,,
+KEY_DISPLAY_OFF,245,,,,,,,,,,,,
+KEY_WIMAX,246,,,,,,,,,,,,
+,247,,,,,,,,,,,,
+,248,,,,,,,,,,,,
+,249,,,,,,,,,,,,
+,250,,,,,,,,,,,,
+,251,,,,,,,,,,,,
+,252,,,,,,,,,,,,
+,253,,,,,,,,,,,,
+,254,,,,,,,,,,,,
+,255,,,,182,,,,,,,,
+BTN_MISC,0x100,,,,,,,,,,,,
+BTN_0,0x100,,,,,,,,,VK_LBUTTON,0x01,,
+BTN_1,0x101,,,,,,,,,VK_RBUTTON,0x02,,
+BTN_2,0x102,,,,,,,,,VK_MBUTTON,0x04,,
+BTN_3,0x103,,,,,,,,,VK_XBUTTON1,0x05,,
+BTN_4,0x104,,,,,,,,,VK_XBUTTON2,0x06,,
+BTN_5,0x105,,,,,,,,,,,,
+BTN_6,0x106,,,,,,,,,,,,
+BTN_7,0x107,,,,,,,,,,,,
+BTN_8,0x108,,,,,,,,,,,,
+BTN_9,0x109,,,,,,,,,,,,
+BTN_MOUSE,0x110,,,,,,,,,,,,
+BTN_LEFT,0x110,,,,,,,,,,,,
+BTN_RIGHT,0x111,,,,,,,,,,,,
+BTN_MIDDLE,0x112,,,,,,,,,,,,
+BTN_SIDE,0x113,,,,,,,,,,,,
+BTN_EXTRA,0x114,,,,,,,,,,,,
+BTN_FORWARD,0x115,,,,,,,,,,,,
+BTN_BACK,0x116,,,,,,,,,,,,
+BTN_TASK,0x117,,,,,,,,,,,,
+BTN_JOYSTICK,0x120,,,,,,,,,,,,
+BTN_TRIGGER,0x120,,,,,,,,,,,,
+BTN_THUMB,0x121,,,,,,,,,,,,
+BTN_THUMB2,0x122,,,,,,,,,,,,
+BTN_TOP,0x123,,,,,,,,,,,,
+BTN_TOP2,0x124,,,,,,,,,,,,
+BTN_PINKIE,0x125,,,,,,,,,,,,
+BTN_BASE,0x126,,,,,,,,,,,,
+BTN_BASE2,0x127,,,,,,,,,,,,
+BTN_BASE3,0x128,,,,,,,,,,,,
+BTN_BASE4,0x129,,,,,,,,,,,,
+BTN_BASE5,0x12a,,,,,,,,,,,,
+BTN_BASE6,0x12b,,,,,,,,,,,,
+BTN_DEAD,0x12f,,,,,,,,,,,,
+BTN_GAMEPAD,0x130,,,,,,,,,,,,
+BTN_A,0x130,,,,,,,,,,,,
+BTN_B,0x131,,,,,,,,,,,,
+BTN_C,0x132,,,,,,,,,,,,
+BTN_X,0x133,,,,,,,,,,,,
+BTN_Y,0x134,,,,,,,,,,,,
+BTN_Z,0x135,,,,,,,,,,,,
+BTN_TL,0x136,,,,,,,,,,,,
+BTN_TR,0x137,,,,,,,,,,,,
+BTN_TL2,0x138,,,,,,,,,,,,
+BTN_TR2,0x139,,,,,,,,,,,,
+BTN_SELECT,0x13a,,,,,,,,,,,,
+BTN_START,0x13b,,,,,,,,,,,,
+BTN_MODE,0x13c,,,,,,,,,,,,
+BTN_THUMBL,0x13d,,,,,,,,,,,,
+BTN_THUMBR,0x13e,,,,,,,,,,,,
+BTN_DIGI,0x140,,,,,,,,,,,,
+BTN_TOOL_PEN,0x140,,,,,,,,,,,,
+BTN_TOOL_RUBBER,0x141,,,,,,,,,,,,
+BTN_TOOL_BRUSH,0x142,,,,,,,,,,,,
+BTN_TOOL_PENCIL,0x143,,,,,,,,,,,,
+BTN_TOOL_AIRBRUSH,0x144,,,,,,,,,,,,
+BTN_TOOL_FINGER,0x145,,,,,,,,,,,,
+BTN_TOOL_MOUSE,0x146,,,,,,,,,,,,
+BTN_TOOL_LENS,0x147,,,,,,,,,,,,
+BTN_TOUCH,0x14a,,,,,,,,,,,,
+BTN_STYLUS,0x14b,,,,,,,,,,,,
+BTN_STYLUS2,0x14c,,,,,,,,,,,,
+BTN_TOOL_DOUBLETAP,0x14d,,,,,,,,,,,,
+BTN_TOOL_TRIPLETAP,0x14e,,,,,,,,,,,,
+BTN_TOOL_QUADTAP,0x14f,,,,,,,,,,,,
+BTN_WHEEL,0x150,,,,,,,,,,,,
+BTN_GEAR_DOWN,0x150,,,,,,,,,,,,
+BTN_GEAR_UP,0x151,,,,,,,,,,,,
+KEY_OK,0x160,,,,,,,,,,,,
+KEY_SELECT,0x161,,,,,,,,,VK_SELECT,0x29,,,XK_Select,0xff60
+KEY_GOTO,0x162,,,,,,,,,,,,
+KEY_CLEAR,0x163,,,,,,,,,,,,
+KEY_POWER2,0x164,,,,,,,,,,,,
+KEY_OPTION,0x165,,,,,,,,,,,,
+KEY_INFO,0x166,,,,,,,,,,,,
+KEY_TIME,0x167,,,,,,,,,,,,
+KEY_VENDOR,0x168,,,,,,,,,,,,
+KEY_ARCHIVE,0x169,,,,,,,,,,,,
+KEY_PROGRAM,0x16a,,,,,,,,,,,,
+KEY_CHANNEL,0x16b,,,,,,,,,,,,
+KEY_FAVORITES,0x16c,,,,,,,,,VK_BROWSER_FAVOURITES,0xab,,
+KEY_EPG,0x16d,,,,,,,,,,,,
+KEY_PVR,0x16e,,,,,,,,,,,,
+KEY_MHP,0x16f,,,,,,,,,,,,
+KEY_LANGUAGE,0x170,,,,,,,,,,,,
+KEY_TITLE,0x171,,,,,,,,,,,,
+KEY_SUBTITLE,0x172,,,,,,,,,,,,
+KEY_ANGLE,0x173,,,,,,,,,,,,
+KEY_ZOOM,0x174,,,,,,,,,VK_ZOOM,0xfb,,
+KEY_MODE,0x175,,,,,,,,,,,,
+KEY_KEYBOARD,0x176,,,,,,,,,,,,
+KEY_SCREEN,0x177,,,,,,,,,,,,
+KEY_PC,0x178,,,,,,,,,,,,
+KEY_TV,0x179,,,,,,,,,,,,
+KEY_TV2,0x17a,,,,,,,,,,,,
+KEY_VCR,0x17b,,,,,,,,,,,,
+KEY_VCR2,0x17c,,,,,,,,,,,,
+KEY_SAT,0x17d,,,,,,,,,,,,
+KEY_SAT2,0x17e,,,,,,,,,,,,
+KEY_CD,0x17f,,,,,,,,,,,,
+KEY_TAPE,0x180,,,,,,,,,,,,
+KEY_RADIO,0x181,,,,,,,,,,,,
+KEY_TUNER,0x182,,,,,,,,,,,,
+KEY_PLAYER,0x183,,,,,,,,,,,,
+KEY_TEXT,0x184,,,,,,,,,,,,
+KEY_DVD,0x185,,,,,,,,,,,,
+KEY_AUX,0x186,,,,,,,,,,,,
+KEY_MP3,0x187,,,,,,,,,,,,
+KEY_AUDIO,0x188,,,,,,,,,,,,
+KEY_VIDEO,0x189,,,,,,,,,,,,
+KEY_DIRECTORY,0x18a,,,,,,,,,,,,
+KEY_LIST,0x18b,,,,,,,,,,,,
+KEY_MEMO,0x18c,,,,,,,,,,,,
+KEY_CALENDAR,0x18d,,,,,,,,,,,,
+KEY_RED,0x18e,,,,,,,,,,,,
+KEY_GREEN,0x18f,,,,,,,,,,,,
+KEY_YELLOW,0x190,,,,,,,,,,,,
+KEY_BLUE,0x191,,,,,,,,,,,,
+KEY_CHANNELUP,0x192,,,,,,,,,,,,
+KEY_CHANNELDOWN,0x193,,,,,,,,,,,,
+KEY_FIRST,0x194,,,,,,,,,,,,
+KEY_LAST,0x195,,,,,,,,,,,,
+KEY_AB,0x196,,,,,,,,,,,,
+KEY_NEXT,0x197,,,,,,,,,,,,
+KEY_RESTART,0x198,,,,,,,,,,,,
+KEY_SLOW,0x199,,,,,,,,,,,,
+KEY_SHUFFLE,0x19a,,,,,,,,,,,,
+KEY_BREAK,0x19b,,,,,,,,,,,,
+KEY_PREVIOUS,0x19c,,,,,,,,,,,,
+KEY_DIGITS,0x19d,,,,,,,,,,,,
+KEY_TEEN,0x19e,,,,,,,,,,,,
+KEY_TWEN,0x19f,,,,,,,,,,,,
+KEY_VIDEOPHONE,0x1a0,,,,,,,,,,,,
+KEY_GAMES,0x1a1,,,,,,,,,,,,
+KEY_ZOOMIN,0x1a2,,,,,,,,,,,,
+KEY_ZOOMOUT,0x1a3,,,,,,,,,,,,
+KEY_ZOOMRESET,0x1a4,,,,,,,,,,,,
+KEY_WORDPROCESSOR,0x1a5,,,,,,,,,,,,
+KEY_EDITOR,0x1a6,,,,,,,,,,,,
+KEY_SPREADSHEET,0x1a7,,,,,,,,,,,,
+KEY_GRAPHICSEDITOR,0x1a8,,,,,,,,,,,,
+KEY_PRESENTATION,0x1a9,,,,,,,,,,,,
+KEY_DATABASE,0x1aa,,,,,,,,,,,,
+KEY_NEWS,0x1ab,,,,,,,,,,,,
+KEY_VOICEMAIL,0x1ac,,,,,,,,,,,,
+KEY_ADDRESSBOOK,0x1ad,,,,,,,,,,,,
+KEY_MESSENGER,0x1ae,,,,,,,,,,,,
+KEY_DISPLAYTOGGLE,0x1af,,,,,,,,,,,,
+KEY_SPELLCHECK,0x1b0,,,,,,,,,,,,
+KEY_LOGOFF,0x1b1,,,,,,,,,,,,
+KEY_DOLLAR,0x1b2,,,,,,,,,,,,
+KEY_EURO,0x1b3,,,,,,,,,,,,
+KEY_FRAMEBACK,0x1b4,,,,,,,,,,,,
+KEY_FRAMEFORWARD,0x1b5,,,,,,,,,,,,
+KEY_CONTEXT_MENU,0x1b6,,,,,,,,,,,,
+KEY_MEDIA_REPEAT,0x1b7,,,,,,,,,,,,
+KEY_DEL_EOL,0x1c0,,,,,,,,,,,,
+KEY_DEL_EOS,0x1c1,,,,,,,,,,,,
+KEY_INS_LINE,0x1c2,,,,,,,,,,,,
+KEY_DEL_LINE,0x1c3,,,,,,,,,,,,
+KEY_FN,0x1d0,,,,,,,,,,,,
+KEY_FN_ESC,0x1d1,,,,,,,,,,,,
+KEY_FN_F1,0x1d2,,,,,,,,,,,,
+KEY_FN_F2,0x1d3,,,,,,,,,,,,
+KEY_FN_F3,0x1d4,,,,,,,,,,,,
+KEY_FN_F4,0x1d5,,,,,,,,,,,,
+KEY_FN_F5,0x1d6,,,,,,,,,,,,
+KEY_FN_F6,0x1d7,,,,,,,,,,,,
+KEY_FN_F7,0x1d8,,,,,,,,,,,,
+KEY_FN_F8,0x1d9,,,,,,,,,,,,
+KEY_FN_F9,0x1da,,,,,,,,,,,,
+KEY_FN_F10,0x1db,,,,,,,,,,,,
+KEY_FN_F11,0x1dc,,,,,,,,,,,,
+KEY_FN_F12,0x1dd,,,,,,,,,,,,
+KEY_FN_1,0x1de,,,,,,,,,,,,
+KEY_FN_2,0x1df,,,,,,,,,,,,
+KEY_FN_D,0x1e0,,,,,,,,,,,,
+KEY_FN_E,0x1e1,,,,,,,,,,,,
+KEY_FN_F,0x1e2,,,,,,,,,,,,
+KEY_FN_S,0x1e3,,,,,,,,,,,,
+KEY_FN_B,0x1e4,,,,,,,,,,,,
+KEY_BRL_DOT1,0x1f1,,,,,,,,,,,,
+KEY_BRL_DOT2,0x1f2,,,,,,,,,,,,
+KEY_BRL_DOT3,0x1f3,,,,,,,,,,,,
+KEY_BRL_DOT4,0x1f4,,,,,,,,,,,,
+KEY_BRL_DOT5,0x1f5,,,,,,,,,,,,
+KEY_BRL_DOT6,0x1f6,,,,,,,,,,,,
+KEY_BRL_DOT7,0x1f7,,,,,,,,,,,,
+KEY_BRL_DOT8,0x1f8,,,,,,,,,,,,
+KEY_BRL_DOT9,0x1f9,,,,,,,,,,,,
+KEY_BRL_DOT10,0x1fa,,,,,,,,,,,,
+KEY_NUMERIC_0,0x200,,,,,,,,,,,,
+KEY_NUMERIC_1,0x201,,,,,,,,,,,,
+KEY_NUMERIC_2,0x202,,,,,,,,,,,,
+KEY_NUMERIC_3,0x203,,,,,,,,,,,,
+KEY_NUMERIC_4,0x204,,,,,,,,,,,,
+KEY_NUMERIC_5,0x205,,,,,,,,,,,,
+KEY_NUMERIC_6,0x206,,,,,,,,,,,,
+KEY_NUMERIC_7,0x207,,,,,,,,,,,,
+KEY_NUMERIC_8,0x208,,,,,,,,,,,,
+KEY_NUMERIC_9,0x209,,,,,,,,,,,,
+KEY_NUMERIC_STAR,0x20a,,,,,,,,,,,,
+KEY_NUMERIC_POUND,0x20b,,,,,,,,,,,,
+KEY_RFKILL,0x20c,,,,,,,,,,,,
diff --git a/src/map-file b/src/map-file
new file mode 100644
index 0000000..d5a073f
--- /dev/null
+++ b/src/map-file
@@ -0,0 +1,139 @@
+SPICEGTK_1 {
+global:
+spice_audio_get;
+spice_audio_get_type;
+spice_audio_new;
+spice_channel_connect;
+spice_channel_destroy;
+spice_channel_disconnect;
+spice_channel_event_get_type;
+spice_channel_flush_async;
+spice_channel_flush_finish;
+spice_channel_get_error;
+spice_channel_get_type;
+spice_channel_new;
+spice_channel_open_fd;
+spice_channel_set_capability;
+spice_channel_string_to_type;
+spice_channel_test_capability;
+spice_channel_test_common_capability;
+spice_channel_type_to_string;
+spice_client_error_quark;
+spice_cursor_channel_get_type;
+spice_display_channel_get_type;
+spice_display_copy_to_guest;
+spice_display_get_grab_keys;
+spice_display_get_pixbuf;
+spice_display_get_primary;
+spice_display_get_type;
+spice_display_key_event_get_type;
+spice_display_mouse_ungrab;
+spice_display_new;
+spice_display_new_with_monitor;
+spice_display_paste_from_guest;
+spice_display_send_keys;
+spice_display_set_grab_keys;
+spice_get_option_group;
+spice_grab_sequence_as_string;
+spice_grab_sequence_copy;
+spice_grab_sequence_free;
+spice_grab_sequence_get_type;
+spice_grab_sequence_new;
+spice_grab_sequence_new_from_string;
+spice_g_signal_connect_object;
+spice_gtk_session_copy_to_guest;
+spice_gtk_session_get;
+spice_gtk_session_get_type;
+spice_gtk_session_paste_from_guest;
+spice_inputs_button_press;
+spice_inputs_button_release;
+spice_inputs_channel_get_type;
+spice_inputs_key_press;
+spice_inputs_key_press_and_release;
+spice_inputs_key_release;
+spice_inputs_lock_get_type;
+spice_inputs_motion;
+spice_inputs_position;
+spice_inputs_set_key_locks;
+spice_main_agent_test_capability;
+spice_main_channel_get_type;
+spice_main_clipboard_grab;
+spice_main_clipboard_notify;
+spice_main_clipboard_release;
+spice_main_clipboard_request;
+spice_main_clipboard_selection_grab;
+spice_main_clipboard_selection_notify;
+spice_main_clipboard_selection_release;
+spice_main_clipboard_selection_request;
+spice_main_file_copy_async;
+spice_main_file_copy_finish;
+spice_main_send_monitor_config;
+spice_main_set_display;
+spice_main_set_display_enabled;
+spice_main_update_display;
+spice_playback_channel_get_type;
+spice_playback_channel_set_delay;
+spice_port_channel_get_type;
+spice_port_event;
+spice_port_write_async;
+spice_port_write_finish;
+spice_record_channel_get_type;
+spice_record_send_data;
+spice_session_connect;
+spice_session_disconnect;
+spice_session_get_channels;
+spice_session_get_proxy_uri;
+spice_session_get_read_only;
+spice_session_get_type;
+spice_session_has_channel_type;
+spice_session_is_for_migration;
+spice_session_migration_get_type;
+spice_session_new;
+spice_session_open_fd;
+spice_session_verify_get_type;
+spice_set_session_option;
+spice_smartcard_channel_get_type;
+spice_smartcard_manager_get;
+spice_smartcard_manager_get_readers;
+spice_smartcard_manager_get_type;
+spice_smartcard_manager_insert_card;
+spice_smartcard_manager_remove_card;
+spice_smartcard_reader_get_type;
+spice_smartcard_reader_insert_card;
+spice_smartcard_reader_is_software;
+spice_smartcard_reader_remove_card;
+spice_uri_get_hostname;
+spice_uri_get_password;
+spice_uri_get_port;
+spice_uri_get_scheme;
+spice_uri_get_type;
+spice_uri_get_user;
+spice_uri_set_hostname;
+spice_uri_set_password;
+spice_uri_set_port;
+spice_uri_set_scheme;
+spice_uri_set_user;
+spice_uri_to_string;
+spice_usb_device_get_description;
+spice_usb_device_get_libusb_device;
+spice_usb_device_get_type;
+spice_usb_device_manager_can_redirect_device;
+spice_usb_device_manager_connect_device_async;
+spice_usb_device_manager_connect_device_finish;
+spice_usb_device_manager_disconnect_device;
+spice_usb_device_manager_get;
+spice_usb_device_manager_get_devices;
+spice_usb_device_manager_get_devices_with_filter;
+spice_usb_device_manager_get_type;
+spice_usb_device_manager_is_device_connected;
+spice_usb_device_widget_get_type;
+spice_usb_device_widget_new;
+spice_usbredir_channel_get_type;
+spice_util_get_debug;
+spice_util_get_version_string;
+spice_util_set_debug;
+spice_uuid_to_string;
+spice_webdav_channel_get_type;
+local:
+*;
+};
diff --git a/src/smartcard-manager-priv.h b/src/smartcard-manager-priv.h
new file mode 100644
index 0000000..409c1c5
--- /dev/null
+++ b/src/smartcard-manager-priv.h
@@ -0,0 +1,37 @@
+/* -*- 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 __SMARTCARD_MANAGER_PRIV_H__
+#define __SMARTCARD_MANAGER_PRIV_H__
+
+#include "config.h"
+#include <gio/gio.h>
+#include "spice-session.h"
+
+G_BEGIN_DECLS
+
+void spice_smartcard_manager_init_async(SpiceSession *session,
+                                        GCancellable *cancellable,
+                                        GAsyncReadyCallback callback,
+                                        gpointer opaque);
+gboolean spice_smartcard_manager_init_finish(SpiceSession *session,
+                                             GAsyncResult *result,
+                                             GError **err);
+
+G_END_DECLS
+
+#endif /* __SMARTCARD_MANAGER_PRIV_H__ */
diff --git a/src/smartcard-manager.c b/src/smartcard-manager.c
new file mode 100644
index 0000000..9e228e9
--- /dev/null
+++ b/src/smartcard-manager.c
@@ -0,0 +1,737 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 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-object.h>
+#include <string.h>
+
+#include "glib-compat.h"
+
+#ifdef USE_SMARTCARD
+#include <vcard_emul.h>
+#include <vevent.h>
+#include <vreader.h>
+#endif
+
+#include "spice-client.h"
+#include "smartcard-manager.h"
+#include "smartcard-manager-priv.h"
+#include "spice-marshal.h"
+
+/**
+ * SECTION:smartcard-manager
+ * @short_description: smartcard management
+ * @title: Spice Smartcard Manager
+ * @section_id:
+ * @see_also:
+ * @stability: Stable
+ * @include: smartcard-manager.h
+ *
+ * #SpiceSmartcardManager monitors smartcard reader plugging/unplugging,
+ * and smartcard insertions/removals. It also provides methods to handle
+ * software smartcards (to emulate a smartcard reader/smartcard on the
+ * guest using 3 certificates available to the client).
+ */
+
+/* ------------------------------------------------------------------ */
+/* gobject glue                                                       */
+
+#define SPICE_SMARTCARD_MANAGER_GET_PRIVATE(obj)                                  \
+    (G_TYPE_INSTANCE_GET_PRIVATE ((obj), SPICE_TYPE_SMARTCARD_MANAGER, SpiceSmartcardManagerPrivate))
+
+struct _SpiceSmartcardManagerPrivate {
+    guint monitor_id;
+
+    /* software smartcard reader, the certificates to use for this reader
+     * were given at the channel creation time. This reader has no physical
+     * existence, it's all controlled by explicit software
+     * insertion/removal of cards
+     */
+#ifdef USE_SMARTCARD
+    VReader *software_reader;
+#endif
+};
+
+G_DEFINE_TYPE(SpiceSmartcardManager, spice_smartcard_manager, G_TYPE_OBJECT)
+#ifdef USE_SMARTCARD
+G_DEFINE_BOXED_TYPE(VReader, spice_smartcard_reader, vreader_reference, vreader_free)
+#else
+typedef GObject VReader;
+G_DEFINE_BOXED_TYPE(VReader, spice_smartcard_reader, g_object_ref, g_object_unref)
+#endif
+
+/* Properties */
+enum {
+    PROP_0,
+};
+
+/* Signals */
+enum {
+    SPICE_SMARTCARD_MANAGER_READER_ADDED,
+    SPICE_SMARTCARD_MANAGER_READER_REMOVED,
+    SPICE_SMARTCARD_MANAGER_CARD_INSERTED,
+    SPICE_SMARTCARD_MANAGER_CARD_REMOVED,
+
+    SPICE_SMARTCARD_MANAGER_LAST_SIGNAL,
+};
+
+static guint signals[SPICE_SMARTCARD_MANAGER_LAST_SIGNAL];
+
+#ifdef USE_SMARTCARD
+typedef gboolean (*SmartcardSourceFunc)(VEvent *event, gpointer user_data);
+static gboolean smartcard_monitor_dispatch(VEvent *event, gpointer user_data);
+#endif
+
+/* ------------------------------------------------------------------ */
+
+static void spice_smartcard_manager_init(SpiceSmartcardManager *smartcard_manager)
+{
+    SpiceSmartcardManagerPrivate *priv;
+
+    priv = SPICE_SMARTCARD_MANAGER_GET_PRIVATE(smartcard_manager);
+    smartcard_manager->priv = priv;
+}
+
+static void spice_smartcard_manager_dispose(GObject *gobject)
+{
+    /* Chain up to the parent class */
+    if (G_OBJECT_CLASS(spice_smartcard_manager_parent_class)->dispose)
+        G_OBJECT_CLASS(spice_smartcard_manager_parent_class)->dispose(gobject);
+}
+
+static void spice_smartcard_manager_finalize(GObject *gobject)
+{
+    SpiceSmartcardManager *manager = SPICE_SMARTCARD_MANAGER(gobject);
+    SpiceSmartcardManagerPrivate *priv = manager->priv;
+
+    if (priv->monitor_id != 0) {
+        g_source_remove(priv->monitor_id);
+        priv->monitor_id = 0;
+    }
+
+#ifdef USE_SMARTCARD
+    if (priv->software_reader != NULL) {
+        vreader_free(priv->software_reader);
+        priv->software_reader = NULL;
+    }
+#endif
+
+    /* Chain up to the parent class */
+    if (G_OBJECT_CLASS(spice_smartcard_manager_parent_class)->finalize)
+        G_OBJECT_CLASS(spice_smartcard_manager_parent_class)->finalize(gobject);
+}
+
+static void spice_smartcard_manager_class_init(SpiceSmartcardManagerClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+    /**
+     * SpiceSmartcardManager::reader-added:
+     * @manager: the #SpiceSmartcardManager that emitted the signal
+     * @vreader: #VReader boxed object corresponding to the added reader
+     *
+     * The #SpiceSmartcardManager::reader-added signal is emitted whenever
+     * a new smartcard reader (software or hardware) has been plugged in.
+     **/
+    signals[SPICE_SMARTCARD_MANAGER_READER_ADDED] =
+        g_signal_new("reader-added",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_FIRST,
+                     G_STRUCT_OFFSET(SpiceSmartcardManagerClass, reader_added),
+                     NULL, NULL,
+                     g_cclosure_marshal_VOID__BOXED,
+                     G_TYPE_NONE,
+                     1,
+                     SPICE_TYPE_SMARTCARD_READER);
+
+    /**
+     * SpiceSmartcardManager::reader-removed:
+     * @manager: the #SpiceSmartcardManager that emitted the signal
+     * @vreader: #VReader boxed object corresponding to the removed reader
+     *
+     * The #SpiceSmartcardManager::reader-removed signal is emitted whenever
+     * a smartcard reader (software or hardware) has been removed.
+     **/
+    signals[SPICE_SMARTCARD_MANAGER_READER_REMOVED] =
+        g_signal_new("reader-removed",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_FIRST,
+                     G_STRUCT_OFFSET(SpiceSmartcardManagerClass, reader_removed),
+                     NULL, NULL,
+                     g_cclosure_marshal_VOID__BOXED,
+                     G_TYPE_NONE,
+                     1,
+                     SPICE_TYPE_SMARTCARD_READER);
+
+    /**
+     * SpiceSmartcardManager::card-inserted:
+     * @manager: the #SpiceSmartcardManager that emitted the signal
+     * @vreader: #VReader boxed object corresponding to the reader a new
+     * card was inserted in
+     *
+     * The #SpiceSmartcardManager::card-inserted signal is emitted whenever
+     * a smartcard is inserted in a reader
+     **/
+    signals[SPICE_SMARTCARD_MANAGER_CARD_INSERTED] =
+        g_signal_new("card-inserted",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_FIRST,
+                     G_STRUCT_OFFSET(SpiceSmartcardManagerClass, card_inserted),
+                     NULL, NULL,
+                     g_cclosure_marshal_VOID__BOXED,
+                     G_TYPE_NONE,
+                     1,
+                     SPICE_TYPE_SMARTCARD_READER);
+
+    /**
+     * SpiceSmartcardManager::card-removed:
+     * @manager: the #SpiceSmartcardManager that emitted the signal
+     * @vreader: #VReader boxed object corresponding to the reader a card
+     * was removed from
+     *
+     * The #SpiceSmartcardManager::card-removed signal is emitted whenever
+     * a smartcard was removed from a reader.
+     **/
+    signals[SPICE_SMARTCARD_MANAGER_CARD_REMOVED] =
+        g_signal_new("card-removed",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_FIRST,
+                     G_STRUCT_OFFSET(SpiceSmartcardManagerClass, card_removed),
+                     NULL, NULL,
+                     g_cclosure_marshal_VOID__BOXED,
+                     G_TYPE_NONE,
+                     1,
+                     SPICE_TYPE_SMARTCARD_READER);
+    gobject_class->dispose      = spice_smartcard_manager_dispose;
+    gobject_class->finalize     = spice_smartcard_manager_finalize;
+
+    g_type_class_add_private(klass, sizeof(SpiceSmartcardManagerPrivate));
+}
+
+/* ------------------------------------------------------------------ */
+/* private api                                                        */
+
+static SpiceSmartcardManager *spice_smartcard_manager_new(void)
+{
+    return g_object_new(SPICE_TYPE_SMARTCARD_MANAGER, NULL);
+}
+
+/* ------------------------------------------------------------------ */
+/* public api                                                         */
+
+/**
+ * spice_smartcard_manager_get:
+ *
+ * #SpiceSmartcardManager is a singleton, use this function to get a pointer
+ * to it. A new SpiceSmartcardManager instance will be created the first
+ * time this function is called
+ *
+ * Returns: (transfer none): a weak reference to the #SpiceSmartcardManager
+ */
+SpiceSmartcardManager *spice_smartcard_manager_get(void)
+{
+    static GOnce manager_singleton_once = G_ONCE_INIT;
+
+    return g_once(&manager_singleton_once,
+                  (GThreadFunc)spice_smartcard_manager_new,
+                  NULL);
+}
+
+#ifdef USE_SMARTCARD
+static gboolean smartcard_monitor_dispatch(VEvent *event, gpointer user_data)
+{
+    g_return_val_if_fail(event != NULL, TRUE);
+    SpiceSmartcardManager *manager = SPICE_SMARTCARD_MANAGER(user_data);
+
+    switch (event->type) {
+        case VEVENT_READER_INSERT:
+            if (spice_smartcard_reader_is_software((SpiceSmartcardReader*)event->reader)) {
+                g_warn_if_fail(manager->priv->software_reader == NULL);
+                manager->priv->software_reader = vreader_reference(event->reader);
+            }
+            SPICE_DEBUG("smartcard: reader-added");
+            g_signal_emit(G_OBJECT(user_data),
+                          signals[SPICE_SMARTCARD_MANAGER_READER_ADDED],
+                          0, event->reader);
+            break;
+
+        case VEVENT_READER_REMOVE:
+            if (spice_smartcard_reader_is_software((SpiceSmartcardReader*)event->reader)) {
+                g_warn_if_fail(manager->priv->software_reader != NULL);
+                vreader_free(manager->priv->software_reader);
+                manager->priv->software_reader = NULL;
+            }
+            SPICE_DEBUG("smartcard: reader-removed");
+            g_signal_emit(G_OBJECT(user_data),
+                          signals[SPICE_SMARTCARD_MANAGER_READER_REMOVED],
+                          0, event->reader);
+            break;
+
+        case VEVENT_CARD_INSERT:
+            SPICE_DEBUG("smartcard: card-inserted");
+            g_signal_emit(G_OBJECT(user_data),
+                          signals[SPICE_SMARTCARD_MANAGER_CARD_INSERTED],
+                          0, event->reader);
+            break;
+        case VEVENT_CARD_REMOVE:
+            SPICE_DEBUG("smartcard: card-removed");
+            g_signal_emit(G_OBJECT(user_data),
+                          signals[SPICE_SMARTCARD_MANAGER_CARD_REMOVED],
+                          0, event->reader);
+            break;
+        case VEVENT_LAST:
+            break;
+    }
+
+    return TRUE;
+}
+
+/* ------------------------------------------------------------------ */
+/* smartcard monitoring GSource                                       */
+struct _SmartcardSource {
+    GSource parent_source;
+    VEvent *pending_event;
+};
+typedef struct _SmartcardSource SmartcardSource;
+
+static gboolean smartcard_source_prepare(GSource *source, gint *timeout)
+{
+    SmartcardSource *smartcard_source = (SmartcardSource *)source;
+
+    if (smartcard_source->pending_event == NULL)
+        smartcard_source->pending_event = vevent_get_next_vevent();
+
+    if (timeout != NULL)
+        *timeout = -1;
+
+    return (smartcard_source->pending_event != NULL);
+}
+
+static gboolean smartcard_source_check(GSource *source)
+{
+    return smartcard_source_prepare(source, NULL);
+}
+
+static gboolean smartcard_source_dispatch(GSource *source,
+                                          GSourceFunc callback,
+                                          gpointer user_data)
+{
+    SmartcardSource *smartcard_source = (SmartcardSource *)source;
+    SmartcardSourceFunc smartcard_callback = (SmartcardSourceFunc)callback;
+
+    g_return_val_if_fail(smartcard_source->pending_event != NULL, FALSE);
+
+    if (callback) {
+        gboolean event_consumed;
+        event_consumed = smartcard_callback(smartcard_source->pending_event,
+                                            user_data);
+        if (event_consumed) {
+            vevent_delete(smartcard_source->pending_event);
+            smartcard_source->pending_event = NULL;
+        }
+    }
+
+    return TRUE;
+}
+
+static void smartcard_source_finalize(GSource *source)
+{
+    SmartcardSource *smartcard_source = (SmartcardSource *)source;
+
+    if (smartcard_source->pending_event) {
+        vevent_delete(smartcard_source->pending_event);
+        smartcard_source->pending_event = NULL;
+    }
+}
+
+static GSource *smartcard_monitor_source_new(void)
+{
+    static GSourceFuncs source_funcs = {
+        .prepare = smartcard_source_prepare,
+        .check = smartcard_source_check,
+        .dispatch = smartcard_source_dispatch,
+        .finalize = smartcard_source_finalize
+    };
+    GSource *source;
+
+    source = g_source_new(&source_funcs, sizeof(SmartcardSource));
+    g_source_set_name(source, "Smartcard event source");
+    return source;
+}
+
+static guint smartcard_monitor_add(SmartcardSourceFunc callback,
+                                   gpointer user_data)
+{
+    GSource *source;
+    guint id;
+
+    source = smartcard_monitor_source_new();
+    g_source_set_callback(source, (GSourceFunc)callback, user_data, NULL);
+    id = g_source_attach(source, NULL);
+    g_source_unref(source);
+
+    return id;
+}
+
+static void
+spice_smartcard_manager_update_monitor(void)
+{
+    SpiceSmartcardManager *self = spice_smartcard_manager_get();
+    SpiceSmartcardManagerPrivate *priv = self->priv;
+
+    if (priv->monitor_id != 0)
+        return;
+
+    priv->monitor_id = smartcard_monitor_add(smartcard_monitor_dispatch, self);
+}
+
+#define SPICE_SOFTWARE_READER_NAME "Spice Software Smartcard"
+
+typedef struct {
+    SpiceSession *session;
+    GCancellable *cancellable;
+    GError *err;
+} SmartcardManagerInitArgs;
+
+static gboolean smartcard_manager_init(SmartcardManagerInitArgs *args)
+{
+    gchar *emul_args = NULL;
+    VCardEmulOptions *options = NULL;
+    VCardEmulError emul_init_status;
+    gchar *dbname = NULL;
+    GStrv certificates = NULL;
+    gboolean retval = FALSE;
+
+    SPICE_DEBUG("smartcard_manager_init");
+    g_return_val_if_fail(SPICE_IS_SESSION(args->session), FALSE);
+    g_object_get(G_OBJECT(args->session),
+                 "smartcard-db", &dbname,
+                 "smartcard-certificates", &certificates,
+                 NULL);
+
+    if ((certificates == NULL) || (g_strv_length(certificates) != 3))
+        goto init;
+
+    if (dbname) {
+        emul_args = g_strdup_printf("db=\"%s\" use_hw=no "
+                                    "soft=(,%s,CAC,,%s,%s,%s)",
+                                    dbname, SPICE_SOFTWARE_READER_NAME,
+                                    certificates[0], certificates[1],
+                                    certificates[2]);
+    } else {
+        emul_args = g_strdup_printf("use_hw=no soft=(,%s,CAC,,%s,%s,%s)",
+                                    SPICE_SOFTWARE_READER_NAME,
+                                    certificates[0], certificates[1],
+                                    certificates[2]);
+    }
+
+    options = vcard_emul_options(emul_args);
+    if (options == NULL) {
+        args->err = g_error_new(SPICE_CLIENT_ERROR,
+                                SPICE_CLIENT_ERROR_FAILED,
+                                "vcard_emul_options() failed!");
+        goto end;
+    }
+
+    if (g_cancellable_set_error_if_cancelled(args->cancellable, &args->err))
+        goto end;
+
+init:
+    SPICE_DEBUG("vcard_emul_init");
+    emul_init_status = vcard_emul_init(options);
+    if ((emul_init_status != VCARD_EMUL_OK)
+            && (emul_init_status != VCARD_EMUL_INIT_ALREADY_INITED)) {
+        args->err = g_error_new(SPICE_CLIENT_ERROR,
+                                SPICE_CLIENT_ERROR_FAILED,
+                                "Failed to initialize smartcard");
+        goto end;
+    }
+
+    retval = TRUE;
+
+end:
+    SPICE_DEBUG("smartcard_manager_init end: %d", retval);
+    g_free(emul_args);
+    g_free(dbname);
+    g_strfreev(certificates);
+    return retval;
+}
+
+static void smartcard_manager_init_helper(GSimpleAsyncResult *res,
+                                          GObject *object,
+                                          GCancellable *cancellable)
+{
+    static GOnce smartcard_manager_once = G_ONCE_INIT;
+    SmartcardManagerInitArgs args;
+
+    args.session = SPICE_SESSION(object);
+    args.cancellable = cancellable;
+    args.err = NULL;
+
+
+    g_once(&smartcard_manager_once,
+           (GThreadFunc)smartcard_manager_init,
+           &args);
+    if (args.err != NULL) {
+        g_simple_async_result_set_from_error(res, args.err);
+        g_error_free(args.err);
+    }
+}
+
+
+G_GNUC_INTERNAL
+void spice_smartcard_manager_init_async(SpiceSession *session,
+                                        GCancellable *cancellable,
+                                        GAsyncReadyCallback callback,
+                                        gpointer opaque)
+{
+    GSimpleAsyncResult *res;
+
+    res = g_simple_async_result_new(G_OBJECT(session),
+                                    callback,
+                                    opaque,
+                                    spice_smartcard_manager_init);
+    g_simple_async_result_run_in_thread(res,
+                                        smartcard_manager_init_helper,
+                                        G_PRIORITY_DEFAULT,
+                                        cancellable);
+    g_object_unref(res);
+}
+
+G_GNUC_INTERNAL
+gboolean spice_smartcard_manager_init_finish(SpiceSession *session,
+                                             GAsyncResult *result,
+                                             GError **err)
+{
+    GSimpleAsyncResult *simple;
+
+    g_return_val_if_fail(SPICE_IS_SESSION(session), FALSE);
+    g_return_val_if_fail(G_IS_SIMPLE_ASYNC_RESULT(result), FALSE);
+
+    SPICE_DEBUG("smartcard_manager_finish");
+
+    simple = G_SIMPLE_ASYNC_RESULT(result);
+    g_return_val_if_fail(g_simple_async_result_get_source_tag(simple) == spice_smartcard_manager_init, FALSE);
+    if (g_simple_async_result_propagate_error(simple, err))
+        return FALSE;
+
+    spice_smartcard_manager_update_monitor();
+
+    return TRUE;
+}
+
+/**
+ * spice_smartcard_reader_is_software:
+ * @reader: a #SpiceSmartcardReader
+ *
+ * Tests if @reader is a software (emulated) smartcard reader.
+ *
+ * Returns: TRUE if @reader is a software (emulated) smartcard reader,
+ * FALSE otherwise
+ */
+gboolean spice_smartcard_reader_is_software(SpiceSmartcardReader *reader)
+{
+    g_return_val_if_fail(reader != NULL, FALSE);
+    return (strcmp(vreader_get_name((VReader*)reader), SPICE_SOFTWARE_READER_NAME) == 0);
+}
+
+/**
+ * spice_smartcard_reader_insert_card:
+ * @reader: a #SpiceSmartcardReader
+ *
+ * Simulates insertion of a smartcard in the software smartcard reader
+ * @reader. If @reader is not a software smartcard reader, FALSE will be
+ * returned.
+ *
+ * Returns: TRUE if insertion of a card was successfully simulated, FALSE
+ * otherwise
+ */
+gboolean spice_smartcard_reader_insert_card(SpiceSmartcardReader *reader)
+{
+    VCardEmulError status;
+
+    g_return_val_if_fail(spice_smartcard_reader_is_software(reader), FALSE);
+
+    status = vcard_emul_force_card_insert((VReader *)reader);
+
+    return (status == VCARD_EMUL_OK);
+}
+
+/**
+ * spice_smartcard_reader_remove_card:
+ * @reader: a #SpiceSmartcardReader
+ *
+ * Simulates removal of a smartcard from the software smartcard reader
+ * @reader. If @reader is not a software smartcard reader, FALSE will be
+ * returned.
+ *
+ * Returns: TRUE if removal of a card was successfully simulated, FALSE
+ * otherwise
+ */
+gboolean spice_smartcard_reader_remove_card(SpiceSmartcardReader *reader)
+{
+    VCardEmulError status;
+
+    g_return_val_if_fail(spice_smartcard_reader_is_software(reader), FALSE);
+
+    status = vcard_emul_force_card_remove((VReader *)reader);
+
+    return (status == VCARD_EMUL_OK);
+}
+
+/**
+ * spice_smartcard_manager_get_readers:
+ *
+ * manager: a #SpiceSmartcardManager
+ *
+ * Gets the list of smartcard readers that are currently available, they
+ * can be either software (emulated) readers, or hardware ones.
+ *
+ * Returns: (element-type SpiceSmartcardReader) (transfer full): a newly
+ * allocated list of SpiceSmartcardReader instances, or NULL if none were
+ * found. When no longer needed, the list must be freed after unreferencing
+ * its elements with g_boxed_free()
+ *
+ * Since: 0.20
+ */
+GList *spice_smartcard_manager_get_readers(SpiceSmartcardManager *manager)
+{
+
+    GList *readers = NULL;
+    VReaderList *vreader_list;
+    VReaderListEntry *entry;
+
+    vreader_list = vreader_get_reader_list();
+
+    if (vreader_list == NULL)
+        return NULL;
+
+    for (entry = vreader_list_get_first(vreader_list);
+         entry != NULL;
+         entry = vreader_list_get_next(entry)) {
+        VReader *reader;
+
+        reader = vreader_list_get_reader(entry);
+        g_warn_if_fail(reader != NULL);
+        readers = g_list_prepend(readers, vreader_reference(reader));
+    }
+    vreader_list_delete(vreader_list);
+
+    return g_list_reverse(readers);
+}
+
+/**
+ * spice_smartcard_manager_insert_card:
+ * @manager: a #SpiceSmartcardManager
+ *
+ * Simulates the insertion of a smartcard in the guest. Valid certificates
+ * must have been set in #SpiceSession:smartcard-certificates for software
+ * smartcard support to work. At the moment, only one software smartcard
+ * reader is supported, that's why there is no parameter to indicate which
+ * reader to insert the card in.
+ *
+ * Returns: TRUE if smartcard insertion was successfully simulated, FALSE
+ * if this failed, or if software smartcard support isn't enabled.
+ *
+ * Since: 0.20
+ */
+gboolean spice_smartcard_manager_insert_card(SpiceSmartcardManager *manager)
+{
+    SpiceSmartcardReader *reader;
+
+    g_return_val_if_fail (manager->priv->software_reader != NULL, FALSE);
+
+    reader = (SpiceSmartcardReader *)manager->priv->software_reader;
+
+    return spice_smartcard_reader_insert_card(reader);
+}
+
+/**
+ * spice_smartcard_manager_remove_card:
+ * @manager: a #SpiceSmartcardManager
+ *
+ * Simulates the removal of a smartcard in the guest. At the moment, only
+ * one software smartcard reader is supported, that's why there is no
+ * parameter to indicate which reader to insert the card in.
+ *
+ * Returns: TRUE if smartcard removal was successfully simulated, FALSE
+ * if this failed, or if software smartcard support isn't enabled.
+ *
+ * Since: 0.20
+ */
+gboolean spice_smartcard_manager_remove_card(SpiceSmartcardManager *manager)
+{
+    SpiceSmartcardReader *reader;
+
+    g_return_val_if_fail (manager->priv->software_reader != NULL, FALSE);
+
+    reader = (SpiceSmartcardReader *)manager->priv->software_reader;
+
+    return spice_smartcard_reader_remove_card(reader);
+}
+#else
+gboolean spice_smartcard_reader_is_software(SpiceSmartcardReader *reader)
+{
+    return TRUE;
+}
+
+G_GNUC_INTERNAL
+void spice_smartcard_manager_init_async(SpiceSession *session,
+                                        GCancellable *cancellable,
+                                        GAsyncReadyCallback callback,
+                                        gpointer opaque)
+{
+    SPICE_DEBUG("using fake smartcard backend");
+}
+
+G_GNUC_INTERNAL
+gboolean spice_smartcard_manager_init_finish(SpiceSession *session,
+                                             GAsyncResult *result,
+                                             GError **err)
+{
+    g_return_val_if_fail(SPICE_IS_SESSION(session), FALSE);
+
+    return TRUE;
+}
+
+gboolean spice_smartcard_manager_insert_card(SpiceSmartcardManager *manager)
+{
+    return FALSE;
+}
+
+gboolean spice_smartcard_manager_remove_card(SpiceSmartcardManager *manager)
+{
+    return FALSE;
+}
+
+gboolean spice_smartcard_reader_insert_card(SpiceSmartcardReader *reader)
+{
+    return FALSE;
+}
+
+gboolean spice_smartcard_reader_remove_card(SpiceSmartcardReader *reader)
+{
+    return FALSE;
+}
+
+GList *spice_smartcard_manager_get_readers(SpiceSmartcardManager *manager)
+{
+    return NULL;
+}
+
+#endif /* USE_SMARTCARD */
diff --git a/src/smartcard-manager.h b/src/smartcard-manager.h
new file mode 100644
index 0000000..4811083
--- /dev/null
+++ b/src/smartcard-manager.h
@@ -0,0 +1,80 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 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/>.
+*/
+#ifndef __SPICE_SMARTCARD_MANAGER_H__
+#define __SPICE_SMARTCARD_MANAGER_H__
+
+G_BEGIN_DECLS
+
+#include "spice-types.h"
+#include "spice-util.h"
+
+#define SPICE_TYPE_SMARTCARD_MANAGER            (spice_smartcard_manager_get_type ())
+#define SPICE_SMARTCARD_MANAGER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), SPICE_TYPE_SMARTCARD_MANAGER, SpiceSmartcardManager))
+#define SPICE_SMARTCARD_MANAGER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SPICE_TYPE_SMARTCARD_MANAGER, SpiceSmartcardManagerClass))
+#define SPICE_IS_SMARTCARD_MANAGER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SPICE_TYPE_SMARTCARD_MANAGER))
+#define SPICE_IS_SMARTCARD_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SPICE_TYPE_SMARTCARD_MANAGER))
+#define SPICE_SMARTCARD_MANAGER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SPICE_TYPE_SMARTCARD_MANAGER, SpiceSmartcardManagerClass))
+
+#define SPICE_TYPE_SMARTCARD_READER (spice_smartcard_reader_get_type())
+
+typedef struct _SpiceSmartcardManager SpiceSmartcardManager;
+typedef struct _SpiceSmartcardManagerClass SpiceSmartcardManagerClass;
+typedef struct _SpiceSmartcardManagerPrivate SpiceSmartcardManagerPrivate;
+typedef struct _SpiceSmartcardReader SpiceSmartcardReader;
+
+struct _SpiceSmartcardManager
+{
+    GObject parent;
+
+    /*< private >*/
+    SpiceSmartcardManagerPrivate *priv;
+    /* Do not add fields to this struct */
+};
+
+struct _SpiceSmartcardManagerClass
+{
+    GObjectClass parent_class;
+    /*< public >*/
+    /* signals */
+    void (*reader_added)(SpiceSmartcardManager *manager, SpiceSmartcardReader *reader);
+    void (*reader_removed)(SpiceSmartcardManager *manager, SpiceSmartcardReader *reader);
+    void (*card_inserted)(SpiceSmartcardManager *manager, SpiceSmartcardReader *reader);
+    void (*card_removed)(SpiceSmartcardManager *manager, SpiceSmartcardReader *reader );
+
+    /*< private >*/
+    /*
+     * If adding fields to this struct, remove corresponding
+     * amount of padding to avoid changing overall struct size
+     */
+    gchar _spice_reserved[SPICE_RESERVED_PADDING];
+};
+
+GType spice_smartcard_manager_get_type(void);
+GType spice_smartcard_reader_get_type(void);
+
+SpiceSmartcardManager *spice_smartcard_manager_get(void);
+gboolean spice_smartcard_manager_insert_card(SpiceSmartcardManager *manager);
+gboolean spice_smartcard_manager_remove_card(SpiceSmartcardManager *manager);
+gboolean spice_smartcard_reader_is_software(SpiceSmartcardReader *reader);
+gboolean spice_smartcard_reader_insert_card(SpiceSmartcardReader *reader);
+gboolean spice_smartcard_reader_remove_card(SpiceSmartcardReader *reader);
+GList *spice_smartcard_manager_get_readers(SpiceSmartcardManager *manager);
+
+G_END_DECLS
+
+#endif /* __SPICE_SMARTCARD_MANAGER_H__ */
diff --git a/src/spice-audio-priv.h b/src/spice-audio-priv.h
new file mode 100644
index 0000000..f108059
--- /dev/null
+++ b/src/spice-audio-priv.h
@@ -0,0 +1,42 @@
+/* -*- 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_AUDIO_PRIVATE_H__
+#define __SPICE_AUDIO_PRIVATE_H__
+
+#include <glib.h>
+#include <gio/gio.h>
+#include "spice-session.h"
+
+G_BEGIN_DECLS
+
+struct _SpiceAudioPrivate {
+    SpiceSession            *session;
+    GMainContext            *main_context;
+};
+
+void spice_audio_get_playback_volume_info_async(SpiceAudio *audio, GCancellable *cancellable,
+        SpiceMainChannel *main_channel, GAsyncReadyCallback callback, gpointer user_data);
+gboolean spice_audio_get_playback_volume_info_finish(SpiceAudio *audio, GAsyncResult *res,
+        gboolean *mute, guint8 *nchannels, guint16 **volume, GError **error);
+void spice_audio_get_record_volume_info_async(SpiceAudio *audio, GCancellable *cancellable,
+        SpiceMainChannel *main_channel, GAsyncReadyCallback callback, gpointer user_data);
+gboolean spice_audio_get_record_volume_info_finish(SpiceAudio *audio, GAsyncResult *res,
+        gboolean *mute, guint8 *nchannels, guint16 **volume, GError **error);
+G_END_DECLS
+
+#endif /* __SPICE_AUDIO_PRIVATE_H__ */
diff --git a/src/spice-audio.c b/src/spice-audio.c
new file mode 100644
index 0000000..ce191e1
--- /dev/null
+++ b/src/spice-audio.c
@@ -0,0 +1,274 @@
+/* -*- 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/>.
+*/
+/*
+ * simple audio init dispatcher
+ */
+
+/**
+ * SECTION:spice-audio
+ * @short_description: a helper to play and to record audio channels
+ * @title: Spice Audio
+ * @section_id:
+ * @see_also: #SpiceRecordChannel, and #SpicePlaybackChannel
+ * @stability: Stable
+ * @include: spice-audio.h
+ *
+ * A class that handles the playback and record channels for your
+ * application, and connect them to the default sound system.
+ */
+
+#include "config.h"
+
+#include "spice-client.h"
+#include "spice-common.h"
+
+#include "spice-audio.h"
+#include "spice-session-priv.h"
+#include "spice-channel-priv.h"
+#include "spice-audio-priv.h"
+
+#ifdef WITH_PULSE
+#include "spice-pulse.h"
+#endif
+#if defined(WITH_GSTAUDIO)
+#include "spice-gstaudio.h"
+#endif
+
+#include "glib-compat.h"
+
+#define SPICE_AUDIO_GET_PRIVATE(obj)                                  \
+    (G_TYPE_INSTANCE_GET_PRIVATE ((obj), SPICE_TYPE_AUDIO, SpiceAudioPrivate))
+
+G_DEFINE_ABSTRACT_TYPE(SpiceAudio, spice_audio, G_TYPE_OBJECT)
+
+enum {
+    PROP_0,
+    PROP_SESSION,
+    PROP_MAIN_CONTEXT,
+};
+
+static void spice_audio_finalize(GObject *gobject)
+{
+    SpiceAudio *self = SPICE_AUDIO(gobject);
+    SpiceAudioPrivate *priv = self->priv;
+
+    if (priv->main_context) {
+        g_main_context_unref(priv->main_context);
+        priv->main_context = NULL;
+    }
+
+    if (G_OBJECT_CLASS(spice_audio_parent_class)->finalize)
+        G_OBJECT_CLASS(spice_audio_parent_class)->finalize(gobject);
+}
+
+static void spice_audio_get_property(GObject *gobject,
+                                     guint prop_id,
+                                     GValue *value,
+                                     GParamSpec *pspec)
+{
+    SpiceAudio *self = SPICE_AUDIO(gobject);
+    SpiceAudioPrivate *priv = self->priv;
+
+    switch (prop_id) {
+    case PROP_SESSION:
+        g_value_set_object(value, priv->session);
+        break;
+    case PROP_MAIN_CONTEXT:
+        g_value_set_boxed(value, priv->main_context);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
+        break;
+    }
+}
+
+static void spice_audio_set_property(GObject *gobject,
+                                     guint prop_id,
+                                     const GValue *value,
+                                     GParamSpec *pspec)
+{
+    SpiceAudio *self = SPICE_AUDIO(gobject);
+    SpiceAudioPrivate *priv = self->priv;
+
+    switch (prop_id) {
+    case PROP_SESSION:
+        priv->session = g_value_get_object(value);
+        break;
+    case PROP_MAIN_CONTEXT:
+        priv->main_context = g_value_dup_boxed(value);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
+        break;
+    }
+}
+
+static void spice_audio_class_init(SpiceAudioClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+    GParamSpec *pspec;
+
+    gobject_class->finalize     = spice_audio_finalize;
+    gobject_class->get_property = spice_audio_get_property;
+    gobject_class->set_property = spice_audio_set_property;
+
+    /**
+     * SpiceAudio:session:
+     *
+     * #SpiceSession this #SpiceAudio is associated with
+     *
+     **/
+    pspec = g_param_spec_object("session", "Session", "SpiceSession",
+                                SPICE_TYPE_SESSION,
+                                G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+    g_object_class_install_property(gobject_class, PROP_SESSION, pspec);
+
+    /**
+     * SpiceAudio:main-context:
+     */
+    pspec = g_param_spec_boxed("main-context", "Main Context",
+                               "GMainContext to use for the event source",
+                               G_TYPE_MAIN_CONTEXT,
+                               G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+    g_object_class_install_property(gobject_class, PROP_MAIN_CONTEXT, pspec);
+
+    g_type_class_add_private(klass, sizeof(SpiceAudioPrivate));
+}
+
+static void spice_audio_init(SpiceAudio *self)
+{
+    self->priv = SPICE_AUDIO_GET_PRIVATE(self);
+}
+
+static void connect_channel(SpiceAudio *self, SpiceChannel *channel)
+{
+    if (channel->priv->state != SPICE_CHANNEL_STATE_UNCONNECTED)
+        return;
+
+    if (SPICE_AUDIO_GET_CLASS(self)->connect_channel(self, channel))
+        spice_channel_connect(channel);
+}
+
+static void update_audio_channels(SpiceAudio *self, SpiceSession *session)
+{
+    GList *list, *tmp;
+
+    if (!spice_session_get_audio_enabled(session)) {
+        g_debug("FIXME: disconnect audio channels");
+        return;
+    }
+
+    list = spice_session_get_channels(session);
+    for (tmp = g_list_first(list); tmp != NULL; tmp = g_list_next(tmp)) {
+        connect_channel(self, tmp->data);
+    }
+    g_list_free(list);
+}
+
+static void channel_new(SpiceSession *session, SpiceChannel *channel, SpiceAudio *self)
+{
+    connect_channel(self, channel);
+}
+
+static void session_enable_audio(GObject *gobject, GParamSpec *pspec,
+                                 gpointer user_data)
+{
+    update_audio_channels(SPICE_AUDIO(user_data), SPICE_SESSION(gobject));
+}
+
+void spice_audio_get_playback_volume_info_async(SpiceAudio *audio,
+                                                GCancellable *cancellable,
+                                                SpiceMainChannel *main_channel,
+                                                GAsyncReadyCallback callback,
+                                                gpointer user_data)
+{
+    SPICE_AUDIO_GET_CLASS(audio)->get_playback_volume_info_async(audio,
+            cancellable, main_channel, callback, user_data);
+}
+
+gboolean spice_audio_get_playback_volume_info_finish(SpiceAudio *audio,
+                                                     GAsyncResult *res,
+                                                     gboolean *mute,
+                                                     guint8 *nchannels,
+                                                     guint16 **volume,
+                                                     GError **error)
+{
+    return SPICE_AUDIO_GET_CLASS(audio)->get_playback_volume_info_finish(audio,
+            res, mute, nchannels, volume, error);
+}
+
+void spice_audio_get_record_volume_info_async(SpiceAudio *audio,
+                                              GCancellable *cancellable,
+                                              SpiceMainChannel *main_channel,
+                                              GAsyncReadyCallback callback,
+                                              gpointer user_data)
+{
+    SPICE_AUDIO_GET_CLASS(audio)->get_record_volume_info_async(audio,
+            cancellable, main_channel, callback, user_data);
+}
+
+gboolean spice_audio_get_record_volume_info_finish(SpiceAudio *audio,
+                                                   GAsyncResult *res,
+                                                   gboolean *mute,
+                                                   guint8 *nchannels,
+                                                   guint16 **volume,
+                                                   GError **error)
+{
+    return SPICE_AUDIO_GET_CLASS(audio)->get_record_volume_info_finish(audio,
+            res, mute, nchannels, volume, error);
+}
+
+/**
+ * spice_audio_new:
+ * @session: the #SpiceSession to connect to
+ * @context: (allow-none): a #GMainContext to attach to (or %NULL for
+ * default).
+ * @name: (allow-none): a name for the audio channels (or %NULL for
+ * application name).
+ *
+ * Once instantiated, #SpiceAudio will handle the playback and record
+ * channels to stream to your local audio system.
+ *
+ * Returns: a new #SpiceAudio instance or %NULL if no backend or failed.
+ * Deprecated: 0.8: Use spice_audio_get() instead
+ **/
+SpiceAudio *spice_audio_new(SpiceSession *session, GMainContext *context,
+                            const char *name)
+{
+    SpiceAudio *self = NULL;
+
+    if (context == NULL)
+        context = g_main_context_default();
+    if (name == NULL)
+        name = g_get_application_name();
+
+#ifdef WITH_PULSE
+    self = SPICE_AUDIO(spice_pulse_new(session, context, name));
+#endif
+#if defined(WITH_GSTAUDIO)
+    self = SPICE_AUDIO(spice_gstaudio_new(session, context, name));
+#endif
+    if (!self)
+        return NULL;
+
+    spice_g_signal_connect_object(session, "notify::enable-audio", G_CALLBACK(session_enable_audio), self, 0);
+    spice_g_signal_connect_object(session, "channel-new", G_CALLBACK(channel_new), self, G_CONNECT_AFTER);
+    update_audio_channels(self, session);
+
+    return self;
+}
diff --git a/src/spice-audio.h b/src/spice-audio.h
new file mode 100644
index 0000000..0bf625b
--- /dev/null
+++ b/src/spice-audio.h
@@ -0,0 +1,109 @@
+/* -*- 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_CLIENT_AUDIO_H__
+#define __SPICE_CLIENT_AUDIO_H__
+
+#include <glib-object.h>
+#include <gio/gio.h>
+#include "spice-util.h"
+#include "spice-session.h"
+#include "channel-main.h"
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_AUDIO spice_audio_get_type()
+
+#define SPICE_AUDIO(obj)					\
+    (G_TYPE_CHECK_INSTANCE_CAST ((obj), SPICE_TYPE_AUDIO, SpiceAudio))
+
+#define SPICE_AUDIO_CLASS(klass)				\
+    (G_TYPE_CHECK_CLASS_CAST ((klass), SPICE_TYPE_AUDIO, SpiceAudioClass))
+
+#define SPICE_IS_AUDIO(obj)                                     \
+    (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SPICE_TYPE_AUDIO))
+
+#define SPICE_IS_AUDIO_CLASS(klass)                             \
+    (G_TYPE_CHECK_CLASS_TYPE ((klass), SPICE_TYPE_AUDIO))
+
+#define SPICE_AUDIO_GET_CLASS(obj)				\
+    (G_TYPE_INSTANCE_GET_CLASS ((obj), SPICE_TYPE_AUDIO, SpiceAudioClass))
+
+typedef struct _SpiceAudio SpiceAudio;
+typedef struct _SpiceAudioClass SpiceAudioClass;
+typedef struct _SpiceAudioPrivate SpiceAudioPrivate;
+
+/**
+ * SpiceAudio:
+ *
+ * The #SpiceAudio struct is opaque and should not be accessed directly.
+ */
+struct _SpiceAudio {
+    GObject parent;
+
+    SpiceAudioPrivate *priv;
+};
+
+/**
+ * SpiceAudioClass:
+ * @parent_class: Parent class.
+ *
+ * Class structure for #SpiceAudio.
+ */
+struct _SpiceAudioClass {
+    GObjectClass parent_class;
+
+    /*< private >*/
+    gboolean (*connect_channel)(SpiceAudio *audio, SpiceChannel *channel);
+    void (*get_playback_volume_info_async)(SpiceAudio *audio,
+                                           GCancellable *cancellable,
+                                           SpiceMainChannel *main_channel,
+                                           GAsyncReadyCallback callback,
+                                           gpointer user_data);
+    gboolean (*get_playback_volume_info_finish)(SpiceAudio *audio,
+                                                GAsyncResult *res,
+                                                gboolean *mute,
+                                                guint8 *nchannels,
+                                                guint16 **volume,
+                                                GError **error);
+    void (*get_record_volume_info_async)(SpiceAudio *audio,
+                                         GCancellable *cancellable,
+                                         SpiceMainChannel *main_channel,
+                                         GAsyncReadyCallback callback,
+                                         gpointer user_data);
+    gboolean (*get_record_volume_info_finish)(SpiceAudio *audio,
+                                              GAsyncResult *res,
+                                              gboolean *mute,
+                                              guint8 *nchannels,
+                                              guint16 **volume,
+                                              GError **error);
+
+    gchar _spice_reserved[SPICE_RESERVED_PADDING - 4 * sizeof(void *)];
+};
+
+GType spice_audio_get_type(void);
+
+SpiceAudio* spice_audio_get(SpiceSession *session, GMainContext *context);
+
+#ifndef SPICE_DISABLE_DEPRECATED
+SPICE_DEPRECATED_FOR(spice_audio_get)
+SpiceAudio* spice_audio_new(SpiceSession *session, GMainContext *context, const char *name);
+#endif
+
+G_END_DECLS
+
+#endif /* __SPICE_CLIENT_AUDIO_H__ */
diff --git a/src/spice-channel-cache.h b/src/spice-channel-cache.h
new file mode 100644
index 0000000..17775e6
--- /dev/null
+++ b/src/spice-channel-cache.h
@@ -0,0 +1,106 @@
+/* -*- 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_CHANNEL_CACHE_H_
+# define SPICE_CHANNEL_CACHE_H_
+
+#include <inttypes.h> /* For PRIx64 */
+#include "common/mem.h"
+#include "common/ring.h"
+
+G_BEGIN_DECLS
+
+typedef struct display_cache_item {
+    guint64                     id;
+    gboolean                    lossy;
+} display_cache_item;
+
+typedef GHashTable display_cache;
+
+static inline display_cache_item* cache_item_new(guint64 id, gboolean lossy)
+{
+    display_cache_item *self = g_slice_new(display_cache_item);
+    self->id = id;
+    self->lossy = lossy;
+    return self;
+}
+
+static inline void cache_item_free(display_cache_item *self)
+{
+    g_slice_free(display_cache_item, self);
+}
+
+static inline display_cache* cache_new(GDestroyNotify value_destroy)
+{
+    GHashTable* self;
+
+    self = g_hash_table_new_full(g_int64_hash, g_int64_equal,
+                                 (GDestroyNotify)cache_item_free,
+                                 value_destroy);
+
+    return self;
+}
+
+static inline gpointer cache_find(display_cache *cache, uint64_t id)
+{
+    return g_hash_table_lookup(cache, &id);
+}
+
+static inline gpointer cache_find_lossy(display_cache *cache, uint64_t id, gboolean *lossy)
+{
+    gpointer value;
+    display_cache_item *item;
+
+    if (!g_hash_table_lookup_extended(cache, &id, (gpointer*)&item, &value))
+        return NULL;
+
+    *lossy = item->lossy;
+
+    return value;
+}
+
+static inline void cache_add_lossy(display_cache *cache, uint64_t id,
+                                   gpointer value, gboolean lossy)
+{
+    display_cache_item *item = cache_item_new(id, lossy);
+
+    g_hash_table_replace(cache, item, value);
+}
+
+static inline void cache_add(display_cache *cache, uint64_t id, gpointer value)
+{
+    cache_add_lossy(cache, id, value, FALSE);
+}
+
+static inline gboolean cache_remove(display_cache *cache, uint64_t id)
+{
+    return g_hash_table_remove(cache, &id);
+}
+
+static inline void cache_clear(display_cache *cache)
+{
+    g_hash_table_remove_all(cache);
+}
+
+static inline void cache_unref(display_cache *cache)
+{
+    g_hash_table_unref(cache);
+}
+
+G_END_DECLS
+
+#endif // SPICE_CHANNEL_CACHE_H_
diff --git a/src/spice-channel-enums.h b/src/spice-channel-enums.h
new file mode 100644
index 0000000..02df762
--- /dev/null
+++ b/src/spice-channel-enums.h
@@ -0,0 +1,7 @@
+#ifndef SPICE_CHANNEL_ENUMS_H
+#define SPICE_CHANNEL_ENUMS_H
+
+#warning "deprecated: please include spice-glib-enums.h"
+#include "spice-glib-enums.h"
+
+#endif /* SPICE_CHANNEL_ENUMS_H */
diff --git a/src/spice-channel-priv.h b/src/spice-channel-priv.h
new file mode 100644
index 0000000..d70cf86
--- /dev/null
+++ b/src/spice-channel-priv.h
@@ -0,0 +1,203 @@
+/* -*- 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_CLIENT_CHANNEL_PRIV_H__
+#define __SPICE_CLIENT_CHANNEL_PRIV_H__
+
+#include "config.h"
+
+#include <openssl/ssl.h>
+#include <gio/gio.h>
+
+#if HAVE_SASL
+#include <sasl/sasl.h>
+#endif
+
+#include "spice-channel.h"
+#include "spice-util-priv.h"
+#include "coroutine.h"
+#include "gio-coroutine.h"
+
+#include "common/client_marshallers.h"
+#include "common/client_demarshallers.h"
+#include "common/ssl_verify.h"
+
+G_BEGIN_DECLS
+
+#define MAX_SPICE_DATA_HEADER_SIZE sizeof(SpiceDataHeader)
+
+#define CHANNEL_DEBUG(channel, fmt, ...) \
+    SPICE_DEBUG("%s: " fmt, SPICE_CHANNEL(channel)->priv->name, ## __VA_ARGS__)
+
+struct _SpiceMsgOut {
+    int                   refcount;
+    SpiceChannel          *channel;
+    SpiceMessageMarshallers *marshallers;
+    SpiceMarshaller       *marshaller;
+    uint8_t               *header;
+    gboolean              ro_check;
+};
+
+struct _SpiceMsgIn {
+    int                   refcount;
+    SpiceChannel          *channel;
+    uint8_t               header[MAX_SPICE_DATA_HEADER_SIZE];
+    uint8_t               *data;
+    int                   dpos;
+    uint8_t               *parsed;
+    size_t                psize;
+    message_destructor_t  pfree;
+    SpiceMsgIn            *parent;
+};
+
+enum spice_channel_state {
+    SPICE_CHANNEL_STATE_UNCONNECTED = 0,
+    SPICE_CHANNEL_STATE_RECONNECTING,
+    SPICE_CHANNEL_STATE_CONNECTING,
+    SPICE_CHANNEL_STATE_READY,
+    SPICE_CHANNEL_STATE_SWITCHING,
+    SPICE_CHANNEL_STATE_MIGRATING,
+    SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE,
+};
+
+struct _SpiceChannelPrivate {
+    /* swapped on migration */
+    SSL_CTX                     *ctx;
+    SSL                         *ssl;
+    SpiceOpenSSLVerify          *sslverify;
+    GSocket                     *sock;
+    GSocketConnection           *conn;
+    GInputStream                *in;
+    GOutputStream               *out;
+
+#if HAVE_SASL
+    sasl_conn_t                 *sasl_conn;
+    const char                  *sasl_decoded;
+    unsigned int                sasl_decoded_length;
+    unsigned int                sasl_decoded_offset;
+#endif
+
+    gboolean                    use_mini_header;
+    uint64_t                    out_serial;
+    uint64_t                    in_serial;
+
+    /* not swapped */
+    SpiceSession                *session;
+    GCoroutine                  coroutine;
+    int                         fd;
+    gboolean                    has_error;
+    guint                       connect_delayed_id;
+
+    GQueue                      xmit_queue;
+    gboolean                    xmit_queue_blocked;
+    STATIC_MUTEX                xmit_queue_lock;
+    guint                       xmit_queue_wakeup_id;
+
+    char                        name[16];
+    enum spice_channel_state    state;
+    SpiceChannelEvent           event;
+
+    spice_parse_channel_func_t  parser;
+    SpiceMessageMarshallers     *marshallers;
+    guint                       channel_watch;
+    int                         tls;
+
+    int                         channel_id;
+    int                         channel_type;
+    SpiceLinkHeader             link_hdr;
+    SpiceLinkMess               link_msg;
+    SpiceLinkHeader             peer_hdr;
+    SpiceLinkReply*             peer_msg;
+    int                         peer_pos;
+
+    int                         message_ack_window;
+    int                         message_ack_count;
+
+    GArray                      *caps;
+    GArray                      *common_caps;
+    GArray                      *remote_caps;
+    GArray                      *remote_common_caps;
+
+    gsize                       total_read_bytes;
+    uint64_t                    last_message_serial;
+    GSList                      *flushing;
+
+    gboolean                    disable_channel_msg;
+    gboolean                    auth_needs_username_and_password;
+    GError                      *error;
+};
+
+SpiceMsgIn *spice_msg_in_new(SpiceChannel *channel);
+SpiceMsgIn *spice_msg_in_sub_new(SpiceChannel *channel, SpiceMsgIn *parent,
+                                   SpiceSubMessage *sub);
+void spice_msg_in_ref(SpiceMsgIn *in);
+void spice_msg_in_unref(SpiceMsgIn *in);
+int spice_msg_in_type(SpiceMsgIn *in);
+void *spice_msg_in_parsed(SpiceMsgIn *in);
+void *spice_msg_in_raw(SpiceMsgIn *in, int *len);
+void spice_msg_in_hexdump(SpiceMsgIn *in);
+
+SpiceMsgOut *spice_msg_out_new(SpiceChannel *channel, int type);
+void spice_msg_out_ref(SpiceMsgOut *out);
+void spice_msg_out_unref(SpiceMsgOut *out);
+void spice_msg_out_send(SpiceMsgOut *out);
+void spice_msg_out_send_internal(SpiceMsgOut *out);
+void spice_msg_out_hexdump(SpiceMsgOut *out, unsigned char *data, int len);
+
+uint16_t spice_header_get_msg_type(uint8_t *header, gboolean is_mini_header);
+uint32_t spice_header_get_msg_size(uint8_t *header, gboolean is_mini_header);
+
+void spice_channel_up(SpiceChannel *channel);
+void spice_channel_wakeup(SpiceChannel *channel, gboolean cancel);
+
+SpiceSession* spice_channel_get_session(SpiceChannel *channel);
+enum spice_channel_state spice_channel_get_state(SpiceChannel *channel);
+
+/* coroutine context */
+typedef void (*handler_msg_in)(SpiceChannel *channel, SpiceMsgIn *msg, gpointer data);
+void spice_channel_recv_msg(SpiceChannel *channel, handler_msg_in handler, gpointer data);
+
+/* channel-base.c */
+void spice_channel_set_handlers(SpiceChannelClass *klass,
+                                const spice_msg_handler* handlers, const int n);
+void spice_channel_handle_wait_for_channels(SpiceChannel *channel, SpiceMsgIn *in);
+
+gint spice_channel_get_channel_id(SpiceChannel *channel);
+gint spice_channel_get_channel_type(SpiceChannel *channel);
+void spice_channel_swap(SpiceChannel *channel, SpiceChannel *swap, gboolean swap_msgs);
+gboolean spice_channel_get_read_only(SpiceChannel *channel);
+void spice_channel_reset(SpiceChannel *channel, gboolean migrating);
+
+void spice_caps_set(GArray *caps, guint32 cap, const gchar *desc);
+#define spice_channel_set_common_capability(channel, cap)               \
+    spice_caps_set(SPICE_CHANNEL(channel)->priv->common_caps, cap, #cap)
+#define spice_channel_set_capability(channel, cap)                      \
+    spice_caps_set(SPICE_CHANNEL(channel)->priv->caps, cap, #cap)
+
+gchar *spice_channel_supported_string(void);
+
+void spice_vmc_write_async(SpiceChannel *self,
+                           const void *buffer, gsize count,
+                           GCancellable *cancellable,
+                           GAsyncReadyCallback callback,
+                           gpointer user_data);
+gssize spice_vmc_write_finish(SpiceChannel *self,
+                              GAsyncResult *result, GError **error);
+
+G_END_DECLS
+
+#endif /* __SPICE_CLIENT_CHANNEL_PRIV_H__ */
diff --git a/src/spice-channel.c b/src/spice-channel.c
new file mode 100644
index 0000000..4e7d8b7
--- /dev/null
+++ b/src/spice-channel.c
@@ -0,0 +1,2960 @@
+/* -*- 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 "glib-compat.h"
+
+#include "spice-channel-priv.h"
+#include "spice-session-priv.h"
+#include "spice-marshal.h"
+#include "bio-gio.h"
+
+#include <glib/gi18n.h>
+
+#include <openssl/rsa.h>
+#include <openssl/evp.h>
+#include <openssl/x509.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/x509v3.h>
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#include <netinet/tcp.h> // TCP_NODELAY
+#endif
+#ifdef HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+#include <ctype.h>
+
+#include "gio-coroutine.h"
+
+static void spice_channel_handle_msg(SpiceChannel *channel, SpiceMsgIn *msg);
+static void spice_channel_write_msg(SpiceChannel *channel, SpiceMsgOut *out);
+static void spice_channel_send_link(SpiceChannel *channel);
+static void channel_reset(SpiceChannel *channel, gboolean migrating);
+static void spice_channel_reset_capabilities(SpiceChannel *channel);
+static void spice_channel_send_migration_handshake(SpiceChannel *channel);
+static gboolean channel_connect(SpiceChannel *channel, gboolean tls);
+
+/**
+ * SECTION:spice-channel
+ * @short_description: the base channel class
+ * @title: Spice Channel
+ * @section_id:
+ * @see_also: #SpiceSession, #SpiceMainChannel and other channels
+ * @stability: Stable
+ * @include: spice-channel.h
+ *
+ * #SpiceChannel is the base class for the different kind of Spice
+ * channel connections, such as #SpiceMainChannel, or
+ * #SpiceInputsChannel.
+ */
+
+/* ------------------------------------------------------------------ */
+/* gobject glue                                                       */
+
+#define SPICE_CHANNEL_GET_PRIVATE(obj)                                  \
+    (G_TYPE_INSTANCE_GET_PRIVATE ((obj), SPICE_TYPE_CHANNEL, SpiceChannelPrivate))
+
+G_DEFINE_TYPE(SpiceChannel, spice_channel, G_TYPE_OBJECT);
+
+/* Properties */
+enum {
+    PROP_0,
+    PROP_SESSION,
+    PROP_CHANNEL_TYPE,
+    PROP_CHANNEL_ID,
+    PROP_TOTAL_READ_BYTES,
+};
+
+/* Signals */
+enum {
+    SPICE_CHANNEL_EVENT,
+    SPICE_CHANNEL_OPEN_FD,
+
+    SPICE_CHANNEL_LAST_SIGNAL,
+};
+
+static guint signals[SPICE_CHANNEL_LAST_SIGNAL];
+
+static void spice_channel_iterate_write(SpiceChannel *channel);
+static void spice_channel_iterate_read(SpiceChannel *channel);
+
+static void spice_channel_init(SpiceChannel *channel)
+{
+    SpiceChannelPrivate *c;
+
+    c = channel->priv = SPICE_CHANNEL_GET_PRIVATE(channel);
+
+    c->out_serial = 1;
+    c->in_serial = 1;
+    c->fd = -1;
+    c->auth_needs_username_and_password = FALSE;
+    strcpy(c->name, "?");
+    c->caps = g_array_new(FALSE, TRUE, sizeof(guint32));
+    c->common_caps = g_array_new(FALSE, TRUE, sizeof(guint32));
+    c->remote_caps = g_array_new(FALSE, TRUE, sizeof(guint32));
+    c->remote_common_caps = g_array_new(FALSE, TRUE, sizeof(guint32));
+    spice_channel_set_common_capability(channel, SPICE_COMMON_CAP_PROTOCOL_AUTH_SELECTION);
+    spice_channel_set_common_capability(channel, SPICE_COMMON_CAP_MINI_HEADER);
+#if HAVE_SASL
+    spice_channel_set_common_capability(channel, SPICE_COMMON_CAP_AUTH_SASL);
+#endif
+    g_queue_init(&c->xmit_queue);
+    STATIC_MUTEX_INIT(c->xmit_queue_lock);
+}
+
+static void spice_channel_constructed(GObject *gobject)
+{
+    SpiceChannel *channel = SPICE_CHANNEL(gobject);
+    SpiceChannelPrivate *c = channel->priv;
+    const char *desc = spice_channel_type_to_string(c->channel_type);
+
+    snprintf(c->name, sizeof(c->name), "%s-%d:%d",
+             desc, c->channel_type, c->channel_id);
+    CHANNEL_DEBUG(channel, "%s", __FUNCTION__);
+
+    const char *disabled  = g_getenv("SPICE_DISABLE_CHANNELS");
+    if (disabled && strstr(disabled, desc))
+        c->disable_channel_msg = TRUE;
+
+    spice_session_channel_new(c->session, channel);
+
+    /* Chain up to the parent class */
+    if (G_OBJECT_CLASS(spice_channel_parent_class)->constructed)
+        G_OBJECT_CLASS(spice_channel_parent_class)->constructed(gobject);
+}
+
+static void spice_channel_dispose(GObject *gobject)
+{
+    SpiceChannel *channel = SPICE_CHANNEL(gobject);
+    SpiceChannelPrivate *c = channel->priv;
+
+    CHANNEL_DEBUG(channel, "%s %p", __FUNCTION__, gobject);
+
+    spice_channel_disconnect(channel, SPICE_CHANNEL_CLOSED);
+
+    if (c->session) {
+         g_object_unref(c->session);
+         c->session = NULL;
+    }
+
+    g_clear_error(&c->error);
+
+    /* Chain up to the parent class */
+    if (G_OBJECT_CLASS(spice_channel_parent_class)->dispose)
+        G_OBJECT_CLASS(spice_channel_parent_class)->dispose(gobject);
+}
+
+static void spice_channel_finalize(GObject *gobject)
+{
+    SpiceChannel *channel = SPICE_CHANNEL(gobject);
+    SpiceChannelPrivate *c = channel->priv;
+
+    CHANNEL_DEBUG(channel, "%s %p", __FUNCTION__, gobject);
+
+    g_idle_remove_by_data(gobject);
+
+    STATIC_MUTEX_CLEAR(c->xmit_queue_lock);
+
+    if (c->caps)
+        g_array_free(c->caps, TRUE);
+
+    if (c->common_caps)
+        g_array_free(c->common_caps, TRUE);
+
+    if (c->remote_caps)
+        g_array_free(c->remote_caps, TRUE);
+
+    if (c->remote_common_caps)
+        g_array_free(c->remote_common_caps, TRUE);
+
+    /* Chain up to the parent class */
+    if (G_OBJECT_CLASS(spice_channel_parent_class)->finalize)
+        G_OBJECT_CLASS(spice_channel_parent_class)->finalize(gobject);
+}
+
+static void spice_channel_get_property(GObject    *gobject,
+                                       guint       prop_id,
+                                       GValue     *value,
+                                       GParamSpec *pspec)
+{
+    SpiceChannel *channel = SPICE_CHANNEL(gobject);
+    SpiceChannelPrivate *c = channel->priv;
+
+    switch (prop_id) {
+    case PROP_SESSION:
+        g_value_set_object(value, c->session);
+        break;
+    case PROP_CHANNEL_TYPE:
+        g_value_set_int(value, c->channel_type);
+        break;
+    case PROP_CHANNEL_ID:
+        g_value_set_int(value, c->channel_id);
+        break;
+    case PROP_TOTAL_READ_BYTES:
+        g_value_set_ulong(value, c->total_read_bytes);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
+        break;
+    }
+}
+
+G_GNUC_INTERNAL
+gint spice_channel_get_channel_id(SpiceChannel *channel)
+{
+    SpiceChannelPrivate *c = channel->priv;
+
+    g_return_val_if_fail(c != NULL, 0);
+    return c->channel_id;
+}
+
+G_GNUC_INTERNAL
+gint spice_channel_get_channel_type(SpiceChannel *channel)
+{
+    SpiceChannelPrivate *c = channel->priv;
+
+    g_return_val_if_fail(c != NULL, 0);
+    return c->channel_type;
+}
+
+static void spice_channel_set_property(GObject      *gobject,
+                                       guint         prop_id,
+                                       const GValue *value,
+                                       GParamSpec   *pspec)
+{
+    SpiceChannel *channel = SPICE_CHANNEL(gobject);
+    SpiceChannelPrivate *c = channel->priv;
+
+    switch (prop_id) {
+    case PROP_SESSION:
+        c->session = g_value_dup_object(value);
+        break;
+    case PROP_CHANNEL_TYPE:
+        c->channel_type = g_value_get_int(value);
+        break;
+    case PROP_CHANNEL_ID:
+        c->channel_id = g_value_get_int(value);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
+        break;
+    }
+}
+
+static void spice_channel_class_init(SpiceChannelClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+    klass->iterate_write = spice_channel_iterate_write;
+    klass->iterate_read  = spice_channel_iterate_read;
+    klass->channel_reset = channel_reset;
+
+    gobject_class->constructed  = spice_channel_constructed;
+    gobject_class->dispose      = spice_channel_dispose;
+    gobject_class->finalize     = spice_channel_finalize;
+    gobject_class->get_property = spice_channel_get_property;
+    gobject_class->set_property = spice_channel_set_property;
+    klass->handle_msg           = spice_channel_handle_msg;
+
+    g_object_class_install_property
+        (gobject_class, PROP_SESSION,
+         g_param_spec_object("spice-session",
+                             "Spice session",
+                             "",
+                             SPICE_TYPE_SESSION,
+                             G_PARAM_READWRITE |
+                             G_PARAM_CONSTRUCT_ONLY |
+                             G_PARAM_STATIC_STRINGS));
+
+    g_object_class_install_property
+        (gobject_class, PROP_CHANNEL_TYPE,
+         g_param_spec_int("channel-type",
+                          "Channel type",
+                          "",
+                          -1, INT_MAX, -1,
+                          G_PARAM_READWRITE |
+                          G_PARAM_CONSTRUCT_ONLY |
+                          G_PARAM_STATIC_STRINGS));
+
+    g_object_class_install_property
+        (gobject_class, PROP_CHANNEL_ID,
+         g_param_spec_int("channel-id",
+                          "Channel ID",
+                          "",
+                          -1, INT_MAX, -1,
+                          G_PARAM_READWRITE |
+                          G_PARAM_CONSTRUCT_ONLY |
+                          G_PARAM_STATIC_STRINGS));
+
+    g_object_class_install_property
+        (gobject_class, PROP_TOTAL_READ_BYTES,
+         g_param_spec_ulong("total-read-bytes",
+                            "Total read bytes",
+                            "",
+                            0, G_MAXULONG, 0,
+                            G_PARAM_READABLE |
+                            G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceChannel::channel-event:
+     * @channel: the channel that emitted the signal
+     * @event: a #SpiceChannelEvent
+     *
+     * The #SpiceChannel::channel-event signal is emitted when the
+     * state of the connection is changed. In case of errors,
+     * spice_channel_get_error() may provide additional informations
+     * on the source of the error.
+     **/
+    signals[SPICE_CHANNEL_EVENT] =
+        g_signal_new("channel-event",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_FIRST,
+                     G_STRUCT_OFFSET(SpiceChannelClass, channel_event),
+                     NULL, NULL,
+                     g_cclosure_marshal_VOID__ENUM,
+                     G_TYPE_NONE,
+                     1,
+                     SPICE_TYPE_CHANNEL_EVENT);
+
+    /**
+     * SpiceChannel::open-fd:
+     * @channel: the channel that emitted the signal
+     * @with_tls: wether TLS connection is requested
+     *
+     * The #SpiceChannel::open-fd signal is emitted when a new
+     * connection is requested. This signal is emitted when the
+     * connection is made with spice_session_open_fd().
+     **/
+    signals[SPICE_CHANNEL_OPEN_FD] =
+        g_signal_new("open-fd",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_FIRST,
+                     G_STRUCT_OFFSET(SpiceChannelClass, open_fd),
+                     NULL, NULL,
+                     g_cclosure_marshal_VOID__INT,
+                     G_TYPE_NONE,
+                     1,
+                     G_TYPE_INT);
+
+    g_type_class_add_private(klass, sizeof(SpiceChannelPrivate));
+
+    SSL_library_init();
+    SSL_load_error_strings();
+}
+
+/* ---------------------------------------------------------------- */
+/* private header api                                               */
+
+static inline void spice_header_set_msg_type(uint8_t *header, gboolean is_mini_header,
+                                             uint16_t type)
+{
+    if (is_mini_header) {
+        ((SpiceMiniDataHeader *)header)->type = type;
+    } else {
+        ((SpiceDataHeader *)header)->type = type;
+    }
+}
+
+static inline void spice_header_set_msg_size(uint8_t *header, gboolean is_mini_header,
+                                             uint32_t size)
+{
+    if (is_mini_header) {
+        ((SpiceMiniDataHeader *)header)->size = size;
+    } else {
+        ((SpiceDataHeader *)header)->size = size;
+    }
+}
+
+G_GNUC_INTERNAL
+uint16_t spice_header_get_msg_type(uint8_t *header, gboolean is_mini_header)
+{
+    if (is_mini_header) {
+        return ((SpiceMiniDataHeader *)header)->type;
+    } else {
+        return ((SpiceDataHeader *)header)->type;
+    }
+}
+
+G_GNUC_INTERNAL
+uint32_t spice_header_get_msg_size(uint8_t *header, gboolean is_mini_header)
+{
+    if (is_mini_header) {
+        return ((SpiceMiniDataHeader *)header)->size;
+    } else {
+        return ((SpiceDataHeader *)header)->size;
+    }
+}
+
+static inline int spice_header_get_header_size(gboolean is_mini_header)
+{
+    return is_mini_header ? sizeof(SpiceMiniDataHeader) : sizeof(SpiceDataHeader);
+}
+
+static inline void spice_header_set_msg_serial(uint8_t *header, gboolean is_mini_header,
+                                               uint64_t serial)
+{
+    if (!is_mini_header) {
+        ((SpiceDataHeader *)header)->serial = serial;
+    }
+}
+
+static inline void spice_header_reset_msg_sub_list(uint8_t *header, gboolean is_mini_header)
+{
+    if (!is_mini_header) {
+        ((SpiceDataHeader *)header)->sub_list = 0;
+    }
+}
+
+static inline uint64_t spice_header_get_in_msg_serial(SpiceMsgIn *in)
+{
+    SpiceChannelPrivate *c = in->channel->priv;
+    uint8_t *header = in->header;
+
+    if (c->use_mini_header) {
+        return c->in_serial;
+    } else {
+        return ((SpiceDataHeader *)header)->serial;
+    }
+}
+
+static inline uint64_t spice_header_get_out_msg_serial(SpiceMsgOut *out)
+{
+    SpiceChannelPrivate *c = out->channel->priv;
+
+    if (c->use_mini_header) {
+        return c->out_serial;
+    } else {
+        return ((SpiceDataHeader *)out->header)->serial;
+    }
+}
+
+static inline uint32_t spice_header_get_msg_sub_list(uint8_t *header, gboolean is_mini_header)
+{
+    if (is_mini_header) {
+        return 0;
+    } else {
+        return ((SpiceDataHeader *)header)->sub_list;
+    }
+}
+
+/* ---------------------------------------------------------------- */
+/* private msg api                                                  */
+
+G_GNUC_INTERNAL
+SpiceMsgIn *spice_msg_in_new(SpiceChannel *channel)
+{
+    SpiceMsgIn *in;
+
+    g_return_val_if_fail(channel != NULL, NULL);
+
+    in = g_slice_new0(SpiceMsgIn);
+    in->refcount = 1;
+    in->channel  = channel;
+
+    return in;
+}
+
+G_GNUC_INTERNAL
+SpiceMsgIn *spice_msg_in_sub_new(SpiceChannel *channel, SpiceMsgIn *parent,
+                                   SpiceSubMessage *sub)
+{
+    SpiceMsgIn *in;
+
+    g_return_val_if_fail(channel != NULL, NULL);
+
+    in = spice_msg_in_new(channel);
+    spice_header_set_msg_type(in->header, channel->priv->use_mini_header, sub->type);
+    spice_header_set_msg_size(in->header, channel->priv->use_mini_header, sub->size);
+    in->data = (uint8_t*)(sub+1);
+    in->dpos = sub->size;
+    in->parent = parent;
+    spice_msg_in_ref(parent);
+    return in;
+}
+
+G_GNUC_INTERNAL
+void spice_msg_in_ref(SpiceMsgIn *in)
+{
+    g_return_if_fail(in != NULL);
+
+    in->refcount++;
+}
+
+G_GNUC_INTERNAL
+void spice_msg_in_unref(SpiceMsgIn *in)
+{
+    g_return_if_fail(in != NULL);
+
+    in->refcount--;
+    if (in->refcount > 0)
+        return;
+    if (in->parsed)
+        in->pfree(in->parsed);
+    if (in->parent) {
+        spice_msg_in_unref(in->parent);
+    } else {
+        g_free(in->data);
+    }
+    g_slice_free(SpiceMsgIn, in);
+}
+
+G_GNUC_INTERNAL
+int spice_msg_in_type(SpiceMsgIn *in)
+{
+    g_return_val_if_fail(in != NULL, -1);
+
+    return spice_header_get_msg_type(in->header, in->channel->priv->use_mini_header);
+}
+
+G_GNUC_INTERNAL
+void *spice_msg_in_parsed(SpiceMsgIn *in)
+{
+    g_return_val_if_fail(in != NULL, NULL);
+
+    return in->parsed;
+}
+
+G_GNUC_INTERNAL
+void *spice_msg_in_raw(SpiceMsgIn *in, int *len)
+{
+    g_return_val_if_fail(in != NULL, NULL);
+    g_return_val_if_fail(len != NULL, NULL);
+
+    *len = in->dpos;
+    return in->data;
+}
+
+static void hexdump(const char *prefix, unsigned char *data, int len)
+{
+    int i;
+
+    for (i = 0; i < len; i++) {
+        if (i % 16 == 0)
+            fprintf(stderr, "%s:", prefix);
+        if (i % 4 == 0)
+            fprintf(stderr, " ");
+        fprintf(stderr, " %02x", data[i]);
+        if (i % 16 == 15)
+            fprintf(stderr, "\n");
+    }
+    if (i % 16 != 0)
+        fprintf(stderr, "\n");
+}
+
+G_GNUC_INTERNAL
+void spice_msg_in_hexdump(SpiceMsgIn *in)
+{
+    SpiceChannelPrivate *c = in->channel->priv;
+
+    fprintf(stderr, "--\n<< hdr: %s serial %" PRIu64 " type %d size %d sub-list %d\n",
+            c->name, spice_header_get_in_msg_serial(in),
+            spice_header_get_msg_type(in->header, c->use_mini_header),
+            spice_header_get_msg_size(in->header, c->use_mini_header),
+            spice_header_get_msg_sub_list(in->header, c->use_mini_header));
+    hexdump("<< msg", in->data, in->dpos);
+}
+
+G_GNUC_INTERNAL
+void spice_msg_out_hexdump(SpiceMsgOut *out, unsigned char *data, int len)
+{
+    SpiceChannelPrivate *c = out->channel->priv;
+
+    fprintf(stderr, "--\n>> hdr: %s serial %" PRIu64 " type %d size %d sub-list %d\n",
+            c->name,
+            spice_header_get_out_msg_serial(out),
+            spice_header_get_msg_type(out->header, c->use_mini_header),
+            spice_header_get_msg_size(out->header, c->use_mini_header),
+            spice_header_get_msg_sub_list(out->header, c->use_mini_header));
+    hexdump(">> msg", data, len);
+}
+
+static gboolean msg_check_read_only (int channel_type, int msg_type)
+{
+    if (msg_type < 100) // those are the common messages
+        return FALSE;
+
+    switch (channel_type) {
+    /* messages allowed to be sent in read-only mode */
+    case SPICE_CHANNEL_MAIN:
+        switch (msg_type) {
+        case SPICE_MSGC_MAIN_CLIENT_INFO:
+        case SPICE_MSGC_MAIN_MIGRATE_CONNECTED:
+        case SPICE_MSGC_MAIN_MIGRATE_CONNECT_ERROR:
+        case SPICE_MSGC_MAIN_ATTACH_CHANNELS:
+        case SPICE_MSGC_MAIN_MIGRATE_END:
+            return FALSE;
+        }
+        break;
+    case SPICE_CHANNEL_DISPLAY:
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+G_GNUC_INTERNAL
+SpiceMsgOut *spice_msg_out_new(SpiceChannel *channel, int type)
+{
+    SpiceChannelPrivate *c = channel->priv;
+    SpiceMsgOut *out;
+
+    g_return_val_if_fail(c != NULL, NULL);
+
+    out = g_slice_new0(SpiceMsgOut);
+    out->refcount = 1;
+    out->channel  = channel;
+    out->ro_check = msg_check_read_only(c->channel_type, type);
+
+    out->marshallers = c->marshallers;
+    out->marshaller = spice_marshaller_new();
+
+    out->header = spice_marshaller_reserve_space(out->marshaller,
+                                                 spice_header_get_header_size(c->use_mini_header));
+    spice_marshaller_set_base(out->marshaller, spice_header_get_header_size(c->use_mini_header));
+    spice_header_set_msg_type(out->header, c->use_mini_header, type);
+    spice_header_set_msg_serial(out->header, c->use_mini_header, c->out_serial);
+    spice_header_reset_msg_sub_list(out->header, c->use_mini_header);
+
+    c->out_serial++;
+    return out;
+}
+
+G_GNUC_INTERNAL
+void spice_msg_out_ref(SpiceMsgOut *out)
+{
+    g_return_if_fail(out != NULL);
+
+    out->refcount++;
+}
+
+G_GNUC_INTERNAL
+void spice_msg_out_unref(SpiceMsgOut *out)
+{
+    g_return_if_fail(out != NULL);
+
+    out->refcount--;
+    if (out->refcount > 0)
+        return;
+    spice_marshaller_destroy(out->marshaller);
+    g_slice_free(SpiceMsgOut, out);
+}
+
+/* system context */
+static gboolean spice_channel_idle_wakeup(gpointer user_data)
+{
+    SpiceChannel *channel = SPICE_CHANNEL(user_data);
+    SpiceChannelPrivate *c = channel->priv;
+
+    /*
+     * Note:
+     *
+     * - This must be done before the wakeup as that may eventually
+     *   call channel_reset() which checks this.
+     * - The lock calls are really necessary, this fixes the following race:
+     *   1) usb-event-thread calls spice_msg_out_send()
+     *   2) spice_msg_out_send calls g_timeout_add_full(...)
+     *   3) we run, set xmit_queue_wakeup_id to 0
+     *   4) spice_msg_out_send stores the result of g_timeout_add_full() in
+     *      xmit_queue_wakeup_id, overwriting the 0 we just stored
+     *   5) xmit_queue_wakeup_id now says there is a wakeup pending which is
+     *      false
+     */
+    STATIC_MUTEX_LOCK(c->xmit_queue_lock);
+    c->xmit_queue_wakeup_id = 0;
+    STATIC_MUTEX_UNLOCK(c->xmit_queue_lock);
+
+    spice_channel_wakeup(channel, FALSE);
+
+    return FALSE;
+}
+
+/* any context (system/co-routine/usb-event-thread) */
+G_GNUC_INTERNAL
+void spice_msg_out_send(SpiceMsgOut *out)
+{
+    SpiceChannelPrivate *c;
+    gboolean was_empty;
+
+    g_return_if_fail(out != NULL);
+    g_return_if_fail(out->channel != NULL);
+    c = out->channel->priv;
+
+    STATIC_MUTEX_LOCK(c->xmit_queue_lock);
+    if (c->xmit_queue_blocked) {
+        g_warning("message queue is blocked, dropping message");
+        goto end;
+    }
+
+    was_empty = g_queue_is_empty(&c->xmit_queue);
+    g_queue_push_tail(&c->xmit_queue, out);
+
+    /* One wakeup is enough to empty the entire queue -> only do a wakeup
+       if the queue was empty, and there isn't one pending already. */
+    if (was_empty && !c->xmit_queue_wakeup_id) {
+        c->xmit_queue_wakeup_id =
+            /* Use g_timeout_add_full so that can specify the priority */
+            g_timeout_add_full(G_PRIORITY_HIGH, 0,
+                               spice_channel_idle_wakeup,
+                               out->channel, NULL);
+    }
+
+end:
+    STATIC_MUTEX_UNLOCK(c->xmit_queue_lock);
+}
+
+/* coroutine context */
+G_GNUC_INTERNAL
+void spice_msg_out_send_internal(SpiceMsgOut *out)
+{
+    g_return_if_fail(out != NULL);
+
+    spice_channel_write_msg(out->channel, out);
+}
+
+/*
+ * Write all 'data' of length 'datalen' bytes out to
+ * the wire
+ */
+/* coroutine context */
+static void spice_channel_flush_wire(SpiceChannel *channel,
+                                     const void *data,
+                                     size_t datalen)
+{
+    SpiceChannelPrivate *c = channel->priv;
+    const char *ptr = data;
+    size_t offset = 0;
+    GIOCondition cond;
+
+    while (offset < datalen) {
+        gssize ret;
+        GError *error = NULL;
+
+        if (c->has_error) return;
+
+        cond = 0;
+        if (c->tls) {
+            ret = SSL_write(c->ssl, ptr+offset, datalen-offset);
+            if (ret < 0) {
+                ret = SSL_get_error(c->ssl, ret);
+                if (ret == SSL_ERROR_WANT_READ)
+                    cond |= G_IO_IN;
+                if (ret == SSL_ERROR_WANT_WRITE)
+                    cond |= G_IO_OUT;
+                ret = -1;
+            }
+        } else {
+            ret = g_pollable_output_stream_write_nonblocking(G_POLLABLE_OUTPUT_STREAM(c->out),
+                                                             ptr+offset, datalen-offset, NULL, &error);
+            if (ret < 0) {
+                if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
+                    cond = G_IO_OUT;
+                } else {
+                    CHANNEL_DEBUG(channel, "Send error %s", error->message);
+                }
+                g_clear_error(&error);
+                ret = -1;
+            }
+        }
+        if (ret == -1) {
+            if (cond != 0) {
+                // TODO: should use g_pollable_input/output_stream_create_source() in 2.28 ?
+                g_coroutine_socket_wait(&c->coroutine, c->sock, cond);
+                continue;
+            } else {
+                CHANNEL_DEBUG(channel, "Closing the channel: spice_channel_flush %d", errno);
+                c->has_error = TRUE;
+                return;
+            }
+        }
+        if (ret == 0) {
+            CHANNEL_DEBUG(channel, "Closing the connection: spice_channel_flush");
+            c->has_error = TRUE;
+            return;
+        }
+        offset += ret;
+    }
+}
+
+#if HAVE_SASL
+/*
+ * Encode all buffered data, write all encrypted data out
+ * to the wire
+ */
+static void spice_channel_flush_sasl(SpiceChannel *channel, const void *data, size_t len)
+{
+    SpiceChannelPrivate *c = channel->priv;
+    const char *output;
+    unsigned int outputlen;
+    int err;
+
+    err = sasl_encode(c->sasl_conn, data, len, &output, &outputlen);
+    if (err != SASL_OK) {
+        g_warning ("Failed to encode SASL data %s",
+                   sasl_errstring(err, NULL, NULL));
+        c->has_error = TRUE;
+        return;
+    }
+
+    //CHANNEL_DEBUG(channel, "Flush SASL %d: %p %d", len, output, outputlen);
+    spice_channel_flush_wire(channel, output, outputlen);
+}
+#endif
+
+/* coroutine context */
+static void spice_channel_write(SpiceChannel *channel, const void *data, size_t len)
+{
+#if HAVE_SASL
+    SpiceChannelPrivate *c = channel->priv;
+
+    if (c->sasl_conn)
+        spice_channel_flush_sasl(channel, data, len);
+    else
+#endif
+        spice_channel_flush_wire(channel, data, len);
+}
+
+/* coroutine context */
+static void spice_channel_write_msg(SpiceChannel *channel, SpiceMsgOut *out)
+{
+    uint8_t *data;
+    int free_data;
+    size_t len;
+    uint32_t msg_size;
+
+    g_return_if_fail(channel != NULL);
+    g_return_if_fail(out != NULL);
+    g_return_if_fail(channel == out->channel);
+
+    if (out->ro_check &&
+        spice_channel_get_read_only(channel)) {
+        g_warning("Try to send message while read-only. Please report a bug.");
+        return;
+    }
+
+    msg_size = spice_marshaller_get_total_size(out->marshaller) -
+               spice_header_get_header_size(channel->priv->use_mini_header);
+    spice_header_set_msg_size(out->header, channel->priv->use_mini_header, msg_size);
+    data = spice_marshaller_linearize(out->marshaller, 0, &len, &free_data);
+    /* spice_msg_out_hexdump(out, data, len); */
+    spice_channel_write(channel, data, len);
+
+    if (free_data)
+        g_free(data);
+
+    spice_msg_out_unref(out);
+}
+
+/*
+ * Read at least 1 more byte of data straight off the wire
+ * into the requested buffer.
+ */
+/* coroutine context */
+static int spice_channel_read_wire(SpiceChannel *channel, void *data, size_t len)
+{
+    SpiceChannelPrivate *c = channel->priv;
+    gssize ret;
+    GIOCondition cond;
+
+reread:
+
+    if (c->has_error) return 0; /* has_error is set by disconnect(), return no error */
+
+    cond = 0;
+    if (c->tls) {
+        ret = SSL_read(c->ssl, data, len);
+        if (ret < 0) {
+            ret = SSL_get_error(c->ssl, ret);
+            if (ret == SSL_ERROR_WANT_READ)
+                cond |= G_IO_IN;
+            if (ret == SSL_ERROR_WANT_WRITE)
+                cond |= G_IO_OUT;
+            ret = -1;
+        }
+    } else {
+        GError *error = NULL;
+        ret = g_pollable_input_stream_read_nonblocking(G_POLLABLE_INPUT_STREAM(c->in),
+                                                       data, len, NULL, &error);
+        if (ret < 0) {
+            if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
+                cond = G_IO_IN;
+            } else {
+                CHANNEL_DEBUG(channel, "Read error %s", error->message);
+            }
+            g_clear_error(&error);
+            ret = -1;
+        }
+    }
+
+    if (ret == -1) {
+        if (cond != 0) {
+            // TODO: should use g_pollable_input/output_stream_create_source() ?
+            g_coroutine_socket_wait(&c->coroutine, c->sock, cond);
+            goto reread;
+        } else {
+            c->has_error = TRUE;
+            return -errno;
+        }
+    }
+    if (ret == 0) {
+        CHANNEL_DEBUG(channel, "Closing the connection: spice_channel_read() - ret=0");
+        c->has_error = TRUE;
+        return 0;
+    }
+
+    return ret;
+}
+
+#if HAVE_SASL
+/*
+ * Read at least 1 more byte of data out of the SASL decrypted
+ * data buffer, into the internal read buffer
+ */
+static int spice_channel_read_sasl(SpiceChannel *channel, void *data, size_t len)
+{
+    SpiceChannelPrivate *c = channel->priv;
+
+    /* CHANNEL_DEBUG(channel, "Read %lu SASL %p size %d offset %d", len, c->sasl_decoded, */
+    /*             c->sasl_decoded_length, c->sasl_decoded_offset); */
+
+    if (c->sasl_decoded == NULL || c->sasl_decoded_length == 0) {
+        char encoded[8192]; /* should stay lower than maxbufsize */
+        int err, ret;
+
+        g_warn_if_fail(c->sasl_decoded_offset == 0);
+
+        ret = spice_channel_read_wire(channel, encoded, sizeof(encoded));
+        if (ret < 0)
+            return ret;
+
+        err = sasl_decode(c->sasl_conn, encoded, ret,
+                          &c->sasl_decoded, &c->sasl_decoded_length);
+        if (err != SASL_OK) {
+            g_warning("Failed to decode SASL data %s",
+                      sasl_errstring(err, NULL, NULL));
+            c->has_error = TRUE;
+            return -EINVAL;
+        }
+        c->sasl_decoded_offset = 0;
+    }
+
+    if (c->sasl_decoded_length == 0)
+        return 0;
+
+    len = MIN(c->sasl_decoded_length - c->sasl_decoded_offset, len);
+    memcpy(data, c->sasl_decoded + c->sasl_decoded_offset, len);
+    c->sasl_decoded_offset += len;
+
+    if (c->sasl_decoded_offset == c->sasl_decoded_length) {
+        c->sasl_decoded_length = c->sasl_decoded_offset = 0;
+        c->sasl_decoded = NULL;
+    }
+
+    return len;
+}
+#endif
+
+/*
+ * Fill the 'data' buffer up with exactly 'len' bytes worth of data
+ */
+/* coroutine context */
+static int spice_channel_read(SpiceChannel *channel, void *data, size_t length)
+{
+    SpiceChannelPrivate *c = channel->priv;
+    gsize len = length;
+    int ret;
+
+    while (len > 0) {
+        if (c->has_error) return 0; /* has_error is set by disconnect(), return no error */
+
+#if HAVE_SASL
+        if (c->sasl_conn)
+            ret = spice_channel_read_sasl(channel, data, len);
+        else
+#endif
+            ret = spice_channel_read_wire(channel, data, len);
+        if (ret < 0)
+            return ret;
+        g_assert(ret <= len);
+        len -= ret;
+        data = ((char*)data) + ret;
+#if DEBUG
+        if (len > 0)
+            CHANNEL_DEBUG(channel, "still needs %" G_GSIZE_FORMAT, len);
+#endif
+    }
+    c->total_read_bytes += length;
+
+    return length;
+}
+
+/* coroutine context */
+static void spice_channel_send_spice_ticket(SpiceChannel *channel)
+{
+    SpiceChannelPrivate *c = channel->priv;
+    EVP_PKEY *pubkey;
+    int nRSASize;
+    BIO *bioKey;
+    RSA *rsa;
+    char *password;
+    uint8_t *encrypted;
+    int rc;
+
+    bioKey = BIO_new(BIO_s_mem());
+    g_return_if_fail(bioKey != NULL);
+
+    BIO_write(bioKey, c->peer_msg->pub_key, SPICE_TICKET_PUBKEY_BYTES);
+    pubkey = d2i_PUBKEY_bio(bioKey, NULL);
+    g_return_if_fail(pubkey != NULL);
+
+    rsa = pubkey->pkey.rsa;
+    nRSASize = RSA_size(rsa);
+
+    encrypted = g_alloca(nRSASize);
+    /*
+      The use of RSA encryption limit the potential maximum password length.
+      for RSA_PKCS1_OAEP_PADDING it is RSA_size(rsa) - 41.
+    */
+    g_object_get(c->session, "password", &password, NULL);
+    if (password == NULL)
+        password = g_strdup("");
+    rc = RSA_public_encrypt(strlen(password) + 1, (uint8_t*)password,
+                            encrypted, rsa, RSA_PKCS1_OAEP_PADDING);
+    g_warn_if_fail(rc > 0);
+
+    spice_channel_write(channel, encrypted, nRSASize);
+    memset(encrypted, 0, nRSASize);
+    EVP_PKEY_free(pubkey);
+    BIO_free(bioKey);
+    g_free(password);
+}
+
+/* coroutine context */
+static void spice_channel_failed_authentication(SpiceChannel *channel)
+{
+    SpiceChannelPrivate *c = channel->priv;
+
+    if (c->auth_needs_username_and_password)
+        g_set_error_literal(&c->error,
+                            SPICE_CLIENT_ERROR,
+                            SPICE_CLIENT_ERROR_AUTH_NEEDS_PASSWORD_AND_USERNAME,
+                            _("Authentication failed: password and username are required"));
+    else
+        g_set_error_literal(&c->error,
+                            SPICE_CLIENT_ERROR,
+                            SPICE_CLIENT_ERROR_AUTH_NEEDS_PASSWORD,
+                            _("Authentication failed: password is required"));
+
+    c->event = SPICE_CHANNEL_ERROR_AUTH;
+
+    c->has_error = TRUE; /* force disconnect */
+}
+
+/* coroutine context */
+static gboolean spice_channel_recv_auth(SpiceChannel *channel)
+{
+    SpiceChannelPrivate *c = channel->priv;
+    uint32_t link_res;
+    int rc;
+
+    rc = spice_channel_read(channel, &link_res, sizeof(link_res));
+    if (rc != sizeof(link_res)) {
+        CHANNEL_DEBUG(channel, "incomplete auth reply (%d/%" G_GSIZE_FORMAT ")",
+                    rc, sizeof(link_res));
+        c->event = SPICE_CHANNEL_ERROR_LINK;
+        return FALSE;
+    }
+
+    if (link_res != SPICE_LINK_ERR_OK) {
+        CHANNEL_DEBUG(channel, "link result: reply %d", link_res);
+        spice_channel_failed_authentication(channel);
+        return FALSE;
+    }
+
+    c->state = SPICE_CHANNEL_STATE_READY;
+
+    g_coroutine_signal_emit(channel, signals[SPICE_CHANNEL_EVENT], 0, SPICE_CHANNEL_OPENED);
+
+    if (c->state == SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE) {
+        spice_channel_send_migration_handshake(channel);
+    }
+
+    if (c->state != SPICE_CHANNEL_STATE_MIGRATING)
+        spice_channel_up(channel);
+
+    return TRUE;
+}
+
+G_GNUC_INTERNAL
+void spice_channel_up(SpiceChannel *channel)
+{
+    SpiceChannelPrivate *c = channel->priv;
+
+    CHANNEL_DEBUG(channel, "channel up, state %d", c->state);
+
+    if (SPICE_CHANNEL_GET_CLASS(channel)->channel_up)
+        SPICE_CHANNEL_GET_CLASS(channel)->channel_up(channel);
+}
+
+/* coroutine context */
+static void spice_channel_send_link(SpiceChannel *channel)
+{
+    SpiceChannelPrivate *c = channel->priv;
+    uint8_t *buffer, *p;
+    int protocol, i;
+
+    c->link_hdr.magic = SPICE_MAGIC;
+    c->link_hdr.size = sizeof(c->link_msg);
+
+    g_object_get(c->session, "protocol", &protocol, NULL);
+    switch (protocol) {
+    case 1: /* protocol 1 == major 1, old 0.4 protocol, last active minor */
+        c->link_hdr.major_version = 1;
+        c->link_hdr.minor_version = 3;
+        c->parser = spice_get_server_channel_parser1(c->channel_type, NULL);
+        c->marshallers = spice_message_marshallers_get1();
+        break;
+    case SPICE_VERSION_MAJOR: /* protocol 2 == current */
+        c->link_hdr.major_version = SPICE_VERSION_MAJOR;
+        c->link_hdr.minor_version = SPICE_VERSION_MINOR;
+        c->parser = spice_get_server_channel_parser(c->channel_type, NULL);
+        c->marshallers = spice_message_marshallers_get();
+        break;
+    default:
+        g_critical("unknown major %d", protocol);
+        return;
+    }
+
+    c->link_msg.connection_id = spice_session_get_connection_id(c->session);
+    c->link_msg.channel_type  = c->channel_type;
+    c->link_msg.channel_id    = c->channel_id;
+    c->link_msg.caps_offset   = sizeof(c->link_msg);
+
+    c->link_msg.num_common_caps = c->common_caps->len;
+    c->link_msg.num_channel_caps = c->caps->len;
+    c->link_hdr.size += (c->link_msg.num_common_caps +
+                         c->link_msg.num_channel_caps) * sizeof(uint32_t);
+
+    buffer = g_malloc0(sizeof(c->link_hdr) + c->link_hdr.size);
+    p = buffer;
+
+    memcpy(p, &c->link_hdr, sizeof(c->link_hdr)); p += sizeof(c->link_hdr);
+    memcpy(p, &c->link_msg, sizeof(c->link_msg)); p += sizeof(c->link_msg);
+
+    for (i = 0; i < c->common_caps->len; i++) {
+        *(uint32_t *)p = g_array_index(c->common_caps, uint32_t, i);
+        p += sizeof(uint32_t);
+    }
+    for (i = 0; i < c->caps->len; i++) {
+        *(uint32_t *)p = g_array_index(c->caps, uint32_t, i);
+        p += sizeof(uint32_t);
+    }
+    CHANNEL_DEBUG(channel, "channel type %d id %d num common caps %d num caps %d",
+                  c->link_msg.channel_type,
+                  c->link_msg.channel_id,
+                  c->link_msg.num_common_caps,
+                  c->link_msg.num_channel_caps);
+    spice_channel_write(channel, buffer, p - buffer);
+    g_free(buffer);
+}
+
+/* coroutine context */
+static gboolean spice_channel_recv_link_hdr(SpiceChannel *channel)
+{
+    SpiceChannelPrivate *c = channel->priv;
+    int rc;
+
+    rc = spice_channel_read(channel, &c->peer_hdr, sizeof(c->peer_hdr));
+    if (rc != sizeof(c->peer_hdr)) {
+        g_warning("incomplete link header (%d/%" G_GSIZE_FORMAT ")",
+                  rc, sizeof(c->peer_hdr));
+        goto error;
+    }
+    if (c->peer_hdr.magic != SPICE_MAGIC) {
+        g_warning("invalid SPICE_MAGIC!");
+        goto error;
+    }
+
+    CHANNEL_DEBUG(channel, "Peer version: %d:%d", c->peer_hdr.major_version, c->peer_hdr.minor_version);
+    if (c->peer_hdr.major_version != c->link_hdr.major_version) {
+        g_warning("major mismatch (got %d, expected %d)",
+                  c->peer_hdr.major_version, c->link_hdr.major_version);
+        goto error;
+    }
+
+    c->peer_msg = g_malloc0(c->peer_hdr.size);
+    if (c->peer_msg == NULL) {
+        g_warning("invalid peer header size: %u", c->peer_hdr.size);
+        goto error;
+    }
+
+    return TRUE;
+
+error:
+    /* Windows socket seems to give early CONNRESET errors. The server
+       does not linger when closing the socket if the protocol is
+       incompatible. Try with the oldest protocol in this case: */
+    if (c->link_hdr.major_version != 1) {
+        SPICE_DEBUG("%s: error, switching to protocol 1 (spice 0.4)", c->name);
+        c->state = SPICE_CHANNEL_STATE_RECONNECTING;
+        g_object_set(c->session, "protocol", 1, NULL);
+        return FALSE;
+    }
+
+    c->event = SPICE_CHANNEL_ERROR_LINK;
+    return FALSE;
+}
+
+#if HAVE_SASL
+/*
+ * NB, keep in sync with similar method in spice/server/reds.c
+ */
+static gchar *addr_to_string(GSocketAddress *addr)
+{
+    GInetSocketAddress *iaddr = G_INET_SOCKET_ADDRESS(addr);
+    guint16 port;
+    GInetAddress *host;
+    gchar *hoststr;
+    gchar *ret;
+
+    host = g_inet_socket_address_get_address(iaddr);
+    port = g_inet_socket_address_get_port(iaddr);
+    hoststr = g_inet_address_to_string(host);
+
+    ret = g_strdup_printf("%s;%hu", hoststr, port);
+    g_free(hoststr);
+
+    return ret;
+}
+
+static gboolean
+spice_channel_gather_sasl_credentials(SpiceChannel *channel,
+				       sasl_interact_t *interact)
+{
+    SpiceChannelPrivate *c;
+    int ninteract;
+    gboolean ret = TRUE;
+
+    g_return_val_if_fail(channel != NULL, FALSE);
+    g_return_val_if_fail(channel->priv != NULL, FALSE);
+
+    c = channel->priv;
+
+    /* FIXME: we could keep connection open and ask connection details if missing */
+
+    for (ninteract = 0 ; interact[ninteract].id != 0 ; ninteract++) {
+        switch (interact[ninteract].id) {
+        case SASL_CB_AUTHNAME:
+        case SASL_CB_USER:
+            c->auth_needs_username_and_password = TRUE;
+            if (spice_session_get_username(c->session) == NULL)
+                return FALSE;
+
+            interact[ninteract].result =  spice_session_get_username(c->session);
+            interact[ninteract].len = strlen(interact[ninteract].result);
+            break;
+
+        case SASL_CB_PASS:
+            if (spice_session_get_password(c->session) == NULL) {
+                /* Even if we reach this point, we have to continue looking for
+                 * SASL_CB_AUTHNAME|SASL_CB_USER, otherwise we would return a
+                 * wrong error to the applications */
+                ret = FALSE;
+                continue;
+            }
+
+            interact[ninteract].result =  spice_session_get_password(c->session);
+            interact[ninteract].len = strlen(interact[ninteract].result);
+            break;
+        }
+    }
+
+    CHANNEL_DEBUG(channel, "Filled SASL interact");
+
+    return ret;
+}
+
+/*
+ *
+ * Init msg from server
+ *
+ *  u32 mechlist-length
+ *  u8-array mechlist-string
+ *
+ * Start msg to server
+ *
+ *  u32 mechname-length
+ *  u8-array mechname-string
+ *  u32 clientout-length
+ *  u8-array clientout-string
+ *
+ * Start msg from server
+ *
+ *  u32 serverin-length
+ *  u8-array serverin-string
+ *  u8 continue
+ *
+ * Step msg to server
+ *
+ *  u32 clientout-length
+ *  u8-array clientout-string
+ *
+ * Step msg from server
+ *
+ *  u32 serverin-length
+ *  u8-array serverin-string
+ *  u8 continue
+ */
+
+#define SASL_MAX_MECHLIST_LEN 300
+#define SASL_MAX_MECHNAME_LEN 100
+#define SASL_MAX_DATA_LEN (1024 * 1024)
+
+/* Perform the SASL authentication process
+ */
+static gboolean spice_channel_perform_auth_sasl(SpiceChannel *channel)
+{
+    SpiceChannelPrivate *c;
+    sasl_conn_t *saslconn = NULL;
+    sasl_security_properties_t secprops;
+    const char *clientout;
+    char *serverin = NULL;
+    unsigned int clientoutlen;
+    int err;
+    char *localAddr = NULL, *remoteAddr = NULL;
+    const void *val;
+    sasl_ssf_t ssf;
+    static const sasl_callback_t saslcb[] = {
+        { .id = SASL_CB_USER },
+        { .id = SASL_CB_AUTHNAME },
+        { .id = SASL_CB_PASS },
+        { .id = 0 },
+    };
+    sasl_interact_t *interact = NULL;
+    guint32 len;
+    char *mechlist = NULL;
+    const char *mechname;
+    gboolean ret = FALSE;
+    GSocketAddress *addr = NULL;
+    guint8 complete;
+
+    g_return_val_if_fail(channel != NULL, FALSE);
+    g_return_val_if_fail(channel->priv != NULL, FALSE);
+
+    c = channel->priv;
+
+    /* Sets up the SASL library as a whole */
+    err = sasl_client_init(NULL);
+    CHANNEL_DEBUG(channel, "Client initialize SASL authentication %d", err);
+    if (err != SASL_OK) {
+        g_critical("failed to initialize SASL library: %d (%s)",
+                   err, sasl_errstring(err, NULL, NULL));
+        goto error;
+    }
+
+    /* Get local address in form  IPADDR:PORT */
+    addr = g_socket_get_local_address(c->sock, NULL);
+    if (!addr) {
+        g_critical("failed to get local address");
+        goto error;
+    }
+    if ((g_socket_address_get_family(addr) == G_SOCKET_FAMILY_IPV4 ||
+         g_socket_address_get_family(addr) == G_SOCKET_FAMILY_IPV6) &&
+        (localAddr = addr_to_string(addr)) == NULL)
+        goto error;
+    g_clear_object(&addr);
+
+    /* Get remote address in form  IPADDR:PORT */
+    addr = g_socket_get_remote_address(c->sock, NULL);
+    if (!addr) {
+        g_critical("failed to get peer address");
+        goto error;
+    }
+    if ((g_socket_address_get_family(addr) == G_SOCKET_FAMILY_IPV4 ||
+         g_socket_address_get_family(addr) == G_SOCKET_FAMILY_IPV6) &&
+        (remoteAddr = addr_to_string(addr)) == NULL)
+        goto error;
+    g_clear_object(&addr);
+
+    CHANNEL_DEBUG(channel, "Client SASL new host:'%s' local:'%s' remote:'%s'",
+                  spice_session_get_host(c->session), localAddr, remoteAddr);
+
+    /* Setup a handle for being a client */
+    err = sasl_client_new("spice",
+                          spice_session_get_host(c->session),
+                          localAddr,
+                          remoteAddr,
+                          saslcb,
+                          SASL_SUCCESS_DATA,
+                          &saslconn);
+
+    if (err != SASL_OK) {
+        g_critical("Failed to create SASL client context: %d (%s)",
+                   err, sasl_errstring(err, NULL, NULL));
+        goto error;
+    }
+
+    if (c->ssl) {
+        sasl_ssf_t ssf;
+
+        ssf = SSL_get_cipher_bits(c->ssl, NULL);
+        err = sasl_setprop(saslconn, SASL_SSF_EXTERNAL, &ssf);
+        if (err != SASL_OK) {
+            g_critical("cannot set SASL external SSF %d (%s)",
+                       err, sasl_errstring(err, NULL, NULL));
+            goto error;
+        }
+    }
+
+    memset(&secprops, 0, sizeof secprops);
+    /* If we've got TLS, we don't care about SSF */
+    secprops.min_ssf = c->ssl ? 0 : 56; /* Equiv to DES supported by all Kerberos */
+    secprops.max_ssf = c->ssl ? 0 : 100000; /* Very strong ! AES == 256 */
+    secprops.maxbufsize = 100000;
+    /* If we're not TLS, then forbid any anonymous or trivially crackable auth */
+    secprops.security_flags = c->ssl ? 0 :
+        SASL_SEC_NOANONYMOUS | SASL_SEC_NOPLAINTEXT;
+
+    err = sasl_setprop(saslconn, SASL_SEC_PROPS, &secprops);
+    if (err != SASL_OK) {
+        g_critical("cannot set security props %d (%s)",
+                   err, sasl_errstring(err, NULL, NULL));
+        goto error;
+    }
+
+    /* Get the supported mechanisms from the server */
+    spice_channel_read(channel, &len, sizeof(len));
+    if (c->has_error)
+        goto error;
+    if (len > SASL_MAX_MECHLIST_LEN) {
+        g_critical("mechlistlen %d too long", len);
+        goto error;
+    }
+
+    mechlist = g_malloc0(len + 1);
+    spice_channel_read(channel, mechlist, len);
+    mechlist[len] = '\0';
+    if (c->has_error) {
+        goto error;
+    }
+
+restart:
+    /* Start the auth negotiation on the client end first */
+    CHANNEL_DEBUG(channel, "Client start negotiation mechlist '%s'", mechlist);
+    err = sasl_client_start(saslconn,
+                            mechlist,
+                            &interact,
+                            &clientout,
+                            &clientoutlen,
+                            &mechname);
+    if (err != SASL_OK && err != SASL_CONTINUE && err != SASL_INTERACT) {
+        g_critical("Failed to start SASL negotiation: %d (%s)",
+                   err, sasl_errdetail(saslconn));
+        goto error;
+    }
+
+    /* Need to gather some credentials from the client */
+    if (err == SASL_INTERACT) {
+        if (!spice_channel_gather_sasl_credentials(channel, interact)) {
+            CHANNEL_DEBUG(channel, "Failed to collect auth credentials");
+            goto error;
+        }
+        goto restart;
+    }
+
+    CHANNEL_DEBUG(channel, "Server start negotiation with mech %s. Data %d bytes %p '%s'",
+                  mechname, clientoutlen, clientout, clientout);
+
+    if (clientoutlen > SASL_MAX_DATA_LEN) {
+        g_critical("SASL negotiation data too long: %d bytes",
+                   clientoutlen);
+        goto error;
+    }
+
+    /* Send back the chosen mechname */
+    len = strlen(mechname);
+    spice_channel_write(channel, &len, sizeof(guint32));
+    spice_channel_write(channel, mechname, len);
+
+    /* NB, distinction of NULL vs "" is *critical* in SASL */
+    if (clientout) {
+        len = clientoutlen + 1;
+        spice_channel_write(channel, &len, sizeof(guint32));
+        spice_channel_write(channel, clientout, len);
+    } else {
+        len = 0;
+        spice_channel_write(channel, &len, sizeof(guint32));
+    }
+
+    if (c->has_error)
+        goto error;
+
+    CHANNEL_DEBUG(channel, "Getting sever start negotiation reply");
+    /* Read the 'START' message reply from server */
+    spice_channel_read(channel, &len, sizeof(len));
+    if (c->has_error)
+        goto error;
+    if (len > SASL_MAX_DATA_LEN) {
+        g_critical("SASL negotiation data too long: %d bytes",
+                   len);
+        goto error;
+    }
+
+    /* NB, distinction of NULL vs "" is *critical* in SASL */
+    if (len > 0) {
+        serverin = g_malloc0(len);
+        spice_channel_read(channel, serverin, len);
+        serverin[len - 1] = '\0';
+        len--;
+    } else {
+        serverin = NULL;
+    }
+    spice_channel_read(channel, &complete, sizeof(guint8));
+    if (c->has_error)
+        goto error;
+
+    CHANNEL_DEBUG(channel, "Client start result complete: %d. Data %d bytes %p '%s'",
+                complete, len, serverin, serverin);
+
+    /* Loop-the-loop...
+     * Even if the server has completed, the client must *always* do at least one step
+     * in this loop to verify the server isn't lying about something. Mutual auth */
+    for (;;) {
+       if (complete && err == SASL_OK)
+            break;
+
+    restep:
+        err = sasl_client_step(saslconn,
+                               serverin,
+                               len,
+                               &interact,
+                               &clientout,
+                               &clientoutlen);
+        if (err != SASL_OK && err != SASL_CONTINUE && err != SASL_INTERACT) {
+            g_critical("Failed SASL step: %d (%s)",
+                       err, sasl_errdetail(saslconn));
+            goto error;
+        }
+
+        /* Need to gather some credentials from the client */
+        if (err == SASL_INTERACT) {
+            if (!spice_channel_gather_sasl_credentials(channel,
+                                                       interact)) {
+                CHANNEL_DEBUG(channel, "%s", "Failed to collect auth credentials");
+                goto error;
+            }
+            goto restep;
+        }
+
+        if (serverin) {
+            g_free(serverin);
+            serverin = NULL;
+        }
+
+        CHANNEL_DEBUG(channel, "Client step result %d. Data %d bytes %p '%s'", err, clientoutlen, clientout, clientout);
+
+        /* Previous server call showed completion & we're now locally complete too */
+        if (complete && err == SASL_OK)
+            break;
+
+        /* Not done, prepare to talk with the server for another iteration */
+
+        /* NB, distinction of NULL vs "" is *critical* in SASL */
+        if (clientout) {
+            len = clientoutlen + 1;
+            spice_channel_write(channel, &len, sizeof(guint32));
+            spice_channel_write(channel, clientout, len);
+        } else {
+            len = 0;
+            spice_channel_write(channel, &len, sizeof(guint32));
+        }
+
+        if (c->has_error)
+            goto error;
+
+        CHANNEL_DEBUG(channel, "Server step with %d bytes %p", clientoutlen, clientout);
+
+        spice_channel_read(channel, &len, sizeof(guint32));
+        if (c->has_error)
+            goto error;
+        if (len > SASL_MAX_DATA_LEN) {
+            g_critical("SASL negotiation data too long: %d bytes", len);
+            goto error;
+        }
+
+        /* NB, distinction of NULL vs "" is *critical* in SASL */
+        if (len) {
+            serverin = g_malloc0(len);
+            spice_channel_read(channel, serverin, len);
+            serverin[len - 1] = '\0';
+            len--;
+        } else {
+            serverin = NULL;
+        }
+
+        spice_channel_read(channel, &complete, sizeof(guint8));
+        if (c->has_error)
+            goto error;
+
+        CHANNEL_DEBUG(channel, "Client step result complete: %d. Data %d bytes %p '%s'",
+                    complete, len, serverin, serverin);
+
+        /* This server call shows complete, and earlier client step was OK */
+        if (complete) {
+            g_free(serverin);
+            serverin = NULL;
+            if (err == SASL_CONTINUE) /* something went wrong */
+                goto complete;
+            break;
+        }
+    }
+
+    /* Check for suitable SSF if non-TLS */
+    if (!c->ssl) {
+        err = sasl_getprop(saslconn, SASL_SSF, &val);
+        if (err != SASL_OK) {
+            g_critical("cannot query SASL ssf on connection %d (%s)",
+                       err, sasl_errstring(err, NULL, NULL));
+            goto error;
+        }
+        ssf = *(const int *)val;
+        CHANNEL_DEBUG(channel, "SASL SSF value %d", ssf);
+        if (ssf < 56) { /* 56 == DES level, good for Kerberos */
+            g_critical("negotiation SSF %d was not strong enough", ssf);
+            goto error;
+        }
+    }
+
+complete:
+    CHANNEL_DEBUG(channel, "%s", "SASL authentication complete");
+    spice_channel_read(channel, &len, sizeof(len));
+    if (len == SPICE_LINK_ERR_OK) {
+        ret = TRUE;
+        /* This must come *after* check-auth-result, because the former
+         * is defined to be sent unencrypted, and setting saslconn turns
+         * on the SSF layer encryption processing */
+        c->sasl_conn = saslconn;
+        goto cleanup;
+    }
+
+error:
+    if (saslconn)
+        sasl_dispose(&saslconn);
+
+    spice_channel_failed_authentication(channel);
+    ret = FALSE;
+
+cleanup:
+    g_free(localAddr);
+    g_free(remoteAddr);
+    g_free(mechlist);
+    g_free(serverin);
+    g_clear_object(&addr);
+    return ret;
+}
+#endif /* HAVE_SASL */
+
+/* coroutine context */
+static gboolean spice_channel_recv_link_msg(SpiceChannel *channel)
+{
+    SpiceChannelPrivate *c;
+    int rc, num_caps, i;
+    uint32_t *caps;
+
+    g_return_val_if_fail(channel != NULL, FALSE);
+    g_return_val_if_fail(channel->priv != NULL, FALSE);
+
+    c = channel->priv;
+
+    rc = spice_channel_read(channel, (uint8_t*)c->peer_msg + c->peer_pos,
+                            c->peer_hdr.size - c->peer_pos);
+    c->peer_pos += rc;
+    if (c->peer_pos != c->peer_hdr.size) {
+        g_critical("%s: %s: incomplete link reply (%d/%d)",
+                  c->name, __FUNCTION__, rc, c->peer_hdr.size);
+        goto error;
+    }
+    switch (c->peer_msg->error) {
+    case SPICE_LINK_ERR_OK:
+        /* nothing */
+        break;
+    case SPICE_LINK_ERR_NEED_SECURED:
+        c->state = SPICE_CHANNEL_STATE_RECONNECTING;
+        CHANNEL_DEBUG(channel, "switching to tls");
+        c->tls = TRUE;
+        return FALSE;
+    default:
+        g_warning("%s: %s: unhandled error %d",
+                c->name, __FUNCTION__, c->peer_msg->error);
+        goto error;
+    }
+
+    num_caps = c->peer_msg->num_channel_caps + c->peer_msg->num_common_caps;
+    CHANNEL_DEBUG(channel, "%s: %d caps", __FUNCTION__, num_caps);
+
+    /* see original spice/client code: */
+    /* g_return_if_fail(c->peer_msg + c->peer_msg->caps_offset * sizeof(uint32_t) > c->peer_msg + c->peer_hdr.size); */
+
+    caps = (uint32_t *)((uint8_t *)c->peer_msg + c->peer_msg->caps_offset);
+
+    g_array_set_size(c->remote_common_caps, c->peer_msg->num_common_caps);
+    for (i = 0; i < c->peer_msg->num_common_caps; i++, caps++) {
+        g_array_index(c->remote_common_caps, uint32_t, i) = *caps;
+        CHANNEL_DEBUG(channel, "got common caps %u:0x%X", i, *caps);
+    }
+
+    g_array_set_size(c->remote_caps, c->peer_msg->num_channel_caps);
+    for (i = 0; i < c->peer_msg->num_channel_caps; i++, caps++) {
+        g_array_index(c->remote_caps, uint32_t, i) = *caps;
+        CHANNEL_DEBUG(channel, "got channel caps %u:0x%X", i, *caps);
+    }
+
+    if (!spice_channel_test_common_capability(channel,
+            SPICE_COMMON_CAP_PROTOCOL_AUTH_SELECTION)) {
+        CHANNEL_DEBUG(channel, "Server supports spice ticket auth only");
+        spice_channel_send_spice_ticket(channel);
+    } else {
+        SpiceLinkAuthMechanism auth = { 0, };
+
+#if HAVE_SASL
+        if (spice_channel_test_common_capability(channel, SPICE_COMMON_CAP_AUTH_SASL)) {
+            CHANNEL_DEBUG(channel, "Choosing SASL mechanism");
+            auth.auth_mechanism = SPICE_COMMON_CAP_AUTH_SASL;
+            spice_channel_write(channel, &auth, sizeof(auth));
+            if (!spice_channel_perform_auth_sasl(channel))
+                return FALSE;
+        } else
+#endif
+        if (spice_channel_test_common_capability(channel, SPICE_COMMON_CAP_AUTH_SPICE)) {
+            auth.auth_mechanism = SPICE_COMMON_CAP_AUTH_SPICE;
+            spice_channel_write(channel, &auth, sizeof(auth));
+            spice_channel_send_spice_ticket(channel);
+        } else {
+            g_warning("No compatible AUTH mechanism");
+            goto error;
+        }
+    }
+    c->use_mini_header = spice_channel_test_common_capability(channel,
+                                                              SPICE_COMMON_CAP_MINI_HEADER);
+    CHANNEL_DEBUG(channel, "use mini header: %d", c->use_mini_header);
+    return TRUE;
+
+error:
+    c->has_error = TRUE;
+    c->event = SPICE_CHANNEL_ERROR_LINK;
+    return FALSE;
+}
+
+/* system context */
+G_GNUC_INTERNAL
+void spice_channel_wakeup(SpiceChannel *channel, gboolean cancel)
+{
+    GCoroutine *c = &channel->priv->coroutine;
+
+    if (cancel)
+        g_coroutine_condition_cancel(c);
+
+    g_coroutine_wakeup(c);
+}
+
+G_GNUC_INTERNAL
+gboolean spice_channel_get_read_only(SpiceChannel *channel)
+{
+    return spice_session_get_read_only(channel->priv->session);
+}
+
+/* coroutine context */
+G_GNUC_INTERNAL
+void spice_channel_recv_msg(SpiceChannel *channel,
+                            handler_msg_in msg_handler, gpointer data)
+{
+    SpiceChannelPrivate *c = channel->priv;
+    SpiceMsgIn *in;
+    int msg_size;
+    int msg_type;
+    int sub_list_offset = 0;
+
+    in = spice_msg_in_new(channel);
+
+    /* receive message */
+    spice_channel_read(channel, in->header,
+                       spice_header_get_header_size(c->use_mini_header));
+    if (c->has_error)
+        goto end;
+
+    msg_size = spice_header_get_msg_size(in->header, c->use_mini_header);
+    /* FIXME: do not allow others to take ref on in, and use realloc here?
+     * this would avoid malloc/free on each message?
+     */
+    in->data = g_malloc0(msg_size);
+    spice_channel_read(channel, in->data, msg_size);
+    if (c->has_error)
+        goto end;
+    in->dpos = msg_size;
+
+    msg_type = spice_header_get_msg_type(in->header, c->use_mini_header);
+    sub_list_offset = spice_header_get_msg_sub_list(in->header, c->use_mini_header);
+
+    if (msg_type == SPICE_MSG_LIST || sub_list_offset) {
+        SpiceSubMessageList *sub_list;
+        SpiceSubMessage *sub;
+        SpiceMsgIn *sub_in;
+        int i;
+
+        sub_list = (SpiceSubMessageList *)(in->data + sub_list_offset);
+        for (i = 0; i < sub_list->size; i++) {
+            sub = (SpiceSubMessage *)(in->data + sub_list->sub_messages[i]);
+            sub_in = spice_msg_in_sub_new(channel, in, sub);
+            sub_in->parsed = c->parser(sub_in->data, sub_in->data + sub_in->dpos,
+                                       spice_header_get_msg_type(sub_in->header,
+                                                                 c->use_mini_header),
+                                       c->peer_hdr.minor_version,
+                                       &sub_in->psize, &sub_in->pfree);
+            if (sub_in->parsed == NULL) {
+                g_critical("failed to parse sub-message: %s type %d",
+                           c->name, spice_header_get_msg_type(sub_in->header, c->use_mini_header));
+                goto end;
+            }
+            msg_handler(channel, sub_in, data);
+            spice_msg_in_unref(sub_in);
+        }
+    }
+
+    /* ack message */
+    if (c->message_ack_count) {
+        c->message_ack_count--;
+        if (!c->message_ack_count) {
+            SpiceMsgOut *out = spice_msg_out_new(channel, SPICE_MSGC_ACK);
+            spice_msg_out_send_internal(out);
+            c->message_ack_count = c->message_ack_window;
+        }
+    }
+
+    if (msg_type == SPICE_MSG_LIST) {
+        goto end;
+    }
+
+    /* parse message */
+    in->parsed = c->parser(in->data, in->data + msg_size, msg_type,
+                           c->peer_hdr.minor_version, &in->psize, &in->pfree);
+    if (in->parsed == NULL) {
+        g_critical("failed to parse message: %s type %d",
+                   c->name, msg_type);
+        goto end;
+    }
+
+    /* process message */
+    /* spice_msg_in_hexdump(in); */
+    msg_handler(channel, in, data);
+
+end:
+    /* If the server uses full header, the serial is not necessarily equal
+     * to c->in_serial (the server can sometimes skip serials) */
+    c->last_message_serial = spice_header_get_in_msg_serial(in);
+    c->in_serial++;
+    spice_msg_in_unref(in);
+}
+
+static const char *to_string[] = {
+    NULL,
+    [ SPICE_CHANNEL_MAIN ] = "main",
+    [ SPICE_CHANNEL_DISPLAY ] = "display",
+    [ SPICE_CHANNEL_INPUTS ] = "inputs",
+    [ SPICE_CHANNEL_CURSOR ] = "cursor",
+    [ SPICE_CHANNEL_PLAYBACK ] = "playback",
+    [ SPICE_CHANNEL_RECORD ] = "record",
+    [ SPICE_CHANNEL_TUNNEL ] = "tunnel",
+    [ SPICE_CHANNEL_SMARTCARD ] = "smartcard",
+    [ SPICE_CHANNEL_USBREDIR ] = "usbredir",
+    [ SPICE_CHANNEL_PORT ] = "port",
+    [ SPICE_CHANNEL_WEBDAV ] = "webdav",
+};
+
+/**
+ * spice_channel_type_to_string:
+ * @type: a channel-type property value
+ *
+ * Convert a channel-type property value to a string.
+ *
+ * Returns: string representation of @type.
+ * Since: 0.20
+ **/
+const gchar* spice_channel_type_to_string(gint type)
+{
+    const char *str = NULL;
+
+    if (type >= 0 && type < G_N_ELEMENTS(to_string)) {
+        str = to_string[type];
+    }
+
+    return str ? str : "unknown channel type";
+}
+
+/**
+ * spice_channel_string_to_type:
+ * @str: a string representation of the channel-type property
+ *
+ * Convert a channel-type property value to a string.
+ *
+ * Returns: the channel-type property value for a @str channel
+ * Since: 0.21
+ **/
+gint spice_channel_string_to_type(const gchar *str)
+{
+    int i;
+
+    g_return_val_if_fail(str != NULL, -1);
+
+    for (i = 0; i < G_N_ELEMENTS(to_string); i++)
+        if (g_strcmp0(str, to_string[i]) == 0)
+            return i;
+
+    return -1;
+}
+
+G_GNUC_INTERNAL
+gchar *spice_channel_supported_string(void)
+{
+    return g_strjoin(", ",
+                     spice_channel_type_to_string(SPICE_CHANNEL_MAIN),
+                     spice_channel_type_to_string(SPICE_CHANNEL_DISPLAY),
+                     spice_channel_type_to_string(SPICE_CHANNEL_INPUTS),
+                     spice_channel_type_to_string(SPICE_CHANNEL_CURSOR),
+                     spice_channel_type_to_string(SPICE_CHANNEL_PLAYBACK),
+                     spice_channel_type_to_string(SPICE_CHANNEL_RECORD),
+#ifdef USE_SMARTCARD
+                     spice_channel_type_to_string(SPICE_CHANNEL_SMARTCARD),
+#endif
+#ifdef USE_USBREDIR
+                     spice_channel_type_to_string(SPICE_CHANNEL_USBREDIR),
+#endif
+#ifdef USE_PHODAV
+                     spice_channel_type_to_string(SPICE_CHANNEL_WEBDAV),
+#endif
+                     NULL);
+}
+
+
+/**
+ * spice_channel_new:
+ * @s: the @SpiceSession the channel is linked to
+ * @type: the requested SPICECHANNELPRIVATE type
+ * @id: the channel-id
+ *
+ * Create a new #SpiceChannel of type @type, and channel ID @id.
+ *
+ * Returns: a weak reference to #SpiceChannel, the session owns the reference
+ **/
+SpiceChannel *spice_channel_new(SpiceSession *s, int type, int id)
+{
+    SpiceChannel *channel;
+    GType gtype = 0;
+
+    g_return_val_if_fail(s != NULL, NULL);
+
+    switch (type) {
+    case SPICE_CHANNEL_MAIN:
+        gtype = SPICE_TYPE_MAIN_CHANNEL;
+        break;
+    case SPICE_CHANNEL_DISPLAY:
+        gtype = SPICE_TYPE_DISPLAY_CHANNEL;
+        break;
+    case SPICE_CHANNEL_CURSOR:
+        gtype = SPICE_TYPE_CURSOR_CHANNEL;
+        break;
+    case SPICE_CHANNEL_INPUTS:
+        gtype = SPICE_TYPE_INPUTS_CHANNEL;
+        break;
+    case SPICE_CHANNEL_PLAYBACK:
+    case SPICE_CHANNEL_RECORD: {
+        if (!spice_session_get_audio_enabled(s)) {
+            g_debug("audio channel is disabled, not creating it");
+            return NULL;
+        }
+        gtype = type == SPICE_CHANNEL_RECORD ?
+            SPICE_TYPE_RECORD_CHANNEL : SPICE_TYPE_PLAYBACK_CHANNEL;
+        break;
+    }
+#ifdef USE_SMARTCARD
+    case SPICE_CHANNEL_SMARTCARD: {
+        if (!spice_session_get_smartcard_enabled(s)) {
+            g_debug("smartcard channel is disabled, not creating it");
+            return NULL;
+        }
+        gtype = SPICE_TYPE_SMARTCARD_CHANNEL;
+        break;
+    }
+#endif
+#ifdef USE_USBREDIR
+    case SPICE_CHANNEL_USBREDIR: {
+        if (!spice_session_get_usbredir_enabled(s)) {
+            g_debug("usbredir channel is disabled, not creating it");
+            return NULL;
+        }
+        gtype = SPICE_TYPE_USBREDIR_CHANNEL;
+        break;
+    }
+#endif
+#ifdef USE_PHODAV
+    case SPICE_CHANNEL_WEBDAV: {
+        gtype = SPICE_TYPE_WEBDAV_CHANNEL;
+        break;
+    }
+#endif
+    case SPICE_CHANNEL_PORT:
+        gtype = SPICE_TYPE_PORT_CHANNEL;
+        break;
+    default:
+        g_debug("unsupported channel kind: %s: %d",
+                spice_channel_type_to_string(type), type);
+        return NULL;
+    }
+    channel = SPICE_CHANNEL(g_object_new(gtype,
+                                         "spice-session", s,
+                                         "channel-type", type,
+                                         "channel-id", id,
+                                         NULL));
+    return channel;
+}
+
+/**
+ * spice_channel_destroy:
+ * @channel:
+ *
+ * Disconnect and unref the @channel.
+ *
+ * Deprecated: 0.27: this function has been deprecated because it is
+ * misleading, the object is not actually destroyed. Instead, it is
+ * recommended to call explicitely spice_channel_disconnect() and
+ * g_object_unref().
+ **/
+void spice_channel_destroy(SpiceChannel *channel)
+{
+    g_return_if_fail(channel != NULL);
+
+    CHANNEL_DEBUG(channel, "channel destroy");
+    spice_channel_disconnect(channel, SPICE_CHANNEL_NONE);
+    g_object_unref(channel);
+}
+
+/* any context */
+static void spice_channel_flushed(SpiceChannel *channel, gboolean success)
+{
+    SpiceChannelPrivate *c = channel->priv;
+    GSList *l;
+
+    for (l = c->flushing; l != NULL; l = l->next) {
+        GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT(l->data);
+        g_simple_async_result_set_op_res_gboolean(result, success);
+        g_simple_async_result_complete_in_idle(result);
+    }
+
+    g_slist_free_full(c->flushing, g_object_unref);
+    c->flushing = NULL;
+}
+
+/* coroutine context */
+static void spice_channel_iterate_write(SpiceChannel *channel)
+{
+    SpiceChannelPrivate *c = channel->priv;
+    SpiceMsgOut *out;
+
+    do {
+        STATIC_MUTEX_LOCK(c->xmit_queue_lock);
+        out = g_queue_pop_head(&c->xmit_queue);
+        STATIC_MUTEX_UNLOCK(c->xmit_queue_lock);
+        if (out)
+            spice_channel_write_msg(channel, out);
+    } while (out);
+
+    spice_channel_flushed(channel, TRUE);
+}
+
+/* coroutine context */
+static void spice_channel_iterate_read(SpiceChannel *channel)
+{
+    SpiceChannelPrivate *c = channel->priv;
+
+    g_coroutine_socket_wait(&c->coroutine, c->sock, G_IO_IN);
+
+    /* treat all incoming data (block on message completion) */
+    while (!c->has_error &&
+           c->state != SPICE_CHANNEL_STATE_MIGRATING &&
+           g_pollable_input_stream_is_readable(G_POLLABLE_INPUT_STREAM(c->in))
+    ) { do
+            spice_channel_recv_msg(channel,
+                                   (handler_msg_in)SPICE_CHANNEL_GET_CLASS(channel)->handle_msg, NULL);
+#if HAVE_SASL
+            /* flush the sasl buffer too */
+        while (c->sasl_decoded != NULL);
+#else
+        while (FALSE);
+#endif
+    }
+
+}
+
+static gboolean wait_migration(gpointer data)
+{
+    SpiceChannel *channel = SPICE_CHANNEL(data);
+    SpiceChannelPrivate *c = channel->priv;
+
+    if (c->state != SPICE_CHANNEL_STATE_MIGRATING) {
+        CHANNEL_DEBUG(channel, "unfreeze channel");
+        return TRUE;
+    }
+
+    return FALSE;
+}
+
+/* coroutine context */
+static gboolean spice_channel_iterate(SpiceChannel *channel)
+{
+    SpiceChannelPrivate *c = channel->priv;
+
+    if (c->state == SPICE_CHANNEL_STATE_MIGRATING &&
+        !g_coroutine_condition_wait(&c->coroutine, wait_migration, channel))
+        CHANNEL_DEBUG(channel, "migration wait cancelled");
+
+    /* flush any pending write and read */
+    if (!c->has_error)
+        SPICE_CHANNEL_GET_CLASS(channel)->iterate_write(channel);
+    if (!c->has_error)
+        SPICE_CHANNEL_GET_CLASS(channel)->iterate_read(channel);
+
+    if (c->has_error) {
+        GIOCondition ret;
+
+        if (!c->sock)
+            return FALSE;
+
+        /* We don't want to report an error if the socket was closed gracefully
+         * on the other end (VM shutdown) */
+        ret = g_socket_condition_check(c->sock, G_IO_IN | G_IO_ERR);
+
+        if (ret & G_IO_ERR) {
+            CHANNEL_DEBUG(channel, "channel got error");
+
+            if (c->state > SPICE_CHANNEL_STATE_CONNECTING) {
+                if (c->state == SPICE_CHANNEL_STATE_READY)
+                    c->event = SPICE_CHANNEL_ERROR_IO;
+                else
+                    c->event = SPICE_CHANNEL_ERROR_LINK;
+            }
+        }
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+/* we use an idle function to allow the coroutine to exit before we actually
+ * unref the object since the coroutine's state is part of the object */
+static gboolean spice_channel_delayed_unref(gpointer data)
+{
+    SpiceChannel *channel = SPICE_CHANNEL(data);
+    SpiceChannelPrivate *c = channel->priv;
+    gboolean was_ready = c->state == SPICE_CHANNEL_STATE_READY;
+
+    CHANNEL_DEBUG(channel, "Delayed unref channel %p", channel);
+
+    g_return_val_if_fail(c->coroutine.coroutine.exited == TRUE, FALSE);
+
+    c->state = SPICE_CHANNEL_STATE_UNCONNECTED;
+
+    if (c->event != SPICE_CHANNEL_NONE) {
+        g_coroutine_signal_emit(channel, signals[SPICE_CHANNEL_EVENT], 0, c->event);
+        c->event = SPICE_CHANNEL_NONE;
+        g_clear_error(&c->error);
+    }
+
+    if (was_ready)
+        g_coroutine_signal_emit(channel, signals[SPICE_CHANNEL_EVENT], 0, SPICE_CHANNEL_CLOSED);
+
+    g_object_unref(G_OBJECT(data));
+
+    return FALSE;
+}
+
+static X509_LOOKUP_METHOD spice_x509_mem_lookup = {
+    "spice_x509_mem_lookup",
+    0
+};
+
+static int spice_channel_load_ca(SpiceChannel *channel)
+{
+    SpiceChannelPrivate *c = channel->priv;
+    STACK_OF(X509_INFO) *inf;
+    X509_INFO *itmp;
+    X509_LOOKUP *lookup;
+    BIO *in;
+    int i, count = 0;
+    guint8 *ca;
+    guint size;
+    const gchar *ca_file;
+    int rc;
+
+    g_return_val_if_fail(c->ctx != NULL, 0);
+
+    lookup = X509_STORE_add_lookup(c->ctx->cert_store, &spice_x509_mem_lookup);
+    ca_file = spice_session_get_ca_file(c->session);
+    spice_session_get_ca(c->session, &ca, &size);
+
+    CHANNEL_DEBUG(channel, "Load CA, file: %s, data: %p", ca_file, ca);
+    g_warn_if_fail(ca_file || ca);
+
+    if (ca != NULL) {
+        in = BIO_new_mem_buf(ca, size);
+        inf = PEM_X509_INFO_read_bio(in, NULL, NULL, NULL);
+        BIO_free(in);
+
+        for (i = 0; i < sk_X509_INFO_num(inf); i++) {
+            itmp = sk_X509_INFO_value(inf, i);
+            if (itmp->x509) {
+                X509_STORE_add_cert(lookup->store_ctx, itmp->x509);
+                count++;
+            }
+            if (itmp->crl) {
+                X509_STORE_add_crl(lookup->store_ctx, itmp->crl);
+                count++;
+            }
+        }
+
+        sk_X509_INFO_pop_free(inf, X509_INFO_free);
+    }
+
+    if (ca_file != NULL) {
+        rc = SSL_CTX_load_verify_locations(c->ctx, ca_file, NULL);
+        if (rc != 1)
+            g_warning("loading ca certs from %s failed", ca_file);
+        else
+            count++;
+    }
+
+    if (count == 0) {
+        rc = SSL_CTX_set_default_verify_paths(c->ctx);
+        if (rc != 1)
+            g_warning("loading ca certs from default location failed");
+        else
+            count++;
+    }
+
+    return count;
+}
+
+/**
+ * spice_channel_get_error:
+ * @channel:
+ *
+ * Retrieves the #GError currently set on channel, if the #SpiceChannel
+ * is in error state and can provide additional error details.
+ *
+ * Returns: the pointer to the error, or %NULL
+ * Since: 0.24
+ **/
+const GError* spice_channel_get_error(SpiceChannel *self)
+{
+    SpiceChannelPrivate *c;
+
+    g_return_val_if_fail(SPICE_IS_CHANNEL(self), NULL);
+    c = self->priv;
+
+    return c->error;
+}
+
+/* coroutine context */
+static void *spice_channel_coroutine(void *data)
+{
+    SpiceChannel *channel = SPICE_CHANNEL(data);
+    SpiceChannelPrivate *c = channel->priv;
+    guint verify;
+    int rc, delay_val = 1;
+    /* When some other SSL/TLS version becomes obsolete, add it to this
+     * variable. */
+    long ssl_options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
+
+    CHANNEL_DEBUG(channel, "Started background coroutine %p", &c->coroutine);
+
+    if (spice_session_get_client_provided_socket(c->session)) {
+        if (c->fd < 0) {
+            g_critical("fd not provided!");
+            c->event = SPICE_CHANNEL_ERROR_CONNECT;
+            goto cleanup;
+        }
+
+        if (!(c->sock = g_socket_new_from_fd(c->fd, NULL))) {
+                CHANNEL_DEBUG(channel, "Failed to open socket from fd %d", c->fd);
+                c->event = SPICE_CHANNEL_ERROR_CONNECT;
+                goto cleanup;
+        }
+
+        g_socket_set_blocking(c->sock, FALSE);
+        g_socket_set_keepalive(c->sock, TRUE);
+        c->conn = g_socket_connection_factory_create_connection(c->sock);
+        goto connected;
+    }
+
+
+reconnect:
+    c->conn = spice_session_channel_open_host(c->session, channel, &c->tls, &c->error);
+    if (c->conn == NULL) {
+        if (!c->error && !c->tls) {
+            CHANNEL_DEBUG(channel, "trying with TLS port");
+            c->tls = true; /* FIXME: does that really work with provided fd */
+            goto reconnect;
+        } else {
+            CHANNEL_DEBUG(channel, "Connect error");
+            c->event = SPICE_CHANNEL_ERROR_CONNECT;
+            goto cleanup;
+        }
+    }
+    c->sock = g_object_ref(g_socket_connection_get_socket(c->conn));
+
+    if (c->tls) {
+        c->ctx = SSL_CTX_new(SSLv23_method());
+        if (c->ctx == NULL) {
+            g_critical("SSL_CTX_new failed");
+            c->event = SPICE_CHANNEL_ERROR_TLS;
+            goto cleanup;
+        }
+
+        SSL_CTX_set_options(c->ctx, ssl_options);
+
+        verify = spice_session_get_verify(c->session);
+        if (verify &
+            (SPICE_SESSION_VERIFY_SUBJECT | SPICE_SESSION_VERIFY_HOSTNAME)) {
+            rc = spice_channel_load_ca(channel);
+            if (rc == 0) {
+                g_warning("no cert loaded");
+                if (verify & SPICE_SESSION_VERIFY_PUBKEY) {
+                    g_warning("only pubkey active");
+                    verify = SPICE_SESSION_VERIFY_PUBKEY;
+                } else {
+                    c->event = SPICE_CHANNEL_ERROR_TLS;
+                    goto cleanup;
+                }
+            }
+        }
+
+        {
+            const gchar *ciphers = spice_session_get_ciphers(c->session);
+            if (ciphers != NULL) {
+                rc = SSL_CTX_set_cipher_list(c->ctx, ciphers);
+                if (rc != 1)
+                    g_warning("loading cipher list %s failed", ciphers);
+            }
+        }
+
+        c->ssl = SSL_new(c->ctx);
+        if (c->ssl == NULL) {
+            g_critical("SSL_new failed");
+            c->event = SPICE_CHANNEL_ERROR_TLS;
+            goto cleanup;
+        }
+
+
+        BIO *bio = bio_new_giostream(G_IO_STREAM(c->conn));
+        SSL_set_bio(c->ssl, bio, bio);
+
+        {
+            guint8 *pubkey;
+            guint pubkey_len;
+
+            spice_session_get_pubkey(c->session, &pubkey, &pubkey_len);
+            c->sslverify = spice_openssl_verify_new(c->ssl, verify,
+                spice_session_get_host(c->session),
+                (char*)pubkey, pubkey_len,
+                spice_session_get_cert_subject(c->session));
+        }
+
+ssl_reconnect:
+        rc = SSL_connect(c->ssl);
+        if (rc <= 0) {
+            rc = SSL_get_error(c->ssl, rc);
+            if (rc == SSL_ERROR_WANT_READ || rc == SSL_ERROR_WANT_WRITE) {
+                g_coroutine_socket_wait(&c->coroutine, c->sock, G_IO_OUT|G_IO_ERR|G_IO_HUP);
+                goto ssl_reconnect;
+            } else {
+                g_warning("%s: SSL_connect: %s",
+                          c->name, ERR_error_string(rc, NULL));
+                c->event = SPICE_CHANNEL_ERROR_TLS;
+                goto cleanup;
+            }
+        }
+    }
+
+connected:
+    c->has_error = FALSE;
+    c->in = g_io_stream_get_input_stream(G_IO_STREAM(c->conn));
+    c->out = g_io_stream_get_output_stream(G_IO_STREAM(c->conn));
+
+    rc = setsockopt(g_socket_get_fd(c->sock), IPPROTO_TCP, TCP_NODELAY,
+                    (const char*)&delay_val, sizeof(delay_val));
+    if ((rc != 0)
+#ifdef ENOTSUP
+        && (errno != ENOTSUP)
+#endif
+        ) {
+        g_warning("%s: could not set sockopt TCP_NODELAY: %s", c->name,
+                  strerror(errno));
+    }
+
+    spice_channel_send_link(channel);
+    if (!spice_channel_recv_link_hdr(channel) ||
+        !spice_channel_recv_link_msg(channel) ||
+        !spice_channel_recv_auth(channel))
+        goto cleanup;
+
+    while (spice_channel_iterate(channel))
+        ;
+
+cleanup:
+    CHANNEL_DEBUG(channel, "Coroutine exit %s", c->name);
+
+    spice_channel_reset(channel, FALSE);
+
+    if (c->state == SPICE_CHANNEL_STATE_RECONNECTING ||
+        c->state == SPICE_CHANNEL_STATE_SWITCHING) {
+        g_warn_if_fail(c->event == SPICE_CHANNEL_NONE);
+        channel_connect(channel, c->tls);
+        g_object_unref(channel);
+    } else
+        g_idle_add(spice_channel_delayed_unref, data);
+
+    /* Co-routine exits now - the SpiceChannel object may no longer exist,
+       so don't do anything else now unless you like SEGVs */
+    return NULL;
+}
+
+static gboolean connect_delayed(gpointer data)
+{
+    SpiceChannel *channel = data;
+    SpiceChannelPrivate *c = channel->priv;
+    struct coroutine *co;
+
+    CHANNEL_DEBUG(channel, "Open coroutine starting %p", channel);
+    c->connect_delayed_id = 0;
+
+    co = &c->coroutine.coroutine;
+
+    co->stack_size = 16 << 20; /* 16Mb */
+    co->entry = spice_channel_coroutine;
+    co->release = NULL;
+
+    coroutine_init(co);
+    coroutine_yieldto(co, channel);
+
+    return FALSE;
+}
+
+/* any context */
+static gboolean channel_connect(SpiceChannel *channel, gboolean tls)
+{
+    SpiceChannelPrivate *c = channel->priv;
+
+    g_return_val_if_fail(c != NULL, FALSE);
+
+    if (c->session == NULL || c->channel_type == -1 || c->channel_id == -1) {
+        /* unset properties or unknown channel type */
+        g_warning("%s: channel setup incomplete", __FUNCTION__);
+        return false;
+    }
+
+    c->state = SPICE_CHANNEL_STATE_CONNECTING;
+    c->tls = tls;
+
+    if (spice_session_get_client_provided_socket(c->session)) {
+        if (c->fd == -1) {
+            CHANNEL_DEBUG(channel, "requesting fd");
+            /* FIXME: no way for client to provide fd atm. */
+            /* It could either chain on parent channel.. */
+            /* or register migration channel on parent session, or ? */
+            g_signal_emit(channel, signals[SPICE_CHANNEL_OPEN_FD], 0, c->tls);
+            return true;
+        }
+    }
+
+    c->xmit_queue_blocked = FALSE;
+
+    g_return_val_if_fail(c->sock == NULL, FALSE);
+    g_object_ref(G_OBJECT(channel)); /* Unref'd when co-routine exits */
+
+    /* we connect in idle, to let previous coroutine exit, if present */
+    c->connect_delayed_id = g_idle_add(connect_delayed, channel);
+
+    return true;
+}
+
+/**
+ * spice_channel_connect:
+ * @channel:
+ *
+ * Connect the channel, using #SpiceSession connection informations
+ *
+ * Returns: %TRUE on success.
+ **/
+gboolean spice_channel_connect(SpiceChannel *channel)
+{
+    g_return_val_if_fail(SPICE_IS_CHANNEL(channel), FALSE);
+    SpiceChannelPrivate *c = channel->priv;
+
+    if (c->state >= SPICE_CHANNEL_STATE_CONNECTING)
+        return TRUE;
+
+    g_return_val_if_fail(channel->priv->fd == -1, FALSE);
+
+    return channel_connect(channel, FALSE);
+}
+
+/**
+ * spice_channel_open_fd:
+ * @channel:
+ * @fd: a file descriptor (socket) or -1.
+ * request mechanism
+ *
+ * Connect the channel using @fd socket.
+ *
+ * If @fd is -1, a valid fd will be requested later via the
+ * SpiceChannel::open-fd signal.
+ *
+ * Returns: %TRUE on success.
+ **/
+gboolean spice_channel_open_fd(SpiceChannel *channel, int fd)
+{
+    SpiceChannelPrivate *c;
+
+    g_return_val_if_fail(SPICE_IS_CHANNEL(channel), FALSE);
+    g_return_val_if_fail(channel->priv != NULL, FALSE);
+    g_return_val_if_fail(channel->priv->fd == -1, FALSE);
+    g_return_val_if_fail(fd >= -1, FALSE);
+
+    c = channel->priv;
+    if (c->state > SPICE_CHANNEL_STATE_CONNECTING) {
+        g_warning("Invalid channel_connect state: %d", c->state);
+        return true;
+    }
+
+    c->fd = fd;
+
+    return channel_connect(channel, FALSE);
+}
+
+/* system or coroutine context */
+static void channel_reset(SpiceChannel *channel, gboolean migrating)
+{
+    SpiceChannelPrivate *c = channel->priv;
+
+    CHANNEL_DEBUG(channel, "channel reset");
+    if (c->connect_delayed_id) {
+        g_source_remove(c->connect_delayed_id);
+        c->connect_delayed_id = 0;
+    }
+
+#if HAVE_SASL
+    if (c->sasl_conn) {
+        sasl_dispose(&c->sasl_conn);
+        c->sasl_conn = NULL;
+        c->sasl_decoded_offset = c->sasl_decoded_length = 0;
+    }
+#endif
+
+    spice_openssl_verify_free(c->sslverify);
+    c->sslverify = NULL;
+
+    if (c->ssl) {
+        SSL_free(c->ssl);
+        c->ssl = NULL;
+    }
+
+    if (c->ctx) {
+        SSL_CTX_free(c->ctx);
+        c->ctx = NULL;
+    }
+
+    if (c->conn) {
+        g_object_unref(c->conn);
+        c->conn = NULL;
+    }
+
+    g_clear_object(&c->sock);
+
+    c->fd = -1;
+
+    c->auth_needs_username_and_password = FALSE;
+
+    g_free(c->peer_msg);
+    c->peer_msg = NULL;
+    c->peer_pos = 0;
+
+    STATIC_MUTEX_LOCK(c->xmit_queue_lock);
+    c->xmit_queue_blocked = TRUE; /* Disallow queuing new messages */
+    gboolean was_empty = g_queue_is_empty(&c->xmit_queue);
+    g_queue_foreach(&c->xmit_queue, (GFunc)spice_msg_out_unref, NULL);
+    g_queue_clear(&c->xmit_queue);
+    if (c->xmit_queue_wakeup_id) {
+        g_source_remove(c->xmit_queue_wakeup_id);
+        c->xmit_queue_wakeup_id = 0;
+    }
+    STATIC_MUTEX_UNLOCK(c->xmit_queue_lock);
+    spice_channel_flushed(channel, was_empty);
+
+    g_array_set_size(c->remote_common_caps, 0);
+    g_array_set_size(c->remote_caps, 0);
+    g_array_set_size(c->common_caps, 0);
+    /* Restore our default capabilities in case the channel gets re-used */
+    spice_channel_set_common_capability(channel, SPICE_COMMON_CAP_PROTOCOL_AUTH_SELECTION);
+    spice_channel_set_common_capability(channel, SPICE_COMMON_CAP_MINI_HEADER);
+    spice_channel_reset_capabilities(channel);
+
+    if (c->state == SPICE_CHANNEL_STATE_SWITCHING)
+        spice_session_set_migration_state(spice_channel_get_session(channel),
+                                          SPICE_SESSION_MIGRATION_NONE);
+}
+
+/* system or coroutine context */
+G_GNUC_INTERNAL
+void spice_channel_reset(SpiceChannel *channel, gboolean migrating)
+{
+    CHANNEL_DEBUG(channel, "reset %s", migrating ? "migrating" : "");
+    SPICE_CHANNEL_GET_CLASS(channel)->channel_reset(channel, migrating);
+}
+
+/**
+ * spice_channel_disconnect:
+ * @channel:
+ * @reason: a channel event emitted on main context (or #SPICE_CHANNEL_NONE)
+ *
+ * Close the socket and reset connection specific data. Finally, emit
+ * @reason #SpiceChannel::channel-event on main context if not
+ * #SPICE_CHANNEL_NONE.
+ **/
+void spice_channel_disconnect(SpiceChannel *channel, SpiceChannelEvent reason)
+{
+    SpiceChannelPrivate *c;
+
+    CHANNEL_DEBUG(channel, "channel disconnect %d", reason);
+
+    g_return_if_fail(SPICE_IS_CHANNEL(channel));
+    g_return_if_fail(channel->priv != NULL);
+
+    c = channel->priv;
+
+    if (c->state == SPICE_CHANNEL_STATE_UNCONNECTED)
+        return;
+
+    if (reason == SPICE_CHANNEL_SWITCHING)
+        c->state = SPICE_CHANNEL_STATE_SWITCHING;
+
+    c->has_error = TRUE; /* break the loop */
+
+    if (c->state == SPICE_CHANNEL_STATE_MIGRATING) {
+        c->state = SPICE_CHANNEL_STATE_READY;
+    } else
+        spice_channel_wakeup(channel, TRUE);
+
+    if (reason != SPICE_CHANNEL_NONE)
+        g_signal_emit(G_OBJECT(channel), signals[SPICE_CHANNEL_EVENT], 0, reason);
+}
+
+static gboolean test_capability(GArray *caps, guint32 cap)
+{
+    guint32 c, word_index = cap / 32;
+    gboolean ret;
+
+    if (caps == NULL)
+        return FALSE;
+
+    if (caps->len < word_index + 1)
+        return FALSE;
+
+    c = g_array_index(caps, guint32, word_index);
+    ret = (c & (1 << (cap % 32))) != 0;
+
+    SPICE_DEBUG("test cap %d in 0x%X: %s", cap, c, ret ? "yes" : "no");
+    return ret;
+}
+
+/**
+ * spice_channel_test_capability:
+ * @channel:
+ * @cap:
+ *
+ * Test availability of remote "channel kind capability".
+ *
+ * Returns: %TRUE if @cap (channel kind capability) is available.
+ **/
+gboolean spice_channel_test_capability(SpiceChannel *self, guint32 cap)
+{
+    SpiceChannelPrivate *c;
+
+    g_return_val_if_fail(SPICE_IS_CHANNEL(self), FALSE);
+
+    c = self->priv;
+    return test_capability(c->remote_caps, cap);
+}
+
+/**
+ * spice_channel_test_common_capability:
+ * @channel:
+ * @cap:
+ *
+ * Test availability of remote "common channel capability".
+ *
+ * Returns: %TRUE if @cap (common channel capability) is available.
+ **/
+gboolean spice_channel_test_common_capability(SpiceChannel *self, guint32 cap)
+{
+    SpiceChannelPrivate *c;
+
+    g_return_val_if_fail(SPICE_IS_CHANNEL(self), FALSE);
+
+    c = self->priv;
+    return test_capability(c->remote_common_caps, cap);
+}
+
+static void set_capability(GArray *caps, guint32 cap)
+{
+    guint word_index = cap / 32;
+
+    g_return_if_fail(caps != NULL);
+
+    if (caps->len <= word_index)
+        g_array_set_size(caps, word_index + 1);
+
+    g_array_index(caps, guint32, word_index) =
+        g_array_index(caps, guint32, word_index) | (1 << (cap % 32));
+}
+
+/**
+ * spice_channel_set_capability:
+ * @channel:
+ * @cap: a capability
+ *
+ * Enable specific channel-kind capability.
+ * Deprecated: 0.13: this function has been removed
+ **/
+#undef spice_channel_set_capability
+void spice_channel_set_capability(SpiceChannel *channel, guint32 cap)
+{
+    SpiceChannelPrivate *c;
+
+    g_return_if_fail(SPICE_IS_CHANNEL(channel));
+
+    c = channel->priv;
+    set_capability(c->caps, cap);
+}
+
+G_GNUC_INTERNAL
+void spice_caps_set(GArray *caps, guint32 cap, const gchar *desc)
+{
+    g_return_if_fail(caps != NULL);
+    g_return_if_fail(desc != NULL);
+
+    if (g_strcmp0(g_getenv(desc), "0") == 0)
+        return;
+
+    set_capability(caps, cap);
+}
+
+G_GNUC_INTERNAL
+SpiceSession* spice_channel_get_session(SpiceChannel *channel)
+{
+    g_return_val_if_fail(SPICE_IS_CHANNEL(channel), NULL);
+
+    return channel->priv->session;
+}
+
+G_GNUC_INTERNAL
+enum spice_channel_state spice_channel_get_state(SpiceChannel *channel)
+{
+    g_return_val_if_fail(SPICE_IS_CHANNEL(channel),
+                         SPICE_CHANNEL_STATE_UNCONNECTED);
+
+    return channel->priv->state;
+}
+
+G_GNUC_INTERNAL
+void spice_channel_swap(SpiceChannel *channel, SpiceChannel *swap, gboolean swap_msgs)
+{
+    SpiceChannelPrivate *c = channel->priv;
+    SpiceChannelPrivate *s = swap->priv;
+
+    g_return_if_fail(c != NULL);
+    g_return_if_fail(s != NULL);
+
+    g_return_if_fail(s->session != NULL);
+    g_return_if_fail(s->sock != NULL);
+
+#define SWAP(Field) ({                          \
+    typeof (c->Field) Field = c->Field;         \
+    c->Field = s->Field;                        \
+    s->Field = Field;                           \
+})
+
+    /* TODO: split channel in 2 objects: a controller and a swappable
+       state object */
+    SWAP(sock);
+    SWAP(conn);
+    SWAP(in);
+    SWAP(out);
+    SWAP(ctx);
+    SWAP(ssl);
+    SWAP(sslverify);
+    SWAP(tls);
+    SWAP(use_mini_header);
+    if (swap_msgs) {
+        SWAP(xmit_queue);
+        SWAP(xmit_queue_blocked);
+        SWAP(in_serial);
+        SWAP(out_serial);
+    }
+    SWAP(caps);
+    SWAP(common_caps);
+    SWAP(remote_caps);
+    SWAP(remote_common_caps);
+#if HAVE_SASL
+    SWAP(sasl_conn);
+    SWAP(sasl_decoded);
+    SWAP(sasl_decoded_length);
+    SWAP(sasl_decoded_offset);
+#endif
+}
+
+/* coroutine context */
+static void spice_channel_handle_msg(SpiceChannel *channel, SpiceMsgIn *msg)
+{
+    SpiceChannelClass *klass = SPICE_CHANNEL_GET_CLASS(channel);
+    int type = spice_msg_in_type(msg);
+    spice_msg_handler handler;
+
+    g_return_if_fail(type < klass->handlers->len);
+    if (type > SPICE_MSG_BASE_LAST && channel->priv->disable_channel_msg)
+        return;
+
+    handler = g_array_index(klass->handlers, spice_msg_handler, type);
+    g_return_if_fail(handler != NULL);
+    handler(channel, msg);
+}
+
+static void spice_channel_reset_capabilities(SpiceChannel *channel)
+{
+    SpiceChannelPrivate *c = channel->priv;
+    g_array_set_size(c->caps, 0);
+
+    if (SPICE_CHANNEL_GET_CLASS(channel)->channel_reset_capabilities) {
+        SPICE_CHANNEL_GET_CLASS(channel)->channel_reset_capabilities(channel);
+    }
+}
+
+static void spice_channel_send_migration_handshake(SpiceChannel *channel)
+{
+    SpiceChannelPrivate *c = channel->priv;
+
+    if (SPICE_CHANNEL_GET_CLASS(channel)->channel_send_migration_handshake) {
+        SPICE_CHANNEL_GET_CLASS(channel)->channel_send_migration_handshake(channel);
+    } else {
+        c->state = SPICE_CHANNEL_STATE_MIGRATING;
+    }
+}
+
+/**
+ * spice_channel_flush_async:
+ * @channel: a #SpiceChannel
+ * @cancellable: (allow-none): optional GCancellable object, %NULL to ignore
+ * @callback: (scope async): callback to call when the request is satisfied
+ * @user_data: (closure): the data to pass to callback function
+ *
+ * Forces an asynchronous write of all user-space buffered data for
+ * the given channel.
+ *
+ * When the operation is finished callback will be called. You can
+ * then call spice_channel_flush_finish() to get the result of the
+ * operation.
+ *
+ * Since: 0.15
+ **/
+void spice_channel_flush_async(SpiceChannel *self, GCancellable *cancellable,
+                               GAsyncReadyCallback callback, gpointer user_data)
+{
+    GSimpleAsyncResult *simple;
+    SpiceChannelPrivate *c;
+    gboolean was_empty;
+
+    g_return_if_fail(SPICE_IS_CHANNEL(self));
+    c = self->priv;
+
+    if (c->state != SPICE_CHANNEL_STATE_READY) {
+        g_simple_async_report_error_in_idle(G_OBJECT(self), callback, user_data,
+            SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+            "The channel is not ready yet");
+        return;
+    }
+
+    simple = g_simple_async_result_new(G_OBJECT(self), callback, user_data,
+                                       spice_channel_flush_async);
+
+    STATIC_MUTEX_LOCK(c->xmit_queue_lock);
+    was_empty = g_queue_is_empty(&c->xmit_queue);
+    STATIC_MUTEX_UNLOCK(c->xmit_queue_lock);
+    if (was_empty) {
+        g_simple_async_result_set_op_res_gboolean(simple, TRUE);
+        g_simple_async_result_complete_in_idle(simple);
+        g_object_unref(simple);
+        return;
+    }
+
+    c->flushing = g_slist_append(c->flushing, simple);
+}
+
+/**
+ * spice_channel_flush_finish:
+ * @channel: a #SpiceChannel
+ * @result: a #GAsyncResult
+ * @error: a #GError location to store the error occurring, or %NULL
+ * to ignore.
+ *
+ * Finishes flushing a channel.
+ *
+ * Returns: %TRUE if flush operation succeeded, %FALSE otherwise.
+ * Since: 0.15
+ **/
+gboolean spice_channel_flush_finish(SpiceChannel *self, GAsyncResult *result,
+                                    GError **error)
+{
+    GSimpleAsyncResult *simple;
+
+    g_return_val_if_fail(SPICE_IS_CHANNEL(self), FALSE);
+    g_return_val_if_fail(result != NULL, FALSE);
+
+    simple = (GSimpleAsyncResult *)result;
+
+    if (g_simple_async_result_propagate_error(simple, error))
+        return -1;
+
+    g_return_val_if_fail(g_simple_async_result_is_valid(result, G_OBJECT(self),
+                                                        spice_channel_flush_async), FALSE);
+
+    CHANNEL_DEBUG(self, "flushed finished!");
+    return g_simple_async_result_get_op_res_gboolean(simple);
+}
diff --git a/src/spice-channel.h b/src/spice-channel.h
new file mode 100644
index 0000000..7f132f6
--- /dev/null
+++ b/src/spice-channel.h
@@ -0,0 +1,131 @@
+/* -*- 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_CLIENT_CHANNEL_H__
+#define __SPICE_CLIENT_CHANNEL_H__
+
+G_BEGIN_DECLS
+
+#include <gio/gio.h>
+#include "spice-types.h"
+#include "spice-glib-enums.h"
+#include "spice-util.h"
+
+#define SPICE_TYPE_CHANNEL            (spice_channel_get_type ())
+#define SPICE_CHANNEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), SPICE_TYPE_CHANNEL, SpiceChannel))
+#define SPICE_CHANNEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SPICE_TYPE_CHANNEL, SpiceChannelClass))
+#define SPICE_IS_CHANNEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SPICE_TYPE_CHANNEL))
+#define SPICE_IS_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SPICE_TYPE_CHANNEL))
+#define SPICE_CHANNEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SPICE_TYPE_CHANNEL, SpiceChannelClass))
+
+typedef struct _SpiceMsgIn  SpiceMsgIn;
+typedef struct _SpiceMsgOut SpiceMsgOut;
+
+/**
+ * SpiceChannelEvent:
+ * @SPICE_CHANNEL_NONE: no event, or ignored event
+ * @SPICE_CHANNEL_OPENED: connection is authentified and ready
+ * @SPICE_CHANNEL_CLOSED: connection is closed normally (sent if channel was ready)
+ * @SPICE_CHANNEL_ERROR_CONNECT: connection error
+ * @SPICE_CHANNEL_ERROR_TLS: SSL error
+ * @SPICE_CHANNEL_ERROR_LINK: error during link process
+ * @SPICE_CHANNEL_ERROR_AUTH: authentication error
+ * @SPICE_CHANNEL_ERROR_IO: IO error
+ *
+ * An event, emitted by #SpiceChannel::channel-event signal.
+ **/
+typedef enum
+{
+    SPICE_CHANNEL_NONE = 0,
+    SPICE_CHANNEL_OPENED = 10,
+    SPICE_CHANNEL_SWITCHING,
+    SPICE_CHANNEL_CLOSED,
+    SPICE_CHANNEL_ERROR_CONNECT = 20,
+    SPICE_CHANNEL_ERROR_TLS,
+    SPICE_CHANNEL_ERROR_LINK,
+    SPICE_CHANNEL_ERROR_AUTH,
+    SPICE_CHANNEL_ERROR_IO,
+} SpiceChannelEvent;
+
+struct _SpiceChannel
+{
+    GObject parent;
+    SpiceChannelPrivate *priv;
+    /* Do not add fields to this struct */
+};
+
+struct _SpiceChannelClass
+{
+    GObjectClass parent_class;
+
+    /*< public >*/
+    /* signals, main context */
+    void (*channel_event)(SpiceChannel *channel, SpiceChannelEvent event);
+    void (*open_fd)(SpiceChannel *channel, int with_tls);
+
+    /*< private >*/
+    /* virtual methods, coroutine context */
+    void (*handle_msg)(SpiceChannel *channel, SpiceMsgIn *msg);
+    void (*channel_up)(SpiceChannel *channel);
+    void (*iterate_write)(SpiceChannel *channel);
+    void (*iterate_read)(SpiceChannel *channel);
+
+    /*< private >*/
+    /* virtual method, any context */
+    gpointer deprecated;
+    void (*channel_reset)(SpiceChannel *channel, gboolean migrating);
+    void (*channel_reset_capabilities)(SpiceChannel *channel);
+
+    /*< private >*/
+    /* virtual methods, coroutine context */
+    void (*channel_send_migration_handshake)(SpiceChannel *channel);
+
+    GArray                      *handlers;
+    /*
+     * If adding fields to this struct, remove corresponding
+     * amount of padding to avoid changing overall struct size
+     */
+    gchar _spice_reserved[SPICE_RESERVED_PADDING - 2 * sizeof(void *)];
+};
+
+GType spice_channel_get_type(void);
+
+typedef void (*spice_msg_handler)(SpiceChannel *channel, SpiceMsgIn *in);
+
+SpiceChannel *spice_channel_new(SpiceSession *s, int type, int id);
+gboolean spice_channel_connect(SpiceChannel *channel);
+gboolean spice_channel_open_fd(SpiceChannel *channel, int fd);
+void spice_channel_disconnect(SpiceChannel *channel, SpiceChannelEvent reason);
+gboolean spice_channel_test_capability(SpiceChannel *channel, guint32 cap);
+gboolean spice_channel_test_common_capability(SpiceChannel *channel, guint32 cap);
+void spice_channel_flush_async(SpiceChannel *channel, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data);
+gboolean spice_channel_flush_finish(SpiceChannel *channel, GAsyncResult *result, GError **error);
+#ifndef SPICE_DISABLE_DEPRECATED
+SPICE_DEPRECATED
+void spice_channel_set_capability(SpiceChannel *channel, guint32 cap);
+SPICE_DEPRECATED
+void spice_channel_destroy(SpiceChannel *channel);
+#endif
+
+const gchar* spice_channel_type_to_string(gint type);
+gint spice_channel_string_to_type(const gchar *str);
+
+const GError* spice_channel_get_error(SpiceChannel *channel);
+
+G_END_DECLS
+
+#endif /* __SPICE_CLIENT_CHANNEL_H__ */
diff --git a/src/spice-client-glib-usb-acl-helper.c b/src/spice-client-glib-usb-acl-helper.c
new file mode 100644
index 0000000..bc09776
--- /dev/null
+++ b/src/spice-client-glib-usb-acl-helper.c
@@ -0,0 +1,372 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2011,2012 Red Hat, Inc.
+   Copyright (C) 2009 Kay Sievers <kay.sievers at vrfy.org>
+
+   Red Hat Authors:
+   Hans de Goede <hdegoede at redhat.com>
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as published
+   by the Free Software Foundation; either version 2 of the License,
+   or (at your option) any later version.
+
+   This program 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
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License along
+   with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "config.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <gio/gunixinputstream.h>
+#include <polkit/polkit.h>
+#include <acl/libacl.h>
+
+#include "glib-compat.h"
+
+#define FATAL_ERROR(...) \
+    do { \
+        /* We print the error both to stdout, for the app invoking us and \
+           stderr for the end user */ \
+        fprintf(stdout, "Error " __VA_ARGS__); \
+        fprintf(stderr, "spice-client-glib-usb-helper: Error " __VA_ARGS__); \
+        exit_status = 1; \
+        cleanup(); \
+    } while (0)
+
+#define ERROR(...) \
+    do { \
+        fprintf(stdout, __VA_ARGS__); \
+        cleanup(); \
+    } while (0)
+
+enum state {
+    STATE_WAITING_FOR_BUS_N_DEV,
+    STATE_WAITING_FOR_POL_KIT,
+    STATE_WAITING_FOR_STDIN_EOF,
+};
+
+static enum state state = STATE_WAITING_FOR_BUS_N_DEV;
+static int exit_status;
+static int busnum, devnum;
+static char path[PATH_MAX];
+static GMainLoop *loop;
+static GDataInputStream *stdin_stream;
+static GCancellable *polkit_cancellable;
+static PolkitSubject *subject;
+static PolkitAuthority *authority;
+
+/*
+ * This function is a copy of the same function in udev, written by Kay
+ * Sievers, you can find it in udev in extras/udev-acl/udev-acl.c
+ */
+static int set_facl(const char* filename, uid_t uid, int add)
+{
+    int get;
+    acl_t acl;
+    acl_entry_t entry = NULL;
+    acl_entry_t e;
+    acl_permset_t permset;
+    int ret;
+
+    /* don't touch ACLs for root */
+    if (uid == 0)
+        return 0;
+
+    /* read current record */
+    acl = acl_get_file(filename, ACL_TYPE_ACCESS);
+    if (!acl)
+        return -1;
+
+    /* locate ACL_USER entry for uid */
+    get = acl_get_entry(acl, ACL_FIRST_ENTRY, &e);
+    while (get == 1) {
+        acl_tag_t t;
+
+        acl_get_tag_type(e, &t);
+        if (t == ACL_USER) {
+            uid_t *u;
+
+            u = (uid_t*)acl_get_qualifier(e);
+            if (u == NULL) {
+                ret = -1;
+                goto out;
+            }
+            if (*u == uid) {
+                entry = e;
+                acl_free(u);
+                break;
+            }
+            acl_free(u);
+        }
+
+        get = acl_get_entry(acl, ACL_NEXT_ENTRY, &e);
+    }
+
+    /* remove ACL_USER entry for uid */
+    if (!add) {
+        if (entry == NULL) {
+            ret = 0;
+            goto out;
+        }
+        acl_delete_entry(acl, entry);
+        goto update;
+    }
+
+    /* create ACL_USER entry for uid */
+    if (entry == NULL) {
+        ret = acl_create_entry(&acl, &entry);
+        if (ret != 0)
+            goto out;
+        acl_set_tag_type(entry, ACL_USER);
+        acl_set_qualifier(entry, &uid);
+    }
+
+    /* add permissions for uid */
+    acl_get_permset(entry, &permset);
+    acl_add_perm(permset, ACL_READ|ACL_WRITE);
+update:
+    /* update record */
+    acl_calc_mask(&acl);
+    ret = acl_set_file(filename, ACL_TYPE_ACCESS, acl);
+    if (ret != 0)
+        goto out;
+out:
+    acl_free(acl);
+    return ret;
+}
+
+static void cleanup(void)
+{
+    if (polkit_cancellable)
+        g_cancellable_cancel(polkit_cancellable);
+
+    if (state == STATE_WAITING_FOR_STDIN_EOF)
+        set_facl(path, getuid(), 0);
+
+    if (loop)
+        g_main_loop_quit(loop);
+}
+
+/* Not available in polkit < 0.101 */
+#if !HAVE_POLKIT_AUTHORIZATION_RESULT_GET_DISMISSED
+static gboolean
+polkit_authorization_result_get_dismissed(PolkitAuthorizationResult *result)
+{
+    gboolean ret;
+    PolkitDetails *details;
+
+    g_return_val_if_fail(POLKIT_IS_AUTHORIZATION_RESULT(result), FALSE);
+
+    ret = FALSE;
+    details = polkit_authorization_result_get_details(result);
+    if (details != NULL && polkit_details_lookup(details, "polkit.dismissed"))
+        ret = TRUE;
+
+    return ret;
+}
+#endif
+
+static void check_authorization_cb(PolkitAuthority *authority,
+                                   GAsyncResult *res, gpointer data)
+{
+    PolkitAuthorizationResult *result;
+    GError *err = NULL;
+    struct stat stat_buf;
+
+    g_clear_object(&polkit_cancellable);
+
+    result = polkit_authority_check_authorization_finish(authority, res, &err);
+    if (err) {
+        FATAL_ERROR("PoliciKit error: %s\n", err->message);
+        g_error_free(err);
+        return;
+    }
+
+    if (polkit_authorization_result_get_dismissed(result)) {
+        ERROR("CANCELED\n");
+        return;
+    }
+
+    if (!polkit_authorization_result_get_is_authorized(result)) {
+        ERROR("Not authorized\n");
+        return;
+    }
+
+    snprintf(path, PATH_MAX, "/dev/bus/usb/%03d/%03d", busnum, devnum);
+
+    if (stat(path, &stat_buf) != 0) {
+        FATAL_ERROR("statting %s: %s\n", path, strerror(errno));
+        return;
+    }
+    if (!S_ISCHR(stat_buf.st_mode)) {
+        FATAL_ERROR("%s is not a character device\n", path);
+        return;
+    }
+
+    if (set_facl(path, getuid(), 1)) {
+        FATAL_ERROR("setting facl: %s\n", strerror(errno));
+        return;
+    }
+
+    fprintf(stdout, "SUCCESS\n");
+    fflush(stdout);
+    state = STATE_WAITING_FOR_STDIN_EOF;
+}
+
+static void stdin_read_complete(GObject *src, GAsyncResult *res, gpointer data)
+{
+    char *s, *ep;
+    GError *err = NULL;
+    gsize len;
+
+    s = g_data_input_stream_read_line_finish(G_DATA_INPUT_STREAM(src), res,
+                                             &len, &err);
+    if (!s) {
+        if (err) {
+            FATAL_ERROR("Reading from stdin: %s\n", err->message);
+            g_error_free(err);
+            return;
+        }
+
+        switch (state) {
+        case STATE_WAITING_FOR_BUS_N_DEV:
+            FATAL_ERROR("EOF while waiting for bus and device num\n");
+            break;
+        case STATE_WAITING_FOR_POL_KIT:
+            ERROR("Cancelled while waiting for authorization\n");
+            break;
+        case STATE_WAITING_FOR_STDIN_EOF:
+            cleanup();
+            break;
+        }
+        return;
+    }
+
+    switch (state) {
+    case STATE_WAITING_FOR_BUS_N_DEV:
+        busnum = strtol(s, &ep, 10);
+        if (!isspace(*ep)) {
+            FATAL_ERROR("Invalid busnum / devnum: %s\n", s);
+            break;
+        }
+        devnum = strtol(ep, &ep, 10);
+        if (*ep != '\0') {
+            FATAL_ERROR("Invalid busnum / devnum: %s\n", s);
+            break;
+        }
+
+        /*
+         * The set_facl() call is a no-op for root, so no need to ask PolKit
+         * and then if ok call set_facl(), when called by a root process.
+         */
+        if (getuid() != 0) {
+            polkit_cancellable = g_cancellable_new();
+            polkit_authority_check_authorization(
+                authority, subject, "org.spice-space.lowlevelusbaccess", NULL,
+                POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION,
+                polkit_cancellable,
+                (GAsyncReadyCallback)check_authorization_cb, NULL);
+            state = STATE_WAITING_FOR_POL_KIT;
+        } else {
+            fprintf(stdout, "SUCCESS\n");
+            fflush(stdout);
+            state = STATE_WAITING_FOR_STDIN_EOF;
+        }
+
+        g_data_input_stream_read_line_async(stdin_stream, G_PRIORITY_DEFAULT,
+                                            NULL, stdin_read_complete, NULL);
+        break;
+    default:
+        FATAL_ERROR("Unexpected extra input in state %d: %s\n", state, s);
+    }
+    g_free(s);
+}
+
+/* Fix for polkit 0.97 and later */
+#if !HAVE_POLKIT_AUTHORITY_GET_SYNC
+static PolkitAuthority *
+polkit_authority_get_sync (GCancellable *cancellable, GError **error)
+{
+    PolkitAuthority *authority;
+
+    authority = polkit_authority_get ();
+    if (!authority)
+        g_set_error (error, 0, 0, "failed to get the PolicyKit authority");
+
+    return authority;
+}
+#endif
+
+#ifndef HAVE_CLEARENV
+extern char **environ;
+
+static int
+clearenv (void)
+{
+        if (environ != NULL)
+                environ[0] = NULL;
+        return 0;
+}
+#endif
+
+int main(void)
+{
+    pid_t parent_pid;
+    GInputStream *stdin_unix_stream;
+
+  /* Nuke the environment to get a well-known and sanitized
+   * environment to avoid attacks via e.g. the DBUS_SYSTEM_BUS_ADDRESS
+   * environment variable and similar.
+   */
+    if (clearenv () != 0) {
+        FATAL_ERROR("Error clearing environment: %s\n", g_strerror (errno));
+        return 1;
+    }
+
+#if !GLIB_CHECK_VERSION(2,36,0)
+    g_type_init();
+#endif
+
+    loop = g_main_loop_new(NULL, FALSE);
+
+    authority = polkit_authority_get_sync(NULL, NULL);
+    parent_pid = getppid ();
+    if (parent_pid == 1) {
+            FATAL_ERROR("Parent process was reaped by init(1)\n");
+            return 1;
+    }
+    /* Do what pkexec does */
+    subject = polkit_unix_process_new_for_owner(parent_pid, 0, getuid ());
+
+    stdin_unix_stream = g_unix_input_stream_new(STDIN_FILENO, 0);
+    stdin_stream = g_data_input_stream_new(stdin_unix_stream);
+    g_data_input_stream_set_newline_type(stdin_stream,
+                                         G_DATA_STREAM_NEWLINE_TYPE_LF);
+    g_clear_object(&stdin_unix_stream);
+    g_data_input_stream_read_line_async(stdin_stream, G_PRIORITY_DEFAULT, NULL,
+                                        stdin_read_complete, NULL);
+
+    g_main_loop_run(loop);
+
+    if (polkit_cancellable)
+        g_clear_object(&polkit_cancellable);
+    g_object_unref(stdin_stream);
+    g_object_unref(authority);
+    g_object_unref(subject);
+    g_main_loop_unref(loop);
+
+    return exit_status;
+}
diff --git a/src/spice-client-gtk-manual.defs b/src/spice-client-gtk-manual.defs
new file mode 100644
index 0000000..9631b74
--- /dev/null
+++ b/src/spice-client-gtk-manual.defs
@@ -0,0 +1,117 @@
+(define-method set_display
+  (of-object "SpiceMainChannel")
+  (c-name "spice_main_set_display")
+  (return-type "none")
+  (parameters
+    '("int" "id")
+    '("int" "x")
+    '("int" "y")
+    '("int" "width")
+    '("int" "height")
+  )
+)
+
+(define-method clipboard_grab
+  (of-object "SpiceMainChannel")
+  (c-name "spice_main_clipboard_grab")
+  (return-type "none")
+  (parameters
+    '("int*" "types")
+    '("int" "ntypes")
+  )
+)
+
+(define-method clipboard_release
+  (of-object "SpiceMainChannel")
+  (c-name "spice_main_clipboard_release")
+  (return-type "none")
+)
+
+(define-method motion
+  (of-object "SpiceInputsChannel")
+  (c-name "spice_inputs_motion")
+  (return-type "none")
+  (parameters
+    '("gint" "dx")
+    '("gint" "dy")
+    '("gint" "button_state")
+  )
+)
+
+(define-method position
+  (of-object "SpiceInputsChannel")
+  (c-name "spice_inputs_position")
+  (return-type "none")
+  (parameters
+    '("gint" "x")
+    '("gint" "y")
+    '("gint" "display")
+    '("gint" "button_state")
+  )
+)
+
+(define-method button_press
+  (of-object "SpiceInputsChannel")
+  (c-name "spice_inputs_button_press")
+  (return-type "none")
+  (parameters
+    '("gint" "button")
+    '("gint" "button_state")
+  )
+)
+
+(define-method button_release
+  (of-object "SpiceInputsChannel")
+  (c-name "spice_inputs_button_release")
+  (return-type "none")
+  (parameters
+    '("gint" "button")
+    '("gint" "button_state")
+  )
+)
+
+(define-method key_press
+  (of-object "SpiceInputsChannel")
+  (c-name "spice_inputs_key_press")
+  (return-type "none")
+  (parameters
+    '("guint" "keyval")
+  )
+)
+
+(define-method key_release
+  (of-object "SpiceInputsChannel")
+  (c-name "spice_inputs_key_release")
+  (return-type "none")
+  (parameters
+    '("guint" "keyval")
+  )
+)
+
+(define-method set_key_locks
+  (of-object "SpiceInputsChannel")
+  (c-name "spice_inputs_set_key_locks")
+  (return-type "none")
+  (parameters
+    '("guint" "locks")
+  )
+)
+
+(define-enum ClientError
+  (in-module "Spice")
+  (c-name "SpiceClientError")
+  (values
+    '("failed" "SPICE_CLIENT_ERROR_FAILED")
+  )
+)
+
+(define-function spice_audio_new
+  (c-name "spice_audio_new")
+  (is-constructor-of "SpiceAudio")
+  (return-type "SpiceAudio*")
+  (parameters
+    '("SpiceSession*" "session")
+    '("GMainContext*" "context")
+    '("const-char*" "name")
+  )
+)
diff --git a/src/spice-client-gtk-module.c b/src/spice-client-gtk-module.c
new file mode 100644
index 0000000..b82f1e3
--- /dev/null
+++ b/src/spice-client-gtk-module.c
@@ -0,0 +1,45 @@
+/* -*- 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 <pygobject.h>
+
+void spice_register_classes (PyObject *d);
+void spice_add_constants(PyObject *module, const gchar *strip_prefix);
+extern PyMethodDef spice_functions[];
+
+DL_EXPORT(void) initSpiceClientGtk(void)
+{
+    PyObject *m, *d;
+
+    init_pygobject();
+
+    m = Py_InitModule("SpiceClientGtk", spice_functions);
+    if (PyErr_Occurred())
+        Py_FatalError("can't init module");
+
+    d = PyModule_GetDict(m);
+    if (PyErr_Occurred())
+        Py_FatalError("can't get dict");
+
+    spice_register_classes(d);
+    spice_add_constants(m, "SPICE_");
+
+    if (PyErr_Occurred()) {
+        Py_FatalError("can't initialise module SpiceClientGtk");
+    }
+}
diff --git a/src/spice-client-gtk.override b/src/spice-client-gtk.override
new file mode 100644
index 0000000..41aeee3
--- /dev/null
+++ b/src/spice-client-gtk.override
@@ -0,0 +1,171 @@
+%%
+headers
+#include <Python.h>
+#include "pygobject.h"
+#include "spice-common.h"
+#include "spice-widget.h"
+#include "spice-gtk-session.h"
+#include "spice-audio.h"
+#include "usb-device-widget.h"
+%%
+modulename spice_client_gtk
+%%
+import gobject.GObject as PyGObject_Type
+import gtk.DrawingArea as PyGtkDrawingArea_Type
+import gtk.Widget as PyGtkWidget_Type
+import gtk.VBox as PyGtkVBox_Type
+%%
+ignore-glob
+  *_get_type
+%%
+%%
+override spice_display_send_keys kwargs
+static PyObject*
+_wrap_spice_display_send_keys(PyGObject *self,
+                            PyObject *args, PyObject *kwargs)
+{
+    static char *kwlist[] = {"keys", "kind", NULL};
+    PyObject *keyList;
+    int kind = SPICE_DISPLAY_KEY_EVENT_CLICK;
+    int i, len;
+    guint *keys;
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs,
+                                     "O|I:SpiceDisplay.send_keys", kwlist,
+                                     &keyList, &kind))
+	return NULL;
+
+    if (!PyList_Check(keyList))
+	return NULL;
+
+    len = PyList_Size(keyList);
+    keys = g_malloc0(sizeof(guint)*len);
+
+    for (i = 0 ; i < len ; i++) {
+        PyObject *val;
+        char *sym;
+        val = PyList_GetItem(keyList, i);
+        sym = PyString_AsString(val);
+        if (!sym) {
+            g_free(keys);
+	    return NULL;
+        }
+        keys[i] = gdk_keyval_from_name(sym);
+    }
+
+    spice_display_send_keys(SPICE_DISPLAY(self->obj), keys, len, kind);
+    g_free(keys);
+
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+%%
+override spice_display_get_grab_keys kwargs
+static PyObject*
+_wrap_spice_display_get_grab_keys(PyGObject *self,
+                            PyObject *args, PyObject *kwargs)
+{
+    SpiceGrabSequence *seq;
+    PyObject *keyList;
+    int i;
+
+    seq = spice_display_get_grab_keys(SPICE_DISPLAY(self->obj));
+
+    keyList = PyList_New(0);
+    for (i = 0 ; i < seq->nkeysyms ; i++)
+       PyList_Append(keyList, PyInt_FromLong(seq->keysyms[i]));
+
+    return keyList;
+}
+%%
+override spice_display_set_grab_keys kwargs
+static PyObject*
+_wrap_spice_display_set_grab_keys(PyGObject *self,
+                            PyObject *args, PyObject *kwargs)
+{
+    static char *kwlist[] = {"keys", NULL};
+    PyObject *keyList;
+    int i;
+    guint nkeysyms;
+    guint *keysyms;
+    SpiceGrabSequence *seq;
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs,
+                                     "O|I:SpiceDisplay.set_grab_keys", kwlist,
+                                     &keyList))
+        return NULL;
+
+    if (!PyList_Check(keyList))
+        return NULL;
+
+    nkeysyms = PyList_Size(keyList);
+    keysyms = g_new0(guint, nkeysyms);
+
+    for (i = 0 ; i < nkeysyms ; i++) {
+        PyObject *val = PyList_GetItem(keyList, i);
+        keysyms[i] = (guint)PyInt_AsLong(val);
+    }
+
+    seq = spice_grab_sequence_new(nkeysyms, keysyms);
+    g_free(keysyms);
+
+    spice_display_set_grab_keys(SPICE_DISPLAY(self->obj), seq);
+
+    spice_grab_sequence_free(seq);
+
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+%%
+override spice_session_get_channels
+static PyObject*
+_wrap_spice_session_get_channels(PyGObject *self,
+                                 PyObject *args, PyObject *kwargs)
+{
+    PyObject *py_list;
+    GList *list, *tmp;
+    PyObject *chann;
+
+    list = spice_session_get_channels(SPICE_SESSION(self->obj));
+
+    if ((py_list = PyList_New(0)) == NULL) {
+        return NULL;
+    }
+    for (tmp = list; tmp != NULL; tmp = tmp->next) {
+        chann = pygobject_new(G_OBJECT(tmp->data));
+        if (chann == NULL) {
+            Py_DECREF(py_list);
+            return NULL;
+        }
+        PyList_Append(py_list, chann);
+        Py_DECREF(chann);
+    }
+    return py_list;
+}
+%%
+override spice_audio_new
+static int
+_wrap_spice_audio_new(PyGObject *self,
+                      PyObject *args, PyObject *kwargs)
+{
+    static char *kwlist[] = {"session", "context", "name", NULL};
+    PyGObject *session = NULL;
+    PyObject *py_context = NULL;
+    char *name = NULL;
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs,
+                                     "O!|Os:SpiceAudio", kwlist,
+                                     &PySpiceSession_Type, &session,
+                                     &py_context, &name))
+	return -1;
+
+    self->obj = (GObject *)spice_audio_new(SPICE_SESSION(session->obj), NULL, NULL);
+
+    if (!self->obj) {
+        PyErr_SetString(PyExc_RuntimeError, "could not create SpiceAudio object");
+        return -1;
+    }
+    pygobject_register_wrapper((PyObject *)self);
+    return 0;
+
+}
diff --git a/src/spice-client.c b/src/spice-client.c
new file mode 100644
index 0000000..5fd511f
--- /dev/null
+++ b/src/spice-client.c
@@ -0,0 +1,27 @@
+/* -*- 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.h>
+
+#include "spice-client.h"
+
+GQuark spice_client_error_quark(void)
+{
+    return g_quark_from_static_string("spice-client-error-quark");
+}
diff --git a/src/spice-client.h b/src/spice-client.h
new file mode 100644
index 0000000..c2474d1
--- /dev/null
+++ b/src/spice-client.h
@@ -0,0 +1,79 @@
+/* -*- 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_CLIENT_CLIENT_H__
+#define __SPICE_CLIENT_CLIENT_H__
+
+/* glib */
+#include <glib.h>
+#include <glib-object.h>
+
+/* spice-protocol */
+#include <spice/enums.h>
+#include <spice/protocol.h>
+
+/* spice/gtk */
+#include "spice-types.h"
+#include "spice-session.h"
+#include "spice-channel.h"
+#include "spice-option.h"
+#include "spice-uri.h"
+#include "spice-version.h"
+
+#include "channel-main.h"
+#include "channel-display.h"
+#include "channel-cursor.h"
+#include "channel-inputs.h"
+#include "channel-playback.h"
+#include "channel-record.h"
+#include "channel-smartcard.h"
+#include "channel-usbredir.h"
+#include "channel-port.h"
+#include "channel-webdav.h"
+
+#include "smartcard-manager.h"
+#include "usb-device-manager.h"
+#include "spice-audio.h"
+
+G_BEGIN_DECLS
+
+#define SPICE_CLIENT_ERROR spice_client_error_quark()
+
+/**
+ * SpiceClientError:
+ * @SPICE_CLIENT_ERROR_FAILED: generic error code
+ * @SPICE_CLIENT_USB_DEVICE_REJECTED: usb device rejected by host
+ * @SPICE_CLIENT_USB_DEVICE_LOST: usb device disconnected (fatal IO error)
+ * @SPICE_CLIENT_ERROR_AUTH_NEEDS_PASSWORD: password is required
+ * @SPICE_CLIENT_ERROR_AUTH_NEEDS_PASSWORD_AND_USERNAME: password and username are required
+ *
+ * Error codes returned by spice-client API.
+ */
+typedef enum
+{
+    SPICE_CLIENT_ERROR_FAILED,
+    SPICE_CLIENT_USB_DEVICE_REJECTED,
+    SPICE_CLIENT_USB_DEVICE_LOST,
+    SPICE_CLIENT_ERROR_AUTH_NEEDS_PASSWORD,
+    SPICE_CLIENT_ERROR_AUTH_NEEDS_PASSWORD_AND_USERNAME,
+} SpiceClientError;
+
+GQuark spice_client_error_quark(void);
+
+G_END_DECLS
+
+#endif /* __SPICE_CLIENT_CLIENT_H__ */
diff --git a/src/spice-cmdline.c b/src/spice-cmdline.c
new file mode 100644
index 0000000..8619b57
--- /dev/null
+++ b/src/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.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
new file mode 100644
index 0000000..11a8086
--- /dev/null
+++ b/src/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/src/spice-common.h b/src/spice-common.h
new file mode 100644
index 0000000..8554f4c
--- /dev/null
+++ b/src/spice-common.h
@@ -0,0 +1,36 @@
+/* -*- 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_COMMON_H_
+# define SPICE_COMMON_H_
+
+/* system */
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <inttypes.h>
+
+#include "common/mem.h"
+#include "common/messages.h"
+#include "common/marshaller.h"
+
+#include "spice-util.h"
+
+#endif // SPICE_COMMON_H_
diff --git a/src/spice-glib-sym-file b/src/spice-glib-sym-file
new file mode 100644
index 0000000..3a8da93
--- /dev/null
+++ b/src/spice-glib-sym-file
@@ -0,0 +1,111 @@
+spice_audio_get
+spice_audio_get_type
+spice_audio_new
+spice_channel_connect
+spice_channel_destroy
+spice_channel_disconnect
+spice_channel_event_get_type
+spice_channel_flush_async
+spice_channel_flush_finish
+spice_channel_get_error
+spice_channel_get_type
+spice_channel_new
+spice_channel_open_fd
+spice_channel_set_capability
+spice_channel_string_to_type
+spice_channel_test_capability
+spice_channel_test_common_capability
+spice_channel_type_to_string
+spice_client_error_quark
+spice_cursor_channel_get_type
+spice_display_channel_get_type
+spice_display_get_primary
+spice_get_option_group
+spice_g_signal_connect_object
+spice_inputs_button_press
+spice_inputs_button_release
+spice_inputs_channel_get_type
+spice_inputs_key_press
+spice_inputs_key_press_and_release
+spice_inputs_key_release
+spice_inputs_lock_get_type
+spice_inputs_motion
+spice_inputs_position
+spice_inputs_set_key_locks
+spice_main_agent_test_capability
+spice_main_channel_get_type
+spice_main_clipboard_grab
+spice_main_clipboard_notify
+spice_main_clipboard_release
+spice_main_clipboard_request
+spice_main_clipboard_selection_grab
+spice_main_clipboard_selection_notify
+spice_main_clipboard_selection_release
+spice_main_clipboard_selection_request
+spice_main_file_copy_async
+spice_main_file_copy_finish
+spice_main_send_monitor_config
+spice_main_set_display
+spice_main_set_display_enabled
+spice_main_update_display
+spice_playback_channel_get_type
+spice_playback_channel_set_delay
+spice_port_channel_get_type
+spice_port_event
+spice_port_write_async
+spice_port_write_finish
+spice_record_channel_get_type
+spice_record_send_data
+spice_session_connect
+spice_session_disconnect
+spice_session_get_channels
+spice_session_get_proxy_uri
+spice_session_get_read_only
+spice_session_get_type
+spice_session_has_channel_type
+spice_session_is_for_migration
+spice_session_migration_get_type
+spice_session_new
+spice_session_open_fd
+spice_session_verify_get_type
+spice_set_session_option
+spice_smartcard_channel_get_type
+spice_smartcard_manager_get
+spice_smartcard_manager_get_readers
+spice_smartcard_manager_get_type
+spice_smartcard_manager_insert_card
+spice_smartcard_manager_remove_card
+spice_smartcard_reader_get_type
+spice_smartcard_reader_insert_card
+spice_smartcard_reader_is_software
+spice_smartcard_reader_remove_card
+spice_uri_get_hostname
+spice_uri_get_password
+spice_uri_get_port
+spice_uri_get_scheme
+spice_uri_get_type
+spice_uri_get_user
+spice_uri_set_hostname
+spice_uri_set_password
+spice_uri_set_port
+spice_uri_set_scheme
+spice_uri_set_user
+spice_uri_to_string
+spice_usb_device_get_description
+spice_usb_device_get_libusb_device
+spice_usb_device_get_type
+spice_usb_device_manager_can_redirect_device
+spice_usb_device_manager_connect_device_async
+spice_usb_device_manager_connect_device_finish
+spice_usb_device_manager_disconnect_device
+spice_usb_device_manager_get
+spice_usb_device_manager_get_devices
+spice_usb_device_manager_get_devices_with_filter
+spice_usb_device_manager_get_type
+spice_usb_device_manager_is_device_connected
+spice_usbredir_channel_get_type
+spice_util_get_debug
+spice_util_get_version_string
+spice_util_set_debug
+spice_uuid_to_string
+spice_webdav_channel_get_type
diff --git a/src/spice-grabsequence.c b/src/spice-grabsequence.c
new file mode 100644
index 0000000..39adfb0
--- /dev/null
+++ b/src/spice-grabsequence.c
@@ -0,0 +1,163 @@
+/*
+ * GTK VNC Widget
+ *
+ * Copyright (C) 2010 Daniel P. Berrange <dan at berrange.com>
+ *
+ * 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.0 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <gdk/gdk.h>
+
+#include "spice-grabsequence.h"
+
+GType spice_grab_sequence_get_type(void)
+{
+	static GType grab_sequence_type = 0;
+	static volatile gsize grab_sequence_type_volatile;
+
+	if (g_once_init_enter(&grab_sequence_type_volatile)) {
+		grab_sequence_type = g_boxed_type_register_static
+			("SpiceGrabSequence",
+			 (GBoxedCopyFunc)spice_grab_sequence_copy,
+			 (GBoxedFreeFunc)spice_grab_sequence_free);
+		g_once_init_leave(&grab_sequence_type_volatile,
+				  grab_sequence_type);
+	}
+
+	return grab_sequence_type;
+}
+
+
+/**
+ * spice_grab_sequence_new:
+ * @nkeysyms: length of @keysyms
+ * @keysyms: (array length=nkeysyms): the keysym values
+ *
+ * Creates a new grab sequence from a list of keysym values
+ *
+ * Returns: (transfer full): a new grab sequence object
+ */
+SpiceGrabSequence *spice_grab_sequence_new(guint nkeysyms, guint *keysyms)
+{
+	SpiceGrabSequence *sequence;
+
+	sequence = g_slice_new0(SpiceGrabSequence);
+	sequence->nkeysyms = nkeysyms;
+	sequence->keysyms = g_new0(guint, nkeysyms);
+	memcpy(sequence->keysyms, keysyms, sizeof(guint)*nkeysyms);
+
+	return sequence;
+}
+
+
+/**
+ * spice_grab_sequence_new_from_string:
+ * @str: a string of '+' seperated key names (ex: "Control_L+Alt_L")
+ *
+ * Returns: a new #SpiceGrabSequence.
+ **/
+SpiceGrabSequence *spice_grab_sequence_new_from_string(const gchar *str)
+{
+	gchar **keysymstr;
+	int i;
+	SpiceGrabSequence *sequence;
+
+	sequence = g_slice_new0(SpiceGrabSequence);
+
+	keysymstr = g_strsplit(str, "+", 5);
+
+	sequence->nkeysyms = 0;
+	while (keysymstr[sequence->nkeysyms])
+		sequence->nkeysyms++;
+
+	sequence->keysyms = g_new0(guint, sequence->nkeysyms);
+	for (i = 0 ; i < sequence->nkeysyms ; i++) {
+		sequence->keysyms[i] =
+			(guint)gdk_keyval_from_name(keysymstr[i]);
+                if (sequence->keysyms[i] == 0) {
+                        g_critical("Invalid key: %s", keysymstr[i]);
+                }
+        }
+	g_strfreev(keysymstr);
+
+	return sequence;
+
+}
+
+
+/**
+ * spice_grab_sequence_copy:
+ * @sequence: sequence to copy
+ *
+ * Returns: (transfer full): a copy of @sequence
+ **/
+SpiceGrabSequence *spice_grab_sequence_copy(SpiceGrabSequence *srcSequence)
+{
+	SpiceGrabSequence *sequence;
+
+	sequence = g_slice_dup(SpiceGrabSequence, srcSequence);
+	sequence->keysyms = g_new0(guint, srcSequence->nkeysyms);
+	memcpy(sequence->keysyms, srcSequence->keysyms,
+	       sizeof(guint) * sequence->nkeysyms);
+
+	return sequence;
+}
+
+
+/**
+ * spice_grab_sequence_free:
+ * @sequence:
+ *
+ * Free @sequence.
+ **/
+void spice_grab_sequence_free(SpiceGrabSequence *sequence)
+{
+	g_free(sequence->keysyms);
+	g_slice_free(SpiceGrabSequence, sequence);
+}
+
+
+/**
+ * spice_grab_sequence_as_string:
+ * @sequence:
+ *
+ * Returns: (transfer full): a newly allocated string representing the key sequence
+ **/
+gchar *spice_grab_sequence_as_string(SpiceGrabSequence *sequence)
+{
+	GString *str = g_string_new("");
+	int i;
+
+	for (i = 0 ; i < sequence->nkeysyms ; i++) {
+		if (i > 0)
+			g_string_append_c(str, '+');
+		g_string_append(str, gdk_keyval_name(sequence->keysyms[i]));
+	}
+
+	return g_string_free(str, FALSE);
+
+}
+
+
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ *  tab-width: 8
+ * End:
+ */
diff --git a/src/spice-grabsequence.h b/src/spice-grabsequence.h
new file mode 100644
index 0000000..fe58fc1
--- /dev/null
+++ b/src/spice-grabsequence.h
@@ -0,0 +1,61 @@
+/*
+ * GTK VNC Widget
+ *
+ * Copyright (C) 2006  Anthony Liguori <anthony at codemonkey.ws>
+ * Copyright (C) 2009-2010 Daniel P. Berrange <dan at berrange.com>
+ *
+ * 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.0 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+#ifndef SPICE_GRAB_SEQUENCE_H
+#define SPICE_GRAB_SEQUENCE_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_GRAB_SEQUENCE            (spice_grab_sequence_get_type ())
+
+typedef struct _SpiceGrabSequence SpiceGrabSequence;
+
+struct _SpiceGrabSequence {
+        /*< private >*/
+	guint nkeysyms;
+	guint *keysyms;
+
+	/* Do not add fields to this struct */
+};
+
+GType spice_grab_sequence_get_type(void);
+
+SpiceGrabSequence *spice_grab_sequence_new(guint nkeysyms, guint *keysyms);
+SpiceGrabSequence *spice_grab_sequence_new_from_string(const gchar *str);
+SpiceGrabSequence *spice_grab_sequence_copy(SpiceGrabSequence *sequence);
+void spice_grab_sequence_free(SpiceGrabSequence *sequence);
+gchar *spice_grab_sequence_as_string(SpiceGrabSequence *sequence);
+
+
+G_END_DECLS
+
+#endif /* SPICE_GRAB_SEQUENCE_H */
+
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ *  tab-width: 8
+ * End:
+ */
diff --git a/src/spice-gstaudio.c b/src/spice-gstaudio.c
new file mode 100644
index 0000000..1623421
--- /dev/null
+++ b/src/spice-gstaudio.c
@@ -0,0 +1,739 @@
+/* -*- 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 <gst/gst.h>
+#include <gst/app/gstappsrc.h>
+#include <gst/app/gstappsink.h>
+#include <gst/audio/streamvolume.h>
+
+#include "spice-gstaudio.h"
+#include "spice-common.h"
+#include "spice-session.h"
+#include "spice-util.h"
+
+#define SPICE_GSTAUDIO_GET_PRIVATE(obj)                                  \
+    (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_GSTAUDIO, SpiceGstaudioPrivate))
+
+G_DEFINE_TYPE(SpiceGstaudio, spice_gstaudio, SPICE_TYPE_AUDIO)
+
+struct stream {
+    GstElement              *pipe;
+    GstElement              *src;
+    GstElement              *sink;
+    guint                   rate;
+    guint                   channels;
+};
+
+struct _SpiceGstaudioPrivate {
+    SpiceChannel            *pchannel;
+    SpiceChannel            *rchannel;
+    struct stream           playback;
+    struct stream           record;
+    guint                   mmtime_id;
+};
+
+static gboolean connect_channel(SpiceAudio *audio, SpiceChannel *channel);
+static void channel_weak_notified(gpointer data, GObject *where_the_object_was);
+static void spice_gstaudio_get_playback_volume_info_async(SpiceAudio *audio,
+        GCancellable *cancellable, SpiceMainChannel *main_channel,
+        GAsyncReadyCallback callback, gpointer user_data);
+static gboolean spice_gstaudio_get_playback_volume_info_finish(SpiceAudio *audio,
+        GAsyncResult *res, gboolean *mute, guint8 *nchannels, guint16 **volume, GError **error);
+static void spice_gstaudio_get_record_volume_info_async(SpiceAudio *audio,
+        GCancellable *cancellable, SpiceMainChannel *main_channel,
+        GAsyncReadyCallback callback, gpointer user_data);
+static gboolean spice_gstaudio_get_record_volume_info_finish(SpiceAudio *audio,
+        GAsyncResult *res, gboolean *mute, guint8 *nchannels, guint16 **volume, GError **error);
+
+static void spice_gstaudio_finalize(GObject *obj)
+{
+    G_OBJECT_CLASS(spice_gstaudio_parent_class)->finalize(obj);
+}
+
+void stream_dispose(struct stream *s)
+{
+    if (s->pipe) {
+        gst_element_set_state(s->pipe, GST_STATE_NULL);
+        gst_object_unref(s->pipe);
+        s->pipe = NULL;
+    }
+
+    if (s->src) {
+        gst_object_unref(s->src);
+        s->src = NULL;
+    }
+
+    if (s->sink) {
+        gst_object_unref(s->sink);
+        s->sink = NULL;
+    }
+}
+
+static void spice_gstaudio_dispose(GObject *obj)
+{
+    SpiceGstaudio *gstaudio = SPICE_GSTAUDIO(obj);
+    SpiceGstaudioPrivate *p;
+    SPICE_DEBUG("%s", __FUNCTION__);
+    p = gstaudio->priv;
+
+    stream_dispose(&p->playback);
+    stream_dispose(&p->record);
+
+    if (p->pchannel)
+        g_object_weak_unref(G_OBJECT(p->pchannel), channel_weak_notified, gstaudio);
+    p->pchannel = NULL;
+
+    if (p->rchannel)
+        g_object_weak_unref(G_OBJECT(p->rchannel), channel_weak_notified, gstaudio);
+    p->rchannel = NULL;
+
+    if (G_OBJECT_CLASS(spice_gstaudio_parent_class)->dispose)
+        G_OBJECT_CLASS(spice_gstaudio_parent_class)->dispose(obj);
+}
+
+static void spice_gstaudio_init(SpiceGstaudio *gstaudio)
+{
+    gstaudio->priv = SPICE_GSTAUDIO_GET_PRIVATE(gstaudio);
+}
+
+static void spice_gstaudio_class_init(SpiceGstaudioClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+    SpiceAudioClass *audio_class = SPICE_AUDIO_CLASS(klass);
+
+    audio_class->connect_channel = connect_channel;
+    audio_class->get_playback_volume_info_async = spice_gstaudio_get_playback_volume_info_async;
+    audio_class->get_playback_volume_info_finish = spice_gstaudio_get_playback_volume_info_finish;
+    audio_class->get_record_volume_info_async = spice_gstaudio_get_record_volume_info_async;
+    audio_class->get_record_volume_info_finish = spice_gstaudio_get_record_volume_info_finish;
+
+    gobject_class->finalize = spice_gstaudio_finalize;
+    gobject_class->dispose = spice_gstaudio_dispose;
+
+    g_type_class_add_private(klass, sizeof(SpiceGstaudioPrivate));
+}
+
+static GstFlowReturn record_new_buffer(GstAppSink *appsink, gpointer data)
+{
+    SpiceGstaudio *gstaudio = data;
+    SpiceGstaudioPrivate *p = gstaudio->priv;
+    GstMessage *msg;
+
+    g_return_val_if_fail(p != NULL, GST_FLOW_ERROR);
+
+    msg = gst_message_new_application(GST_OBJECT(p->record.pipe),
+                                      gst_structure_new_empty ("new-sample"));
+    gst_element_post_message(p->record.pipe, msg);
+    return GST_FLOW_OK;
+}
+
+static void record_stop(SpiceGstaudio *gstaudio)
+{
+    SpiceGstaudioPrivate *p = gstaudio->priv;
+
+    SPICE_DEBUG("%s", __FUNCTION__);
+    if (p->record.pipe)
+        gst_element_set_state(p->record.pipe, GST_STATE_READY);
+}
+
+static gboolean record_bus_cb(GstBus *bus, GstMessage *msg, gpointer data)
+{
+    SpiceGstaudio *gstaudio = data;
+    SpiceGstaudioPrivate *p = gstaudio->priv;
+
+    g_return_val_if_fail(p != NULL, FALSE);
+
+    switch (GST_MESSAGE_TYPE(msg)) {
+    case GST_MESSAGE_APPLICATION: {
+        GstSample *s;
+        GstBuffer *buffer;
+        GstMapInfo mapping;
+
+        s = gst_app_sink_pull_sample(GST_APP_SINK(p->record.sink));
+        if (!s) {
+            if (!gst_app_sink_is_eos(GST_APP_SINK(p->record.sink)))
+                g_warning("eos not reached, but can't pull new sample");
+            return TRUE;
+        }
+
+        buffer = gst_sample_get_buffer(s);
+        if (!buffer) {
+            if (!gst_app_sink_is_eos(GST_APP_SINK(p->record.sink)))
+                g_warning("eos not reached, but can't pull new buffer");
+            return TRUE;
+        }
+        if (!gst_buffer_map(buffer, &mapping, GST_MAP_READ)) {
+            return TRUE;
+        }
+
+        spice_record_send_data(SPICE_RECORD_CHANNEL(p->rchannel),
+                               /* FIXME: server side doesn't care about ts?
+                                  what is the unit? ms apparently */
+                               mapping.data, mapping.size, 0);
+        gst_buffer_unmap(buffer, &mapping);
+        gst_sample_unref(s);
+        break;
+    }
+    default:
+        break;
+    }
+
+    return TRUE;
+}
+
+static void record_start(SpiceRecordChannel *channel, gint format, gint channels,
+                         gint frequency, gpointer data)
+{
+    SpiceGstaudio *gstaudio = data;
+    SpiceGstaudioPrivate *p = gstaudio->priv;
+
+    g_return_if_fail(p != NULL);
+    g_return_if_fail(format == SPICE_AUDIO_FMT_S16);
+
+    if (p->record.pipe &&
+        (p->record.rate != frequency ||
+         p->record.channels != channels)) {
+        record_stop(gstaudio);
+        gst_object_unref(p->record.pipe);
+        p->record.pipe = NULL;
+    }
+
+    if (!p->record.pipe) {
+        GError *error = NULL;
+        GstBus *bus;
+        gchar *audio_caps =
+            g_strdup_printf("audio/x-raw,format=\"S16LE\",channels=%d,rate=%d,"
+                            "layout=interleaved", channels, frequency);
+        gchar *pipeline =
+            g_strdup_printf("autoaudiosrc name=audiosrc ! queue ! audioconvert ! audioresample ! "
+                            "appsink caps=\"%s\" name=appsink", audio_caps);
+
+        p->record.pipe = gst_parse_launch(pipeline, &error);
+        if (error != NULL) {
+            g_warning("Failed to create pipeline: %s", error->message);
+            goto cleanup;
+        }
+
+        bus = gst_pipeline_get_bus(GST_PIPELINE(p->record.pipe));
+        gst_bus_add_watch(bus, record_bus_cb, data);
+        gst_object_unref(GST_OBJECT(bus));
+
+        p->record.src = gst_bin_get_by_name(GST_BIN(p->record.pipe), "audiosrc");
+        p->record.sink = gst_bin_get_by_name(GST_BIN(p->record.pipe), "appsink");
+        p->record.rate = frequency;
+        p->record.channels = channels;
+
+        gst_app_sink_set_emit_signals(GST_APP_SINK(p->record.sink), TRUE);
+        spice_g_signal_connect_object(p->record.sink, "new-sample",
+                                      G_CALLBACK(record_new_buffer), gstaudio, 0);
+
+cleanup:
+        if (error != NULL && p->record.pipe != NULL) {
+            gst_object_unref(p->record.pipe);
+            p->record.pipe = NULL;
+        }
+        g_clear_error(&error);
+        g_free(audio_caps);
+        g_free(pipeline);
+    }
+
+    if (p->record.pipe)
+        gst_element_set_state(p->record.pipe, GST_STATE_PLAYING);
+}
+
+static void playback_stop(SpiceGstaudio *gstaudio)
+{
+    SpiceGstaudioPrivate *p = gstaudio->priv;
+
+    if (p->playback.pipe)
+        gst_element_set_state(p->playback.pipe, GST_STATE_READY);
+    if (p->mmtime_id != 0) {
+        g_source_remove(p->mmtime_id);
+        p->mmtime_id = 0;
+    }
+}
+
+static gboolean update_mmtime_timeout_cb(gpointer data)
+{
+    SpiceGstaudio *gstaudio = data;
+    SpiceGstaudioPrivate *p = gstaudio->priv;
+    GstQuery *q;
+
+    q = gst_query_new_latency();
+    if (gst_element_query(p->playback.pipe, q)) {
+        gboolean live;
+        GstClockTime minlat, maxlat;
+        gst_query_parse_latency(q, &live, &minlat, &maxlat);
+        SPICE_DEBUG("got min latency %" GST_TIME_FORMAT ", max latency %"
+                    GST_TIME_FORMAT ", live %d", GST_TIME_ARGS (minlat),
+                    GST_TIME_ARGS (maxlat), live);
+        spice_playback_channel_set_delay(SPICE_PLAYBACK_CHANNEL(p->pchannel), GST_TIME_AS_MSECONDS(minlat));
+    }
+    gst_query_unref (q);
+
+    return TRUE;
+}
+
+static void playback_start(SpicePlaybackChannel *channel, gint format, gint channels,
+                           gint frequency, gpointer data)
+{
+    SpiceGstaudio *gstaudio = data;
+    SpiceGstaudioPrivate *p = gstaudio->priv;
+
+    g_return_if_fail(p != NULL);
+    g_return_if_fail(format == SPICE_AUDIO_FMT_S16);
+
+    if (p->playback.pipe &&
+        (p->playback.rate != frequency ||
+         p->playback.channels != channels)) {
+        playback_stop(gstaudio);
+        gst_object_unref(p->playback.pipe);
+        p->playback.pipe = NULL;
+    }
+
+    if (!p->playback.pipe) {
+        GError *error = NULL;
+        gchar *audio_caps =
+            g_strdup_printf("audio/x-raw,format=\"S16LE\",channels=%d,rate=%d,"
+                            "layout=interleaved", channels, frequency);
+        gchar *pipeline = g_strdup (g_getenv("SPICE_GST_AUDIOSINK"));
+        if (pipeline == NULL)
+            pipeline = g_strdup_printf("appsrc is-live=1 do-timestamp=0 caps=\"%s\" name=\"appsrc\" ! queue ! "
+                                       "audioconvert ! audioresample ! autoaudiosink name=\"audiosink\"", audio_caps);
+        SPICE_DEBUG("audio pipeline: %s", pipeline);
+        p->playback.pipe = gst_parse_launch(pipeline, &error);
+        if (error != NULL) {
+            g_warning("Failed to create pipeline: %s", error->message);
+            goto cleanup;
+        }
+        p->playback.src = gst_bin_get_by_name(GST_BIN(p->playback.pipe), "appsrc");
+        p->playback.sink = gst_bin_get_by_name(GST_BIN(p->playback.pipe), "audiosink");
+        p->playback.rate = frequency;
+        p->playback.channels = channels;
+
+cleanup:
+        if (error != NULL && p->playback.pipe != NULL) {
+            gst_object_unref(p->playback.pipe);
+            p->playback.pipe = NULL;
+        }
+        g_clear_error(&error);
+        g_free(audio_caps);
+        g_free(pipeline);
+    }
+
+    if (p->playback.pipe)
+        gst_element_set_state(p->playback.pipe, GST_STATE_PLAYING);
+
+    if (p->mmtime_id == 0) {
+        update_mmtime_timeout_cb(gstaudio);
+        p->mmtime_id = g_timeout_add_seconds(1, update_mmtime_timeout_cb, gstaudio);
+    }
+}
+
+static void playback_data(SpicePlaybackChannel *channel,
+                          gpointer *audio, gint size,
+                          gpointer data)
+{
+    SpiceGstaudio *gstaudio = data;
+    SpiceGstaudioPrivate *p = gstaudio->priv;
+    GstBuffer *buf;
+
+    g_return_if_fail(p != NULL);
+
+    audio = g_memdup(audio, size); /* TODO: try to avoid memory copy */
+    buf = gst_buffer_new_wrapped(audio, size);
+    gst_app_src_push_buffer(GST_APP_SRC(p->playback.src), buf);
+}
+
+#define VOLUME_NORMAL 65535
+
+static void playback_volume_changed(GObject *object, GParamSpec *pspec, gpointer data)
+{
+    SpiceGstaudio *gstaudio = data;
+    GstElement *e;
+    guint16 *volume;
+    guint nchannels;
+    SpiceGstaudioPrivate *p = gstaudio->priv;
+    gdouble vol;
+
+    if (!p->playback.sink)
+        return;
+
+    g_object_get(object,
+                 "volume", &volume,
+                 "nchannels", &nchannels,
+                 NULL);
+
+    g_return_if_fail(nchannels > 0);
+
+    vol = 1.0 * volume[0] / VOLUME_NORMAL;
+    SPICE_DEBUG("playback volume changed to %u (%0.2f)", volume[0], 100*vol);
+
+    if (GST_IS_BIN(p->playback.sink))
+        e = gst_bin_get_by_interface(GST_BIN(p->playback.sink), GST_TYPE_STREAM_VOLUME);
+    else
+        e = g_object_ref(p->playback.sink);
+
+    if (GST_IS_STREAM_VOLUME(e))
+        gst_stream_volume_set_volume(GST_STREAM_VOLUME(e), GST_STREAM_VOLUME_FORMAT_CUBIC, vol);
+    else
+        g_object_set(e, "volume", vol, NULL);
+
+    g_object_unref(e);
+}
+
+static void playback_mute_changed(GObject *object, GParamSpec *pspec, gpointer data)
+{
+    SpiceGstaudio *gstaudio = data;
+    SpiceGstaudioPrivate *p = gstaudio->priv;
+    GstElement *e;
+    gboolean mute;
+
+    if (!p->playback.sink)
+        return;
+
+    g_object_get(object, "mute", &mute, NULL);
+    SPICE_DEBUG("playback mute changed to %u", mute);
+
+    if (GST_IS_BIN(p->playback.sink))
+        e = gst_bin_get_by_interface(GST_BIN(p->playback.sink), GST_TYPE_STREAM_VOLUME);
+    else
+        e = g_object_ref(p->playback.sink);
+
+    if (GST_IS_STREAM_VOLUME(e))
+        gst_stream_volume_set_mute(GST_STREAM_VOLUME(e), mute);
+
+    g_object_unref(e);
+}
+
+static void record_volume_changed(GObject *object, GParamSpec *pspec, gpointer data)
+{
+    SpiceGstaudio *gstaudio = data;
+    SpiceGstaudioPrivate *p = gstaudio->priv;
+    GstElement *e;
+    guint16 *volume;
+    guint nchannels;
+    gdouble vol;
+
+    if (!p->record.src)
+        return;
+
+    g_object_get(object,
+                 "volume", &volume,
+                 "nchannels", &nchannels,
+                 NULL);
+
+    g_return_if_fail(nchannels > 0);
+
+    vol = 1.0 * volume[0] / VOLUME_NORMAL;
+    SPICE_DEBUG("record volume changed to %u (%0.2f)", volume[0], 100*vol);
+
+    /* TODO directsoundsrc doesn't support IDirectSoundBuffer_SetVolume */
+    /* TODO pulsesrc doesn't support volume property, it's all coming! */
+
+    if (GST_IS_BIN(p->record.src))
+        e = gst_bin_get_by_interface(GST_BIN(p->record.src), GST_TYPE_STREAM_VOLUME);
+    else
+        e = g_object_ref(p->record.src);
+
+    if (GST_IS_STREAM_VOLUME(e))
+        gst_stream_volume_set_volume(GST_STREAM_VOLUME(e), GST_STREAM_VOLUME_FORMAT_CUBIC, vol);
+    else
+        g_warning("gst lacks volume capabilities on src (TODO)");
+
+    g_object_unref(e);
+}
+
+static void record_mute_changed(GObject *object, GParamSpec *pspec, gpointer data)
+{
+    SpiceGstaudio *gstaudio = data;
+    SpiceGstaudioPrivate *p = gstaudio->priv;
+    GstElement *e;
+    gboolean mute;
+
+    if (!p->record.src)
+        return;
+
+    g_object_get(object, "mute", &mute, NULL);
+    SPICE_DEBUG("record mute changed to %u", mute);
+
+    if (GST_IS_BIN(p->record.src))
+        e = gst_bin_get_by_interface(GST_BIN(p->record.src), GST_TYPE_STREAM_VOLUME);
+    else
+        e = g_object_ref(p->record.src);
+
+    if (GST_IS_STREAM_VOLUME (e))
+        gst_stream_volume_set_mute(GST_STREAM_VOLUME(e), mute);
+    else
+        g_warning("gst lacks mute capabilities on src: %d (TODO)", mute);
+
+    g_object_unref(e);
+}
+
+static void
+channel_weak_notified(gpointer data,
+                      GObject *where_the_object_was)
+{
+    SpiceGstaudio *gstaudio = SPICE_GSTAUDIO(data);
+    SpiceGstaudioPrivate *p = gstaudio->priv;
+
+    if (where_the_object_was == (GObject *)p->pchannel) {
+        SPICE_DEBUG("playback closed");
+        playback_stop(gstaudio);
+        p->pchannel = NULL;
+    } else if (where_the_object_was == (GObject *)p->rchannel) {
+        SPICE_DEBUG("record closed");
+        record_stop(gstaudio);
+        p->rchannel = NULL;
+    }
+}
+
+static gboolean connect_channel(SpiceAudio *audio, SpiceChannel *channel)
+{
+    SpiceGstaudio *gstaudio = SPICE_GSTAUDIO(audio);
+    SpiceGstaudioPrivate *p = gstaudio->priv;
+
+    if (SPICE_IS_PLAYBACK_CHANNEL(channel)) {
+        g_return_val_if_fail(p->pchannel == NULL, FALSE);
+
+        p->pchannel = channel;
+        g_object_weak_ref(G_OBJECT(p->pchannel), channel_weak_notified, audio);
+        spice_g_signal_connect_object(channel, "playback-start",
+                                      G_CALLBACK(playback_start), gstaudio, 0);
+        spice_g_signal_connect_object(channel, "playback-data",
+                                      G_CALLBACK(playback_data), gstaudio, 0);
+        spice_g_signal_connect_object(channel, "playback-stop",
+                                      G_CALLBACK(playback_stop), gstaudio, G_CONNECT_SWAPPED);
+        spice_g_signal_connect_object(channel, "notify::volume",
+                                      G_CALLBACK(playback_volume_changed), gstaudio, 0);
+        spice_g_signal_connect_object(channel, "notify::mute",
+                                      G_CALLBACK(playback_mute_changed), gstaudio, 0);
+
+        return TRUE;
+    }
+
+    if (SPICE_IS_RECORD_CHANNEL(channel)) {
+        g_return_val_if_fail(p->rchannel == NULL, FALSE);
+
+        p->rchannel = channel;
+        g_object_weak_ref(G_OBJECT(p->rchannel), channel_weak_notified, audio);
+        spice_g_signal_connect_object(channel, "record-start",
+                                      G_CALLBACK(record_start), gstaudio, 0);
+        spice_g_signal_connect_object(channel, "record-stop",
+                                      G_CALLBACK(record_stop), gstaudio, G_CONNECT_SWAPPED);
+        spice_g_signal_connect_object(channel, "notify::volume",
+                                      G_CALLBACK(record_volume_changed), gstaudio, 0);
+        spice_g_signal_connect_object(channel, "notify::mute",
+                                      G_CALLBACK(record_mute_changed), gstaudio, 0);
+
+        return TRUE;
+    }
+
+    return FALSE;
+}
+
+SpiceGstaudio *spice_gstaudio_new(SpiceSession *session, GMainContext *context,
+                                  const char *name)
+{
+    SpiceGstaudio *gstaudio;
+
+    gst_init(NULL, NULL);
+    gstaudio = g_object_new(SPICE_TYPE_GSTAUDIO,
+                            "session", session,
+                            "main-context", context,
+                            NULL);
+
+    return gstaudio;
+}
+
+static void spice_gstaudio_get_playback_volume_info_async(SpiceAudio *audio,
+                                                          GCancellable *cancellable,
+                                                          SpiceMainChannel *main_channel,
+                                                          GAsyncReadyCallback callback,
+                                                          gpointer user_data)
+{
+    GSimpleAsyncResult *simple;
+
+    simple = g_simple_async_result_new(G_OBJECT(audio),
+                                       callback,
+                                       user_data,
+                                       spice_gstaudio_get_playback_volume_info_async);
+    g_simple_async_result_set_check_cancellable (simple, cancellable);
+
+    g_simple_async_result_set_op_res_gboolean(simple, TRUE);
+    g_simple_async_result_complete_in_idle(simple);
+}
+
+static gboolean spice_gstaudio_get_playback_volume_info_finish(SpiceAudio *audio,
+                                                               GAsyncResult *res,
+                                                               gboolean *mute,
+                                                               guint8 *nchannels,
+                                                               guint16 **volume,
+                                                               GError **error)
+{
+    SpiceGstaudioPrivate *p = SPICE_GSTAUDIO(audio)->priv;
+    GstElement *e;
+    gboolean lmute;
+    gdouble vol;
+    gboolean fake_channel = FALSE;
+    GSimpleAsyncResult *simple = (GSimpleAsyncResult *) res;
+
+    g_return_val_if_fail(g_simple_async_result_is_valid(res,
+        G_OBJECT(audio), spice_gstaudio_get_playback_volume_info_async), FALSE);
+
+    if (g_simple_async_result_propagate_error(simple, error)) {
+        return FALSE;
+    }
+
+    if (p->playback.sink == NULL || p->playback.channels == 0) {
+        SPICE_DEBUG("PlaybackChannel not created yet, force start");
+        /* In order to get system volume, we start the pipeline */
+        playback_start(NULL, SPICE_AUDIO_FMT_S16, 2, 48000, audio);
+        fake_channel = TRUE;
+    }
+
+    if (GST_IS_BIN(p->playback.sink))
+        e = gst_bin_get_by_interface(GST_BIN(p->playback.sink), GST_TYPE_STREAM_VOLUME);
+    else
+        e = g_object_ref(p->playback.sink);
+
+    if (GST_IS_STREAM_VOLUME(e)) {
+        vol = gst_stream_volume_get_volume(GST_STREAM_VOLUME(e), GST_STREAM_VOLUME_FORMAT_CUBIC);
+        lmute = gst_stream_volume_get_mute(GST_STREAM_VOLUME(e));
+    } else {
+        g_object_get(e,
+                     "volume", &vol,
+                     "mute", &lmute, NULL);
+    }
+    g_object_unref(e);
+
+    if (fake_channel) {
+        SPICE_DEBUG("Stop faked PlaybackChannel");
+        playback_stop(SPICE_GSTAUDIO(audio));
+    }
+
+    if (mute != NULL) {
+        *mute = lmute;
+    }
+
+    if (nchannels != NULL) {
+        *nchannels = p->playback.channels;
+    }
+
+    if (volume != NULL) {
+        gint i;
+        *volume = g_new(guint16, p->playback.channels);
+        for (i = 0; i < p->playback.channels; i++) {
+            (*volume)[i] = (guint16) (vol * VOLUME_NORMAL);
+            SPICE_DEBUG("(playback) volume at %d is %u (%0.2f%%)", i, (*volume)[i], 100*vol);
+        }
+    }
+
+    return g_simple_async_result_get_op_res_gboolean(simple);
+}
+
+static void spice_gstaudio_get_record_volume_info_async(SpiceAudio *audio,
+                                                        GCancellable *cancellable,
+                                                        SpiceMainChannel *main_channel,
+                                                        GAsyncReadyCallback callback,
+                                                        gpointer user_data)
+{
+    GSimpleAsyncResult *simple;
+
+    simple = g_simple_async_result_new(G_OBJECT(audio),
+                                       callback,
+                                       user_data,
+                                       spice_gstaudio_get_record_volume_info_async);
+    g_simple_async_result_set_check_cancellable (simple, cancellable);
+
+    g_simple_async_result_set_op_res_gboolean(simple, TRUE);
+    g_simple_async_result_complete_in_idle(simple);
+}
+
+static gboolean spice_gstaudio_get_record_volume_info_finish(SpiceAudio *audio,
+                                                             GAsyncResult *res,
+                                                             gboolean *mute,
+                                                             guint8 *nchannels,
+                                                             guint16 **volume,
+                                                             GError **error)
+{
+    SpiceGstaudioPrivate *p = SPICE_GSTAUDIO(audio)->priv;
+    GstElement *e;
+    gboolean lmute;
+    gdouble vol;
+    gboolean fake_channel = FALSE;
+    GSimpleAsyncResult *simple = (GSimpleAsyncResult *) res;
+
+    g_return_val_if_fail(g_simple_async_result_is_valid(res,
+        G_OBJECT(audio), spice_gstaudio_get_record_volume_info_async), FALSE);
+
+    if (g_simple_async_result_propagate_error(simple, error)) {
+        /* set out args that should have new alloc'ed memory to NULL */
+        if (volume != NULL) {
+            *volume = NULL;
+        }
+        return FALSE;
+    }
+
+    if (p->record.src == NULL || p->record.channels == 0) {
+        SPICE_DEBUG("RecordChannel not created yet, force start");
+        /* In order to get system volume, we start the pipeline */
+        record_start(NULL, SPICE_AUDIO_FMT_S16, 2, 48000, audio);
+        fake_channel = TRUE;
+    }
+
+    if (GST_IS_BIN(p->record.src))
+        e = gst_bin_get_by_interface(GST_BIN(p->record.src), GST_TYPE_STREAM_VOLUME);
+    else
+        e = g_object_ref(p->record.src);
+
+    if (GST_IS_STREAM_VOLUME(e)) {
+        vol = gst_stream_volume_get_volume(GST_STREAM_VOLUME(e), GST_STREAM_VOLUME_FORMAT_CUBIC);
+        lmute = gst_stream_volume_get_mute(GST_STREAM_VOLUME(e));
+    } else {
+        g_object_get(e,
+                     "volume", &vol,
+                     "mute", &lmute, NULL);
+    }
+    g_object_unref(e);
+
+    if (fake_channel) {
+        SPICE_DEBUG("Stop faked RecordChannel");
+        record_stop(SPICE_GSTAUDIO(audio));
+    }
+
+    if (mute != NULL) {
+        *mute = lmute;
+    }
+
+    if (nchannels != NULL) {
+        *nchannels = p->record.channels;
+    }
+
+    if (volume != NULL) {
+        gint i;
+        *volume = g_new(guint16, p->record.channels);
+        for (i = 0; i < p->record.channels; i++) {
+            (*volume)[i] = (guint16) (vol * VOLUME_NORMAL);
+            SPICE_DEBUG("(record) volume at %d is %u (%0.2f%%)", i, (*volume)[i], 100*vol);
+        }
+    }
+
+    return g_simple_async_result_get_op_res_gboolean(simple);
+}
diff --git a/src/spice-gstaudio.h b/src/spice-gstaudio.h
new file mode 100644
index 0000000..b605f1c
--- /dev/null
+++ b/src/spice-gstaudio.h
@@ -0,0 +1,56 @@
+/* -*- 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_CLIENT_GSTAUDIO_H__
+#define __SPICE_CLIENT_GSTAUDIO_H__
+
+#include "spice-client.h"
+#include "spice-audio.h"
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_GSTAUDIO            (spice_gstaudio_get_type())
+#define SPICE_GSTAUDIO(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), SPICE_TYPE_GSTAUDIO, SpiceGstaudio))
+#define SPICE_GSTAUDIO_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), SPICE_TYPE_GSTAUDIO, SpiceGstaudioClass))
+#define SPICE_IS_GSTAUDIO(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), SPICE_TYPE_GSTAUDIO))
+#define SPICE_IS_GSTAUDIO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SPICE_TYPE_GSTAUDIO))
+#define SPICE_GSTAUDIO_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), SPICE_TYPE_GSTAUDIO, SpiceGstaudioClass))
+
+
+typedef struct _SpiceGstaudio SpiceGstaudio;
+typedef struct _SpiceGstaudioClass SpiceGstaudioClass;
+typedef struct _SpiceGstaudioPrivate SpiceGstaudioPrivate;
+
+struct _SpiceGstaudio {
+    SpiceAudio parent;
+    SpiceGstaudioPrivate *priv;
+    /* Do not add fields to this struct */
+};
+
+struct _SpiceGstaudioClass {
+    SpiceAudioClass parent_class;
+    /* Do not add fields to this struct */
+};
+
+GType spice_gstaudio_get_type(void);
+
+SpiceGstaudio *spice_gstaudio_new(SpiceSession *session,
+                                  GMainContext *context, const char *name);
+
+G_END_DECLS
+
+#endif /* __SPICE_CLIENT_GSTAUDIO_H__ */
diff --git a/src/spice-gtk-session-priv.h b/src/spice-gtk-session-priv.h
new file mode 100644
index 0000000..91304b2
--- /dev/null
+++ b/src/spice-gtk-session-priv.h
@@ -0,0 +1,34 @@
+/* -*- 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/>.
+*/
+#ifndef __SPICE_CLIENT_GTK_SESSION_PRIV_H__
+#define __SPICE_CLIENT_GTK_SESSION_PRIV_H__
+
+#include "spice-gtk-session.h"
+
+G_BEGIN_DECLS
+
+void spice_gtk_session_request_auto_usbredir(SpiceGtkSession *self,
+                                             gboolean state);
+gboolean spice_gtk_session_get_read_only(SpiceGtkSession *self);
+void spice_gtk_session_sync_keyboard_modifiers(SpiceGtkSession *self);
+void spice_gtk_session_set_pointer_grabbed(SpiceGtkSession *self, gboolean grabbed);
+gboolean spice_gtk_session_get_pointer_grabbed(SpiceGtkSession *self);
+
+G_END_DECLS
+
+#endif /* __SPICE_CLIENT_GTK_SESSION_PRIV_H__ */
diff --git a/src/spice-gtk-session.c b/src/spice-gtk-session.c
new file mode 100644
index 0000000..0937434
--- /dev/null
+++ b/src/spice-gtk-session.c
@@ -0,0 +1,1229 @@
+/* -*- 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>
+
+#if HAVE_X11_XKBLIB_H
+#include <X11/XKBlib.h>
+#include <gdk/gdkx.h>
+#endif
+#ifdef GDK_WINDOWING_X11
+#include <X11/Xlib.h>
+#include <gdk/gdkx.h>
+#endif
+#ifdef G_OS_WIN32
+#include <windows.h>
+#include <gdk/gdkwin32.h>
+#ifndef MAPVK_VK_TO_VSC /* may be undefined in older mingw-headers */
+#define MAPVK_VK_TO_VSC 0
+#endif
+#endif
+
+#include <gtk/gtk.h>
+#include <spice/vd_agent.h>
+#include "desktop-integration.h"
+#include "gtk-compat.h"
+#include "spice-common.h"
+#include "spice-gtk-session.h"
+#include "spice-gtk-session-priv.h"
+#include "spice-session-priv.h"
+#include "spice-util-priv.h"
+#include "spice-channel-priv.h"
+
+#define CLIPBOARD_LAST (VD_AGENT_CLIPBOARD_SELECTION_SECONDARY + 1)
+
+struct _SpiceGtkSessionPrivate {
+    SpiceSession            *session;
+    /* Clipboard related */
+    gboolean                auto_clipboard_enable;
+    SpiceMainChannel        *main;
+    GtkClipboard            *clipboard;
+    GtkClipboard            *clipboard_primary;
+    GtkTargetEntry          *clip_targets[CLIPBOARD_LAST];
+    guint                   nclip_targets[CLIPBOARD_LAST];
+    gboolean                clip_hasdata[CLIPBOARD_LAST];
+    gboolean                clip_grabbed[CLIPBOARD_LAST];
+    gboolean                clipboard_by_guest[CLIPBOARD_LAST];
+    /* auto-usbredir related */
+    gboolean                auto_usbredir_enable;
+    int                     auto_usbredir_reqs;
+    gboolean                pointer_grabbed;
+};
+
+/**
+ * SECTION:spice-gtk-session
+ * @short_description: handles GTK connection details
+ * @title: Spice GTK Session
+ * @section_id:
+ * @see_also: #SpiceSession, and the GTK widget #SpiceDisplay
+ * @stability: Stable
+ * @include: spice-gtk-session.h
+ *
+ * The #SpiceGtkSession class is the spice-client-gtk counter part of
+ * #SpiceSession. It contains functionality which should be handled per
+ * session rather then per #SpiceDisplay (one session can have multiple
+ * displays), but which cannot live in #SpiceSession as it depends on
+ * GTK. For example the clipboard functionality.
+ *
+ * There should always be a 1:1 relation between #SpiceGtkSession objects
+ * and #SpiceSession objects. Therefor there is no spice_gtk_session_new,
+ * instead there is spice_gtk_session_get() which ensures this 1:1 relation.
+ *
+ * Client and guest clipboards will be shared automatically if
+ * #SpiceGtkSession:auto-clipboard is set to #TRUE. Alternatively, you
+ * can send / receive clipboard data from client to guest with
+ * spice_gtk_session_copy_to_guest() / spice_gtk_session_paste_from_guest().
+ */
+
+/* ------------------------------------------------------------------ */
+/* Prototypes for private functions */
+static void clipboard_owner_change(GtkClipboard *clipboard,
+                                   GdkEventOwnerChange *event,
+                                   gpointer user_data);
+static void channel_new(SpiceSession *session, SpiceChannel *channel,
+                        gpointer user_data);
+static void channel_destroy(SpiceSession *session, SpiceChannel *channel,
+                            gpointer user_data);
+static gboolean read_only(SpiceGtkSession *self);
+
+/* ------------------------------------------------------------------ */
+/* gobject glue                                                       */
+
+#define SPICE_GTK_SESSION_GET_PRIVATE(obj) \
+    (G_TYPE_INSTANCE_GET_PRIVATE ((obj), SPICE_TYPE_GTK_SESSION, SpiceGtkSessionPrivate))
+
+G_DEFINE_TYPE (SpiceGtkSession, spice_gtk_session, G_TYPE_OBJECT);
+
+/* Properties */
+enum {
+    PROP_0,
+    PROP_SESSION,
+    PROP_AUTO_CLIPBOARD,
+    PROP_AUTO_USBREDIR,
+    PROP_POINTER_GRABBED,
+};
+
+static guint32 get_keyboard_lock_modifiers(void)
+{
+    guint32 modifiers = 0;
+#if GTK_CHECK_VERSION(3,18,0)
+    GdkKeymap *keyboard = gdk_keymap_get_default();
+
+    if (gdk_keymap_get_caps_lock_state(keyboard)) {
+        modifiers |= SPICE_INPUTS_CAPS_LOCK;
+    }
+
+    if (gdk_keymap_get_num_lock_state(keyboard)) {
+        modifiers |= SPICE_INPUTS_NUM_LOCK;
+    }
+
+    if (gdk_keymap_get_scroll_lock_state(keyboard)) {
+        modifiers |= SPICE_INPUTS_SCROLL_LOCK;
+    }
+#else
+#if HAVE_X11_XKBLIB_H
+    Display *x_display = NULL;
+    XKeyboardState keyboard_state;
+
+    GdkScreen *screen = gdk_screen_get_default();
+    if (!GDK_IS_X11_DISPLAY(gdk_screen_get_display(screen))) {
+        SPICE_DEBUG("FIXME: gtk backend is not X11");
+        return 0;
+    }
+
+    x_display = GDK_SCREEN_XDISPLAY(screen);
+    XGetKeyboardControl(x_display, &keyboard_state);
+
+    if (keyboard_state.led_mask & 0x01) {
+        modifiers |= SPICE_INPUTS_CAPS_LOCK;
+    }
+    if (keyboard_state.led_mask & 0x02) {
+        modifiers |= SPICE_INPUTS_NUM_LOCK;
+    }
+    if (keyboard_state.led_mask & 0x04) {
+        modifiers |= SPICE_INPUTS_SCROLL_LOCK;
+    }
+#elif defined(G_OS_WIN32)
+    if (GetKeyState(VK_CAPITAL) & 1) {
+        modifiers |= SPICE_INPUTS_CAPS_LOCK;
+    }
+    if (GetKeyState(VK_NUMLOCK) & 1) {
+        modifiers |= SPICE_INPUTS_NUM_LOCK;
+    }
+    if (GetKeyState(VK_SCROLL) & 1) {
+        modifiers |= SPICE_INPUTS_SCROLL_LOCK;
+    }
+#else
+    g_warning("get_keyboard_lock_modifiers not implemented");
+#endif // HAVE_X11_XKBLIB_H
+#endif // GTK_CHECK_VERSION(3,18,0)
+    return modifiers;
+}
+
+static void spice_gtk_session_sync_keyboard_modifiers_for_channel(SpiceGtkSession *self,
+                                                                  SpiceInputsChannel* inputs,
+                                                                  gboolean force)
+{
+    gint guest_modifiers = 0, client_modifiers = 0;
+
+    g_return_if_fail(SPICE_IS_INPUTS_CHANNEL(inputs));
+
+    g_object_get(inputs, "key-modifiers", &guest_modifiers, NULL);
+    client_modifiers = get_keyboard_lock_modifiers();
+
+    if (force || client_modifiers != guest_modifiers) {
+        CHANNEL_DEBUG(inputs, "client_modifiers:0x%x, guest_modifiers:0x%x",
+                      client_modifiers, guest_modifiers);
+        spice_inputs_set_key_locks(inputs, client_modifiers);
+    }
+}
+
+static void keymap_modifiers_changed(GdkKeymap *keymap, gpointer data)
+{
+    SpiceGtkSession *self = data;
+
+    spice_gtk_session_sync_keyboard_modifiers(self);
+}
+
+static void guest_modifiers_changed(SpiceInputsChannel *inputs, gpointer data)
+{
+    SpiceGtkSession *self = data;
+
+    spice_gtk_session_sync_keyboard_modifiers_for_channel(self, inputs, FALSE);
+}
+
+static void spice_gtk_session_init(SpiceGtkSession *self)
+{
+    SpiceGtkSessionPrivate *s;
+    GdkKeymap *keymap = gdk_keymap_get_default();
+
+    s = self->priv = SPICE_GTK_SESSION_GET_PRIVATE(self);
+
+    s->clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
+    g_signal_connect(G_OBJECT(s->clipboard), "owner-change",
+                     G_CALLBACK(clipboard_owner_change), self);
+    s->clipboard_primary = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
+    g_signal_connect(G_OBJECT(s->clipboard_primary), "owner-change",
+                     G_CALLBACK(clipboard_owner_change), self);
+    spice_g_signal_connect_object(keymap, "state-changed",
+                                  G_CALLBACK(keymap_modifiers_changed), self, 0);
+}
+
+static GObject *
+spice_gtk_session_constructor(GType                  gtype,
+                              guint                  n_properties,
+                              GObjectConstructParam *properties)
+{
+    GObject *obj;
+    SpiceGtkSession *self;
+    SpiceGtkSessionPrivate *s;
+    GList *list;
+    GList *it;
+
+    {
+        /* Always chain up to the parent constructor */
+        GObjectClass *parent_class;
+        parent_class = G_OBJECT_CLASS(spice_gtk_session_parent_class);
+        obj = parent_class->constructor(gtype, n_properties, properties);
+    }
+
+    self = SPICE_GTK_SESSION(obj);
+    s = self->priv;
+    if (!s->session)
+        g_error("SpiceGtKSession constructed without a session");
+
+    g_signal_connect(s->session, "channel-new",
+                     G_CALLBACK(channel_new), self);
+    g_signal_connect(s->session, "channel-destroy",
+                     G_CALLBACK(channel_destroy), self);
+    list = spice_session_get_channels(s->session);
+    for (it = g_list_first(list); it != NULL; it = g_list_next(it)) {
+        channel_new(s->session, it->data, (gpointer*)self);
+    }
+    g_list_free(list);
+
+    return obj;
+}
+
+static void spice_gtk_session_dispose(GObject *gobject)
+{
+    SpiceGtkSession *self = SPICE_GTK_SESSION(gobject);
+    SpiceGtkSessionPrivate *s = self->priv;
+
+    /* release stuff */
+    if (s->clipboard) {
+        g_signal_handlers_disconnect_by_func(s->clipboard,
+                G_CALLBACK(clipboard_owner_change), self);
+        s->clipboard = NULL;
+    }
+
+    if (s->clipboard_primary) {
+        g_signal_handlers_disconnect_by_func(s->clipboard_primary,
+                G_CALLBACK(clipboard_owner_change), self);
+        s->clipboard_primary = NULL;
+    }
+
+    if (s->session) {
+        g_signal_handlers_disconnect_by_func(s->session,
+                                             G_CALLBACK(channel_new),
+                                             self);
+        g_signal_handlers_disconnect_by_func(s->session,
+                                             G_CALLBACK(channel_destroy),
+                                             self);
+        s->session = NULL;
+    }
+
+    /* Chain up to the parent class */
+    if (G_OBJECT_CLASS(spice_gtk_session_parent_class)->dispose)
+        G_OBJECT_CLASS(spice_gtk_session_parent_class)->dispose(gobject);
+}
+
+static void spice_gtk_session_finalize(GObject *gobject)
+{
+    SpiceGtkSession *self = SPICE_GTK_SESSION(gobject);
+    SpiceGtkSessionPrivate *s = self->priv;
+    int i;
+
+    /* release stuff */
+    for (i = 0; i < CLIPBOARD_LAST; ++i) {
+        g_free(s->clip_targets[i]);
+        s->clip_targets[i] = NULL;
+    }
+
+    /* Chain up to the parent class */
+    if (G_OBJECT_CLASS(spice_gtk_session_parent_class)->finalize)
+        G_OBJECT_CLASS(spice_gtk_session_parent_class)->finalize(gobject);
+}
+
+static void spice_gtk_session_get_property(GObject    *gobject,
+                                           guint       prop_id,
+                                           GValue     *value,
+                                           GParamSpec *pspec)
+{
+    SpiceGtkSession *self = SPICE_GTK_SESSION(gobject);
+    SpiceGtkSessionPrivate *s = self->priv;
+
+    switch (prop_id) {
+    case PROP_SESSION:
+        g_value_set_object(value, s->session);
+	break;
+    case PROP_AUTO_CLIPBOARD:
+        g_value_set_boolean(value, s->auto_clipboard_enable);
+        break;
+    case PROP_AUTO_USBREDIR:
+        g_value_set_boolean(value, s->auto_usbredir_enable);
+        break;
+    case PROP_POINTER_GRABBED:
+        g_value_set_boolean(value, s->pointer_grabbed);
+        break;
+    default:
+	G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
+	break;
+    }
+}
+
+static void spice_gtk_session_set_property(GObject      *gobject,
+                                           guint         prop_id,
+                                           const GValue *value,
+                                           GParamSpec   *pspec)
+{
+    SpiceGtkSession *self = SPICE_GTK_SESSION(gobject);
+    SpiceGtkSessionPrivate *s = self->priv;
+
+    switch (prop_id) {
+    case PROP_SESSION:
+        s->session = g_value_get_object(value);
+        break;
+    case PROP_AUTO_CLIPBOARD:
+        s->auto_clipboard_enable = g_value_get_boolean(value);
+        break;
+    case PROP_AUTO_USBREDIR: {
+        SpiceDesktopIntegration *desktop_int;
+        gboolean orig_value = s->auto_usbredir_enable;
+
+        s->auto_usbredir_enable = g_value_get_boolean(value);
+        if (s->auto_usbredir_enable == orig_value)
+            break;
+
+        if (s->auto_usbredir_reqs) {
+            SpiceUsbDeviceManager *manager =
+                spice_usb_device_manager_get(s->session, NULL);
+
+            if (!manager)
+                break;
+
+            g_object_set(manager, "auto-connect", s->auto_usbredir_enable,
+                         NULL);
+
+            desktop_int = spice_desktop_integration_get(s->session);
+            if (s->auto_usbredir_enable)
+                spice_desktop_integration_inhibit_automount(desktop_int);
+            else
+                spice_desktop_integration_uninhibit_automount(desktop_int);
+        }
+        break;
+    }
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
+        break;
+    }
+}
+
+static void spice_gtk_session_class_init(SpiceGtkSessionClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+
+    gobject_class->constructor  = spice_gtk_session_constructor;
+    gobject_class->dispose      = spice_gtk_session_dispose;
+    gobject_class->finalize     = spice_gtk_session_finalize;
+    gobject_class->get_property = spice_gtk_session_get_property;
+    gobject_class->set_property = spice_gtk_session_set_property;
+
+    /**
+     * SpiceGtkSession:session:
+     *
+     * #SpiceSession this #SpiceGtkSession is associated with
+     *
+     * Since: 0.8
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_SESSION,
+         g_param_spec_object("session",
+                             "Session",
+                             "SpiceSession",
+                             SPICE_TYPE_SESSION,
+                             G_PARAM_READWRITE |
+                             G_PARAM_CONSTRUCT_ONLY |
+                             G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceGtkSession:auto-clipboard:
+     *
+     * When this is true the clipboard gets automatically shared between host
+     * and guest.
+     *
+     * Since: 0.8
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_AUTO_CLIPBOARD,
+         g_param_spec_boolean("auto-clipboard",
+                              "Auto clipboard",
+                              "Automatically relay clipboard changes between "
+                              "host and guest.",
+                              TRUE,
+                              G_PARAM_READWRITE |
+                              G_PARAM_CONSTRUCT |
+                              G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceGtkSession:auto-usbredir:
+     *
+     * Automatically redirect newly plugged in USB devices. Note the auto
+     * redirection only happens when a #SpiceDisplay associated with the
+     * session had keyboard focus.
+     *
+     * Since: 0.8
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_AUTO_USBREDIR,
+         g_param_spec_boolean("auto-usbredir",
+                              "Auto USB Redirection",
+                              "Automatically redirect newly plugged in USB"
+                              "Devices to the guest.",
+                              FALSE,
+                              G_PARAM_READWRITE |
+                              G_PARAM_CONSTRUCT |
+                              G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceGtkSession:pointer-grabbed:
+     *
+     * Returns %TRUE if the pointer is currently grabbed by this session.
+     *
+     * Since: 0.27
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_POINTER_GRABBED,
+         g_param_spec_boolean("pointer-grabbed",
+                              "Pointer grabbed",
+                              "Whether the pointer is grabbed",
+                              FALSE,
+                              G_PARAM_READABLE |
+                              G_PARAM_STATIC_STRINGS));
+
+    g_type_class_add_private(klass, sizeof(SpiceGtkSessionPrivate));
+}
+
+/* ---------------------------------------------------------------- */
+/* private functions (clipboard related)                            */
+
+static GtkClipboard* get_clipboard_from_selection(SpiceGtkSessionPrivate *s,
+                                                  guint selection)
+{
+    if (selection == VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD) {
+        return s->clipboard;
+    } else if (selection == VD_AGENT_CLIPBOARD_SELECTION_PRIMARY) {
+        return s->clipboard_primary;
+    } else {
+        g_warning("Unhandled clipboard selection: %d", selection);
+        return NULL;
+    }
+}
+
+static gint get_selection_from_clipboard(SpiceGtkSessionPrivate *s,
+                                         GtkClipboard* cb)
+{
+    if (cb == s->clipboard) {
+        return VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD;
+    } else if (cb == s->clipboard_primary) {
+        return VD_AGENT_CLIPBOARD_SELECTION_PRIMARY;
+    } else {
+        g_warning("Unhandled clipboard");
+        return -1;
+    }
+}
+
+static const struct {
+    const char  *xatom;
+    uint32_t    vdagent;
+} atom2agent[] = {
+    {
+        .vdagent = VD_AGENT_CLIPBOARD_UTF8_TEXT,
+        .xatom   = "UTF8_STRING",
+    },{
+        .vdagent = VD_AGENT_CLIPBOARD_UTF8_TEXT,
+        .xatom   = "text/plain;charset=utf-8"
+    },{
+        .vdagent = VD_AGENT_CLIPBOARD_UTF8_TEXT,
+        .xatom   = "STRING"
+    },{
+        .vdagent = VD_AGENT_CLIPBOARD_UTF8_TEXT,
+        .xatom   = "TEXT"
+    },{
+        .vdagent = VD_AGENT_CLIPBOARD_UTF8_TEXT,
+        .xatom   = "text/plain"
+    },{
+        .vdagent = VD_AGENT_CLIPBOARD_IMAGE_PNG,
+        .xatom   = "image/png"
+    },{
+        .vdagent = VD_AGENT_CLIPBOARD_IMAGE_BMP,
+        .xatom   = "image/bmp"
+    },{
+        .vdagent = VD_AGENT_CLIPBOARD_IMAGE_BMP,
+        .xatom   = "image/x-bmp"
+    },{
+        .vdagent = VD_AGENT_CLIPBOARD_IMAGE_BMP,
+        .xatom   = "image/x-MS-bmp"
+    },{
+        .vdagent = VD_AGENT_CLIPBOARD_IMAGE_BMP,
+        .xatom   = "image/x-win-bitmap"
+    },{
+        .vdagent = VD_AGENT_CLIPBOARD_IMAGE_TIFF,
+        .xatom   = "image/tiff"
+    },{
+        .vdagent = VD_AGENT_CLIPBOARD_IMAGE_JPG,
+        .xatom   = "image/jpeg"
+    }
+};
+
+typedef struct _WeakRef {
+    GObject *object;
+} WeakRef;
+
+static void weak_notify_cb(WeakRef *weakref, GObject *object)
+{
+    weakref->object = NULL;
+}
+
+static WeakRef* weak_ref(GObject *object)
+{
+    WeakRef *weakref = g_new(WeakRef, 1);
+
+    g_object_weak_ref(object, (GWeakNotify)weak_notify_cb, weakref);
+    weakref->object = object;
+
+    return weakref;
+}
+
+static void weak_unref(WeakRef* weakref)
+{
+    if (weakref->object)
+        g_object_weak_unref(weakref->object, (GWeakNotify)weak_notify_cb, weakref);
+
+    g_free(weakref);
+}
+
+static void clipboard_get_targets(GtkClipboard *clipboard,
+                                  GdkAtom *atoms,
+                                  gint n_atoms,
+                                  gpointer user_data)
+{
+    WeakRef *weakref = user_data;
+    SpiceGtkSession *self = (SpiceGtkSession*)weakref->object;
+    weak_unref(weakref);
+
+    if (self == NULL)
+        return;
+
+    g_return_if_fail(SPICE_IS_GTK_SESSION(self));
+
+    SpiceGtkSessionPrivate *s = self->priv;
+    guint32 types[SPICE_N_ELEMENTS(atom2agent)];
+    char *name;
+    int a, m, t;
+    int selection;
+
+    if (s->main == NULL)
+        return;
+
+    selection = get_selection_from_clipboard(s, clipboard);
+    g_return_if_fail(selection != -1);
+
+    SPICE_DEBUG("%s:", __FUNCTION__);
+    if (spice_util_get_debug()) {
+        for (a = 0; a < n_atoms; a++) {
+            name = gdk_atom_name(atoms[a]);
+            SPICE_DEBUG(" \"%s\"", name);
+            g_free(name);
+        }
+    }
+
+    memset(types, 0, sizeof(types));
+    for (a = 0; a < n_atoms; a++) {
+        name = gdk_atom_name(atoms[a]);
+        for (m = 0; m < SPICE_N_ELEMENTS(atom2agent); m++) {
+            if (strcasecmp(name, atom2agent[m].xatom) != 0) {
+                continue;
+            }
+            /* found match */
+            for (t = 0; t < SPICE_N_ELEMENTS(atom2agent); t++) {
+                if (types[t] == atom2agent[m].vdagent) {
+                    /* type already in list */
+                    break;
+                }
+                if (types[t] == 0) {
+                    /* add type to empty slot */
+                    types[t] = atom2agent[m].vdagent;
+                    break;
+                }
+            }
+            break;
+        }
+        g_free(name);
+    }
+    for (t = 0; t < SPICE_N_ELEMENTS(atom2agent); t++) {
+        if (types[t] == 0) {
+            break;
+        }
+    }
+    if (!s->clip_grabbed[selection] && t > 0) {
+        s->clip_grabbed[selection] = TRUE;
+
+        if (spice_main_agent_test_capability(s->main, VD_AGENT_CAP_CLIPBOARD_BY_DEMAND))
+            spice_main_clipboard_selection_grab(s->main, selection, types, t);
+        /* Sending a grab causes the agent to do an impicit release */
+        s->nclip_targets[selection] = 0;
+    }
+}
+
+static void clipboard_owner_change(GtkClipboard        *clipboard,
+                                   GdkEventOwnerChange *event,
+                                   gpointer            user_data)
+{
+    g_return_if_fail(SPICE_IS_GTK_SESSION(user_data));
+
+    SpiceGtkSession *self = user_data;
+    SpiceGtkSessionPrivate *s = self->priv;
+    int selection;
+
+    selection = get_selection_from_clipboard(s, clipboard);
+    g_return_if_fail(selection != -1);
+
+    if (s->main == NULL)
+        return;
+
+    if (s->clip_grabbed[selection]) {
+        s->clip_grabbed[selection] = FALSE;
+        if (spice_main_agent_test_capability(s->main, VD_AGENT_CAP_CLIPBOARD_BY_DEMAND))
+            spice_main_clipboard_selection_release(s->main, selection);
+    }
+
+    switch (event->reason) {
+    case GDK_OWNER_CHANGE_NEW_OWNER:
+        if (gtk_clipboard_get_owner(clipboard) == G_OBJECT(self))
+            break;
+
+        s->clipboard_by_guest[selection] = FALSE;
+        s->clip_hasdata[selection] = TRUE;
+        if (s->auto_clipboard_enable && !read_only(self))
+            gtk_clipboard_request_targets(clipboard, clipboard_get_targets,
+                                          weak_ref(G_OBJECT(self)));
+        break;
+    default:
+        s->clip_hasdata[selection] = FALSE;
+        break;
+    }
+}
+
+typedef struct
+{
+    SpiceGtkSession *self;
+    GMainLoop *loop;
+    GtkSelectionData *selection_data;
+    guint info;
+    guint selection;
+} RunInfo;
+
+static void clipboard_got_from_guest(SpiceMainChannel *main, guint selection,
+                                     guint type, const guchar *data, guint size,
+                                     gpointer user_data)
+{
+    RunInfo *ri = user_data;
+    SpiceGtkSessionPrivate *s = ri->self->priv;
+    gchar *conv = NULL;
+
+    g_return_if_fail(selection == ri->selection);
+
+    SPICE_DEBUG("clipboard got data");
+
+    if (atom2agent[ri->info].vdagent == VD_AGENT_CLIPBOARD_UTF8_TEXT) {
+        /* on windows, gtk+ would already convert to LF endings, but
+           not on unix */
+        if (spice_main_agent_test_capability(s->main, VD_AGENT_CAP_GUEST_LINEEND_CRLF)) {
+            GError *err = NULL;
+
+            conv = spice_dos2unix((gchar*)data, size, &err);
+            if (err) {
+                g_warning("Failed to convert text line ending: %s", err->message);
+                g_clear_error(&err);
+                goto end;
+            }
+
+            size = strlen(conv);
+        }
+
+        gtk_selection_data_set_text(ri->selection_data, conv ?: (gchar*)data, size);
+    } else {
+        gtk_selection_data_set(ri->selection_data,
+            gdk_atom_intern_static_string(atom2agent[ri->info].xatom),
+            8, data, size);
+    }
+
+end:
+    if (g_main_loop_is_running (ri->loop))
+        g_main_loop_quit (ri->loop);
+
+    g_free(conv);
+}
+
+static void clipboard_agent_connected(RunInfo *ri)
+{
+    g_warning("agent status changed, cancel clipboard request");
+
+    if (g_main_loop_is_running(ri->loop))
+        g_main_loop_quit(ri->loop);
+}
+
+static void clipboard_get(GtkClipboard *clipboard,
+                          GtkSelectionData *selection_data,
+                          guint info, gpointer user_data)
+{
+    g_return_if_fail(SPICE_IS_GTK_SESSION(user_data));
+
+    RunInfo ri = { NULL, };
+    SpiceGtkSession *self = user_data;
+    SpiceGtkSessionPrivate *s = self->priv;
+    gboolean agent_connected = FALSE;
+    gulong clipboard_handler;
+    gulong agent_handler;
+    int selection;
+
+    SPICE_DEBUG("clipboard get");
+
+    selection = get_selection_from_clipboard(s, clipboard);
+    g_return_if_fail(selection != -1);
+    g_return_if_fail(info < SPICE_N_ELEMENTS(atom2agent));
+    g_return_if_fail(s->main != NULL);
+
+    ri.selection_data = selection_data;
+    ri.info = info;
+    ri.loop = g_main_loop_new(NULL, FALSE);
+    ri.selection = selection;
+    ri.self = self;
+
+    clipboard_handler = g_signal_connect(s->main, "main-clipboard-selection",
+                                         G_CALLBACK(clipboard_got_from_guest),
+                                         &ri);
+    agent_handler = g_signal_connect_swapped(s->main, "notify::agent-connected",
+                                     G_CALLBACK(clipboard_agent_connected),
+                                     &ri);
+
+    spice_main_clipboard_selection_request(s->main, selection,
+                                           atom2agent[info].vdagent);
+
+
+    g_object_get(s->main, "agent-connected", &agent_connected, NULL);
+    if (!agent_connected) {
+        SPICE_DEBUG("canceled clipboard_get, before running loop");
+        goto cleanup;
+    }
+
+    /* apparently, this is needed to avoid dead-lock, from
+       gtk_dialog_run */
+    gdk_threads_leave();
+    g_main_loop_run(ri.loop);
+    gdk_threads_enter();
+
+cleanup:
+    g_main_loop_unref(ri.loop);
+    ri.loop = NULL;
+    g_signal_handler_disconnect(s->main, clipboard_handler);
+    g_signal_handler_disconnect(s->main, agent_handler);
+}
+
+static void clipboard_clear(GtkClipboard *clipboard, gpointer user_data)
+{
+    SPICE_DEBUG("clipboard_clear");
+    /* We watch for clipboard ownership changes and act on those, so we
+       don't need to do anything here */
+}
+
+static gboolean clipboard_grab(SpiceMainChannel *main, guint selection,
+                               guint32* types, guint32 ntypes,
+                               gpointer user_data)
+{
+    g_return_val_if_fail(SPICE_IS_GTK_SESSION(user_data), FALSE);
+
+    SpiceGtkSession *self = user_data;
+    SpiceGtkSessionPrivate *s = self->priv;
+    GtkTargetEntry targets[SPICE_N_ELEMENTS(atom2agent)];
+    gboolean target_selected[SPICE_N_ELEMENTS(atom2agent)] = { FALSE, };
+    gboolean found;
+    GtkClipboard* cb;
+    int m, n, i;
+
+    cb = get_clipboard_from_selection(s, selection);
+    g_return_val_if_fail(cb != NULL, FALSE);
+
+    i = 0;
+    for (n = 0; n < ntypes; ++n) {
+        found = FALSE;
+        for (m = 0; m < SPICE_N_ELEMENTS(atom2agent); m++) {
+            if (atom2agent[m].vdagent == types[n] && !target_selected[m]) {
+                found = TRUE;
+                g_return_val_if_fail(i < SPICE_N_ELEMENTS(atom2agent), FALSE);
+                targets[i].target = (gchar*)atom2agent[m].xatom;
+                targets[i].info = m;
+                target_selected[m] = TRUE;
+                i += 1;
+            }
+        }
+        if (!found) {
+            g_warning("clipboard: couldn't find a matching type for: %d",
+                      types[n]);
+        }
+    }
+
+    g_free(s->clip_targets[selection]);
+    s->nclip_targets[selection] = i;
+    s->clip_targets[selection] = g_memdup(targets, sizeof(GtkTargetEntry) * i);
+    /* Receiving a grab implies we've released our own grab */
+    s->clip_grabbed[selection] = FALSE;
+
+    if (read_only(self) ||
+        !s->auto_clipboard_enable ||
+        s->nclip_targets[selection] == 0)
+        goto skip_grab_clipboard;
+
+    if (!gtk_clipboard_set_with_owner(cb, targets, i,
+                                      clipboard_get, clipboard_clear, G_OBJECT(self))) {
+        g_warning("clipboard grab failed");
+        return FALSE;
+    }
+    s->clipboard_by_guest[selection] = TRUE;
+    s->clip_hasdata[selection] = FALSE;
+
+skip_grab_clipboard:
+    return TRUE;
+}
+
+static gboolean check_clipboard_size_limits(SpiceGtkSession *session,
+                                            gint clipboard_len)
+{
+    int max_clipboard;
+
+    g_object_get(session->priv->main, "max-clipboard", &max_clipboard, NULL);
+    if (max_clipboard != -1 && clipboard_len > max_clipboard) {
+        g_warning("discarded clipboard of size %d (max: %d)",
+                  clipboard_len, max_clipboard);
+        return FALSE;
+    } else if (clipboard_len <= 0) {
+        SPICE_DEBUG("discarding empty clipboard");
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+static void clipboard_received_cb(GtkClipboard *clipboard,
+                                  GtkSelectionData *selection_data,
+                                  gpointer user_data)
+{
+    WeakRef *weakref = user_data;
+    SpiceGtkSession *self = (SpiceGtkSession*)weakref->object;
+    weak_unref(weakref);
+
+    if (self == NULL)
+        return;
+
+    g_return_if_fail(SPICE_IS_GTK_SESSION(self));
+
+    SpiceGtkSessionPrivate *s = self->priv;
+    gint len = 0, m;
+    guint32 type = VD_AGENT_CLIPBOARD_NONE;
+    gchar* name;
+    GdkAtom atom;
+    int selection;
+
+    selection = get_selection_from_clipboard(s, clipboard);
+    g_return_if_fail(selection != -1);
+
+    len = gtk_selection_data_get_length(selection_data);
+    if (!check_clipboard_size_limits(self, len)) {
+        return;
+    } else {
+        atom = gtk_selection_data_get_data_type(selection_data);
+        name = gdk_atom_name(atom);
+        for (m = 0; m < SPICE_N_ELEMENTS(atom2agent); m++) {
+            if (strcasecmp(name, atom2agent[m].xatom) == 0) {
+                break;
+            }
+        }
+
+        if (m >= SPICE_N_ELEMENTS(atom2agent)) {
+            g_warning("clipboard_received for unsupported type: %s", name);
+        } else {
+            type = atom2agent[m].vdagent;
+        }
+
+        g_free(name);
+    }
+
+    const guchar *data = gtk_selection_data_get_data(selection_data);
+    gpointer conv = NULL;
+
+    /* gtk+ internal utf8 newline is always LF, even on windows */
+    if (type == VD_AGENT_CLIPBOARD_UTF8_TEXT) {
+        if (spice_main_agent_test_capability(s->main, VD_AGENT_CAP_GUEST_LINEEND_CRLF)) {
+            GError *err = NULL;
+
+            conv = spice_unix2dos((gchar*)data, len, &err);
+            if (err) {
+                g_warning("Failed to convert text line ending: %s", err->message);
+                g_clear_error(&err);
+                return;
+            }
+
+            len = strlen(conv);
+        } else {
+            /* On Windows, with some versions of gtk+, GtkSelectionData::length
+             * will include the final '\0'. When a string with this trailing '\0'
+             * is pasted in some linux applications, it will be pasted as <NIL> or
+             * as an invisible character, which is unwanted. Ensure the length we
+             * send to the agent does not include any trailing '\0'
+             * This is gtk+ bug https://bugzilla.gnome.org/show_bug.cgi?id=734670
+             */
+            len = strlen((const char *)data);
+        }
+        if (!check_clipboard_size_limits(self, len)) {
+            g_free(conv);
+            return;
+        }
+    }
+
+    spice_main_clipboard_selection_notify(s->main, selection, type,
+                                          conv ?: data, len);
+    g_free(conv);
+}
+
+static gboolean clipboard_request(SpiceMainChannel *main, guint selection,
+                                  guint type, gpointer user_data)
+{
+    g_return_val_if_fail(SPICE_IS_GTK_SESSION(user_data), FALSE);
+
+    SpiceGtkSession *self = user_data;
+    SpiceGtkSessionPrivate *s = self->priv;
+    GdkAtom atom;
+    GtkClipboard* cb;
+    int m;
+
+    g_return_val_if_fail(s->clipboard_by_guest[selection] == FALSE, FALSE);
+    g_return_val_if_fail(s->clip_grabbed[selection], FALSE);
+
+    if (read_only(self))
+        return FALSE;
+
+    cb = get_clipboard_from_selection(s, selection);
+    g_return_val_if_fail(cb != NULL, FALSE);
+
+    for (m = 0; m < SPICE_N_ELEMENTS(atom2agent); m++) {
+        if (atom2agent[m].vdagent == type)
+            break;
+    }
+
+    g_return_val_if_fail(m < SPICE_N_ELEMENTS(atom2agent), FALSE);
+
+    atom = gdk_atom_intern_static_string(atom2agent[m].xatom);
+    gtk_clipboard_request_contents(cb, atom, clipboard_received_cb,
+                                   weak_ref(G_OBJECT(self)));
+
+    return TRUE;
+}
+
+static void clipboard_release(SpiceMainChannel *main, guint selection,
+                              gpointer user_data)
+{
+    g_return_if_fail(SPICE_IS_GTK_SESSION(user_data));
+
+    SpiceGtkSession *self = user_data;
+    SpiceGtkSessionPrivate *s = self->priv;
+    GtkClipboard* clipboard = get_clipboard_from_selection(s, selection);
+
+    if (!clipboard)
+        return;
+
+    s->nclip_targets[selection] = 0;
+
+    if (!s->clipboard_by_guest[selection])
+        return;
+    gtk_clipboard_clear(clipboard);
+    s->clipboard_by_guest[selection] = FALSE;
+}
+
+static void channel_new(SpiceSession *session, SpiceChannel *channel,
+                        gpointer user_data)
+{
+    g_return_if_fail(SPICE_IS_GTK_SESSION(user_data));
+
+    SpiceGtkSession *self = user_data;
+    SpiceGtkSessionPrivate *s = self->priv;
+
+    if (SPICE_IS_MAIN_CHANNEL(channel)) {
+        SPICE_DEBUG("Changing main channel from %p to %p", s->main, channel);
+        s->main = SPICE_MAIN_CHANNEL(channel);
+        g_signal_connect(channel, "main-clipboard-selection-grab",
+                         G_CALLBACK(clipboard_grab), self);
+        g_signal_connect(channel, "main-clipboard-selection-request",
+                         G_CALLBACK(clipboard_request), self);
+        g_signal_connect(channel, "main-clipboard-selection-release",
+                         G_CALLBACK(clipboard_release), self);
+    }
+    if (SPICE_IS_INPUTS_CHANNEL(channel)) {
+        spice_g_signal_connect_object(channel, "inputs-modifiers",
+                                      G_CALLBACK(guest_modifiers_changed), self, 0);
+        spice_gtk_session_sync_keyboard_modifiers_for_channel(self, SPICE_INPUTS_CHANNEL(channel), TRUE);
+    }
+}
+
+static void channel_destroy(SpiceSession *session, SpiceChannel *channel,
+                            gpointer user_data)
+{
+    g_return_if_fail(SPICE_IS_GTK_SESSION(user_data));
+
+    SpiceGtkSession *self = user_data;
+    SpiceGtkSessionPrivate *s = self->priv;
+    guint i;
+
+    if (SPICE_IS_MAIN_CHANNEL(channel) && SPICE_MAIN_CHANNEL(channel) == s->main) {
+        s->main = NULL;
+        for (i = 0; i < CLIPBOARD_LAST; ++i) {
+            if (s->clipboard_by_guest[i]) {
+                GtkClipboard *cb = get_clipboard_from_selection(s, i);
+                if (cb)
+                    gtk_clipboard_clear(cb);
+                s->clipboard_by_guest[i] = FALSE;
+            }
+            s->clip_grabbed[i] = FALSE;
+            s->nclip_targets[i] = 0;
+        }
+    }
+}
+
+static gboolean read_only(SpiceGtkSession *self)
+{
+    return spice_session_get_read_only(self->priv->session);
+}
+
+/* ---------------------------------------------------------------- */
+/* private functions (usbredir related)                             */
+G_GNUC_INTERNAL
+void spice_gtk_session_request_auto_usbredir(SpiceGtkSession *self,
+                                             gboolean state)
+{
+    g_return_if_fail(SPICE_IS_GTK_SESSION(self));
+
+    SpiceGtkSessionPrivate *s = self->priv;
+    SpiceDesktopIntegration *desktop_int;
+    SpiceUsbDeviceManager *manager;
+
+    if (state) {
+        s->auto_usbredir_reqs++;
+        if (s->auto_usbredir_reqs != 1)
+            return;
+    } else {
+        g_return_if_fail(s->auto_usbredir_reqs > 0);
+        s->auto_usbredir_reqs--;
+        if (s->auto_usbredir_reqs != 0)
+            return;
+    }
+
+    if (!s->auto_usbredir_enable)
+        return;
+
+    manager = spice_usb_device_manager_get(s->session, NULL);
+    if (!manager)
+        return;
+
+    g_object_set(manager, "auto-connect", state, NULL);
+
+    desktop_int = spice_desktop_integration_get(s->session);
+    if (state)
+        spice_desktop_integration_inhibit_automount(desktop_int);
+    else
+        spice_desktop_integration_uninhibit_automount(desktop_int);
+}
+
+/* ------------------------------------------------------------------ */
+/* public functions                                                   */
+
+/**
+ * spice_gtk_session_get:
+ * @session: #SpiceSession for which to get the #SpiceGtkSession
+ *
+ * Gets the #SpiceGtkSession associated with the passed in #SpiceSession.
+ * A new #SpiceGtkSession instance will be created the first time this
+ * function is called for a certain #SpiceSession.
+ *
+ * Note that this function returns a weak reference, which should not be used
+ * after the #SpiceSession itself has been unref-ed by the caller.
+ *
+ * Returns: (transfer none): a weak reference to the #SpiceGtkSession associated with the passed in #SpiceSession
+ *
+ * Since 0.8
+ **/
+SpiceGtkSession *spice_gtk_session_get(SpiceSession *session)
+{
+    g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
+
+    SpiceGtkSession *self;
+    static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
+
+    g_static_mutex_lock(&mutex);
+    self = g_object_get_data(G_OBJECT(session), "spice-gtk-session");
+    if (self == NULL) {
+        self = g_object_new(SPICE_TYPE_GTK_SESSION, "session", session, NULL);
+        g_object_set_data_full(G_OBJECT(session), "spice-gtk-session", self, g_object_unref);
+    }
+    g_static_mutex_unlock(&mutex);
+
+    return SPICE_GTK_SESSION(self);
+}
+
+/**
+ * spice_gtk_session_copy_to_guest:
+ * @self:
+ *
+ * Copy client-side clipboard to guest clipboard.
+ *
+ * Since 0.8
+ **/
+void spice_gtk_session_copy_to_guest(SpiceGtkSession *self)
+{
+    g_return_if_fail(SPICE_IS_GTK_SESSION(self));
+    g_return_if_fail(read_only(self) == FALSE);
+
+    SpiceGtkSessionPrivate *s = self->priv;
+    int selection = VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD;
+
+    if (s->clip_hasdata[selection] && !s->clip_grabbed[selection]) {
+        gtk_clipboard_request_targets(s->clipboard, clipboard_get_targets,
+                                      weak_ref(G_OBJECT(self)));
+    }
+}
+
+/**
+ * spice_gtk_session_paste_from_guest:
+ * @self:
+ *
+ * Copy guest clipboard to client-side clipboard.
+ *
+ * Since 0.8
+ **/
+void spice_gtk_session_paste_from_guest(SpiceGtkSession *self)
+{
+    g_return_if_fail(SPICE_IS_GTK_SESSION(self));
+    g_return_if_fail(read_only(self) == FALSE);
+
+    SpiceGtkSessionPrivate *s = self->priv;
+    int selection = VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD;
+
+    if (s->nclip_targets[selection] == 0) {
+        g_warning("Guest clipboard is not available.");
+        return;
+    }
+
+    if (!gtk_clipboard_set_with_owner(s->clipboard, s->clip_targets[selection], s->nclip_targets[selection],
+                                      clipboard_get, clipboard_clear, G_OBJECT(self))) {
+        g_warning("Clipboard grab failed");
+        return;
+    }
+    s->clipboard_by_guest[selection] = TRUE;
+    s->clip_hasdata[selection] = FALSE;
+}
+
+G_GNUC_INTERNAL
+void spice_gtk_session_sync_keyboard_modifiers(SpiceGtkSession *self)
+{
+    GList *l = NULL, *channels = spice_session_get_channels(self->priv->session);
+
+    for (l = channels; l != NULL; l = l->next) {
+        if (SPICE_IS_INPUTS_CHANNEL(l->data)) {
+            SpiceInputsChannel *inputs = SPICE_INPUTS_CHANNEL(l->data);
+            spice_gtk_session_sync_keyboard_modifiers_for_channel(self, inputs, TRUE);
+        }
+    }
+    g_list_free(channels);
+}
+
+G_GNUC_INTERNAL
+void spice_gtk_session_set_pointer_grabbed(SpiceGtkSession *self, gboolean grabbed)
+{
+    g_return_if_fail(SPICE_IS_GTK_SESSION(self));
+
+    self->priv->pointer_grabbed = grabbed;
+    g_object_notify(G_OBJECT(self), "pointer-grabbed");
+}
+
+G_GNUC_INTERNAL
+gboolean spice_gtk_session_get_pointer_grabbed(SpiceGtkSession *self)
+{
+    g_return_val_if_fail(SPICE_IS_GTK_SESSION(self), FALSE);
+
+    return self->priv->pointer_grabbed;
+}
diff --git a/src/spice-gtk-session.h b/src/spice-gtk-session.h
new file mode 100644
index 0000000..3b4eac6
--- /dev/null
+++ b/src/spice-gtk-session.h
@@ -0,0 +1,65 @@
+/* -*- 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/>.
+*/
+#ifndef __SPICE_CLIENT_GTK_SESSION_H__
+#define __SPICE_CLIENT_GTK_SESSION_H__
+
+#include "spice-client.h"
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_GTK_SESSION            (spice_gtk_session_get_type ())
+#define SPICE_GTK_SESSION(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), SPICE_TYPE_GTK_SESSION, SpiceGtkSession))
+#define SPICE_GTK_SESSION_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SPICE_TYPE_GTK_SESSION, SpiceGtkSessionClass))
+#define SPICE_IS_GTK_SESSION(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SPICE_TYPE_GTK_SESSION))
+#define SPICE_IS_GTK_SESSION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SPICE_TYPE_GTK_SESSION))
+#define SPICE_GTK_SESSION_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SPICE_TYPE_GTK_SESSION, SpiceGtkSessionClass))
+
+typedef struct _SpiceGtkSession SpiceGtkSession;
+typedef struct _SpiceGtkSessionClass SpiceGtkSessionClass;
+typedef struct _SpiceGtkSessionPrivate SpiceGtkSessionPrivate;
+
+struct _SpiceGtkSession
+{
+    GObject parent;
+    SpiceGtkSessionPrivate *priv;
+    /* Do not add fields to this struct */
+};
+
+struct _SpiceGtkSessionClass
+{
+    GObjectClass parent_class;
+
+    /* signals */
+
+    /*< private >*/
+    /*
+     * If adding fields to this struct, remove corresponding
+     * amount of padding to avoid changing overall struct size
+     */
+    gchar _spice_reserved[SPICE_RESERVED_PADDING];
+};
+
+GType spice_gtk_session_get_type(void);
+
+SpiceGtkSession *spice_gtk_session_get(SpiceSession *session);
+void spice_gtk_session_copy_to_guest(SpiceGtkSession *self);
+void spice_gtk_session_paste_from_guest(SpiceGtkSession *self);
+
+G_END_DECLS
+
+#endif /* __SPICE_CLIENT_GTK_SESSION_H__ */
diff --git a/src/spice-gtk-sym-file b/src/spice-gtk-sym-file
new file mode 100644
index 0000000..1574e07
--- /dev/null
+++ b/src/spice-gtk-sym-file
@@ -0,0 +1,23 @@
+spice_display_copy_to_guest
+spice_display_get_grab_keys
+spice_display_get_pixbuf
+spice_display_get_type
+spice_display_key_event_get_type
+spice_display_mouse_ungrab
+spice_display_new
+spice_display_new_with_monitor
+spice_display_paste_from_guest
+spice_display_send_keys
+spice_display_set_grab_keys
+spice_grab_sequence_as_string
+spice_grab_sequence_copy
+spice_grab_sequence_free
+spice_grab_sequence_get_type
+spice_grab_sequence_new
+spice_grab_sequence_new_from_string
+spice_gtk_session_copy_to_guest
+spice_gtk_session_get
+spice_gtk_session_get_type
+spice_gtk_session_paste_from_guest
+spice_usb_device_widget_get_type
+spice_usb_device_widget_new
diff --git a/src/spice-marshal.txt b/src/spice-marshal.txt
new file mode 100644
index 0000000..9c76054
--- /dev/null
+++ b/src/spice-marshal.txt
@@ -0,0 +1,14 @@
+VOID:INT,INT
+VOID:INT,INT,INT
+VOID:INT,INT,INT,INT
+VOID:INT,INT,INT,INT,POINTER
+VOID:INT,INT,INT,INT,INT,POINTER
+VOID:POINTER,INT
+BOOLEAN:POINTER,UINT
+BOOLEAN:UINT
+VOID:UINT,POINTER,UINT
+VOID:UINT,UINT,POINTER,UINT
+BOOLEAN:UINT,POINTER,UINT
+BOOLEAN:UINT,UINT
+VOID:OBJECT,OBJECT
+VOID:BOXED,BOXED
diff --git a/src/spice-option.c b/src/spice-option.c
new file mode 100644
index 0000000..958e03c
--- /dev/null
+++ b/src/spice-option.c
@@ -0,0 +1,284 @@
+/* -*- 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 <stdlib.h>
+#include <glib-object.h>
+#include <glib/gi18n.h>
+#include "glib-compat.h"
+#include "spice-session.h"
+#include "spice-util.h"
+#include "spice-channel-priv.h"
+#include "usb-device-manager.h"
+
+static GStrv disable_effects = NULL;
+static gint color_depth = 0;
+static char *ca_file = NULL;
+static char *host_subject = NULL;
+static char *smartcard_db = NULL;
+static char *smartcard_certificates = NULL;
+static char *usbredir_auto_redirect_filter = NULL;
+static char *usbredir_redirect_on_connect = NULL;
+static gboolean smartcard = FALSE;
+static gboolean disable_audio = FALSE;
+static gboolean disable_usbredir = FALSE;
+static gint cache_size = 0;
+static gint glz_window_size = 0;
+static gchar *secure_channels = NULL;
+static gchar *shared_dir = NULL;
+
+G_GNUC_NORETURN
+static void option_version(void)
+{
+    g_print(PACKAGE_STRING "\n");
+    exit(0);
+}
+
+static gboolean option_debug(void)
+{
+    spice_util_set_debug(TRUE);
+    return TRUE;
+}
+
+static gboolean parse_color_depth(const gchar *option_name, const gchar *value,
+                                  gpointer data, GError **error)
+{
+    unsigned long parsed_depth;
+    char *end;
+
+    if (option_name == NULL) {
+        g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, _("missing color depth, must be 16 or 32"));
+        return FALSE;
+    }
+
+    parsed_depth = strtoul(value, &end, 0);
+    if (*end != '\0')
+        goto error;
+
+    if ((parsed_depth != 16) && (parsed_depth != 32))
+        goto error;
+
+    color_depth = parsed_depth;
+
+    return TRUE;
+
+error:
+    g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, _("invalid color depth (%s), must be 16 or 32"), value);
+    return FALSE;
+}
+
+static gboolean parse_disable_effects(const gchar *option_name, const gchar *value,
+                                      gpointer data, GError **error)
+{
+    GStrv it;
+
+    disable_effects = g_strsplit(value, ",", -1);
+    for (it = disable_effects; *it != NULL; it++) {
+        if ((g_strcmp0(*it, "wallpaper") != 0)
+             && (g_strcmp0(*it, "font-smooth") != 0)
+             && (g_strcmp0(*it, "animation") != 0)
+             && (g_strcmp0(*it, "all") != 0)) {
+            /* Translators: do not translate 'wallpaper', 'font-smooth',
+             * 'animation', 'all' as the user must use these values with the
+             * --spice-disable-effects command line option
+             */
+            g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
+                    _("invalid effect name (%s), must be 'wallpaper', 'font-smooth', 'animation' or 'all'"), *it);
+            g_strfreev(disable_effects);
+            disable_effects = NULL;
+            return FALSE;
+        }
+    }
+
+    return TRUE;
+}
+
+static gboolean parse_secure_channels(const gchar *option_name, const gchar *value,
+                                      gpointer data, GError **error)
+{
+    gint i;
+    gchar **channels = g_strsplit(value, ",", -1);
+
+    g_return_val_if_fail(channels != NULL, FALSE);
+
+    for (i = 0; channels[i]; i++) {
+        if (g_strcmp0(channels[i], "all") == 0)
+            continue;
+
+        if (spice_channel_string_to_type(channels[i]) == -1) {
+            gchar *supported = spice_channel_supported_string();
+            g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
+                        _("invalid channel name (%s), valid names: all, %s"),
+                        channels[i], supported);
+            g_free(supported);
+            return FALSE;
+        }
+    }
+
+    g_strfreev(channels);
+
+    secure_channels = g_strdup(value);
+
+    return TRUE;
+}
+
+
+static gboolean parse_usbredir_filter(const gchar *option_name,
+                                      const gchar *value,
+                                      gpointer data, GError **error)
+
+{
+    g_warning("--spice-usbredir-filter is deprecated, please use --spice-usbredir-auto-redirect-filter instead");
+    g_free(usbredir_auto_redirect_filter);
+    usbredir_auto_redirect_filter = g_strdup(value);
+    return TRUE;
+}
+
+
+/**
+ * spice_get_option_group: (skip)
+ *
+ * Returns: (transfer full): a #GOptionGroup for the commandline
+ * arguments specific to Spice.  You have to call
+ * spice_set_session_option() after to set the options on a
+ * #SpiceSession.
+ **/
+GOptionGroup* spice_get_option_group(void)
+{
+    const GOptionEntry entries[] = {
+        { "spice-secure-channels", '\0', 0, G_OPTION_ARG_CALLBACK, parse_secure_channels,
+          N_("Force the specified channels to be secured"), "<main,display,inputs,...,all>" },
+        { "spice-disable-effects", '\0', 0, G_OPTION_ARG_CALLBACK, parse_disable_effects,
+          N_("Disable guest display effects"), "<wallpaper,font-smooth,animation,all>" },
+        { "spice-color-depth", '\0', 0, G_OPTION_ARG_CALLBACK, parse_color_depth,
+          N_("Guest display color depth"), "<16,32>" },
+        { "spice-ca-file", '\0', 0, G_OPTION_ARG_FILENAME, &ca_file,
+          N_("Truststore file for secure connections"), N_("<file>") },
+        { "spice-host-subject", '\0', 0, G_OPTION_ARG_STRING, &host_subject,
+          N_("Subject of the host certificate (field=value pairs separated by commas)"), N_("<host-subject>") },
+        { "spice-disable-audio", '\0', 0, G_OPTION_ARG_NONE, &disable_audio,
+          N_("Disable audio support"), NULL },
+        { "spice-smartcard", '\0', 0, G_OPTION_ARG_NONE, &smartcard,
+          N_("Enable smartcard support"), NULL },
+        { "spice-smartcard-certificates", '\0', 0, G_OPTION_ARG_STRING, &smartcard_certificates,
+          N_("Certificates to use for software smartcards (field=values separated by commas)"), N_("<certificates>") },
+        { "spice-smartcard-db", '\0', 0, G_OPTION_ARG_STRING, &smartcard_db,
+          N_("Path to the local certificate database to use for software smartcard certificates"), N_("<certificate-db>") },
+        { "spice-disable-usbredir", '\0', 0, G_OPTION_ARG_NONE, &disable_usbredir,
+          N_("Disable USB redirection support"), NULL },
+        /* Backward compats version of spice-usbredir-auto-redirect-filter */
+        { "spice-usbredir-filter", '\0', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, parse_usbredir_filter,
+          NULL, NULL },
+        { "spice-usbredir-auto-redirect-filter", '\0', 0, G_OPTION_ARG_STRING, &usbredir_auto_redirect_filter,
+          N_("Filter selecting USB devices to be auto-redirected when plugged in"), N_("<filter-string>") },
+        { "spice-usbredir-redirect-on-connect", '\0', 0, G_OPTION_ARG_STRING, &usbredir_redirect_on_connect,
+          N_("Filter selecting USB devices to redirect on connect"), N_("<filter-string>") },
+        { "spice-cache-size", '\0', 0, G_OPTION_ARG_INT, &cache_size,
+          N_("Image cache size"), N_("<bytes>") },
+        { "spice-glz-window-size", '\0', 0, G_OPTION_ARG_INT, &glz_window_size,
+          N_("Glz compression history size"), N_("<bytes>") },
+        { "spice-shared-dir", '\0', 0, G_OPTION_ARG_FILENAME, &shared_dir,
+          N_("Shared directory"), N_("<dir>") },
+
+        { "spice-debug", '\0', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, option_debug,
+          N_("Enable Spice-GTK debugging"), NULL },
+        { "spice-gtk-version", '\0', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, option_version,
+          N_("Display Spice-GTK version information"), NULL },
+        { NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL }
+    };
+    GOptionGroup *grp;
+
+    grp = g_option_group_new("spice", _("Spice Options:"), _("Show Spice Options"), NULL, NULL);
+    g_option_group_add_entries(grp, entries);
+
+    return grp;
+}
+
+/**
+ * spice_set_session_option:
+ * @session: a #SpiceSession to set option upon
+ *
+ * Set various properties on @session, according to the commandline
+ * arguments given to spice_get_option_group() option group.
+ **/
+void spice_set_session_option(SpiceSession *session)
+{
+    g_return_if_fail(SPICE_IS_SESSION(session));
+
+    if (ca_file == NULL) {
+        const char *homedir = g_getenv("HOME");
+        if (!homedir)
+            homedir = g_get_home_dir();
+        ca_file = g_build_filename(homedir, ".spicec", "spice_truststore.pem", NULL);
+        if (!g_file_test(ca_file, G_FILE_TEST_IS_REGULAR))
+            g_clear_pointer(&ca_file, g_free);
+    }
+
+    if (disable_effects) {
+        g_object_set(session, "disable-effects", disable_effects, NULL);
+    }
+
+    if (secure_channels) {
+        GStrv channels;
+        channels = g_strsplit(secure_channels, ",", -1);
+        if (channels)
+            g_object_set(session, "secure-channels", channels, NULL);
+        g_strfreev(channels);
+    }
+
+    if (color_depth)
+        g_object_set(session, "color-depth", color_depth, NULL);
+    if (ca_file)
+        g_object_set(session, "ca-file", ca_file, NULL);
+    if (host_subject)
+        g_object_set(session, "cert-subject", host_subject, NULL);
+    if (smartcard) {
+        g_object_set(session, "enable-smartcard", smartcard, NULL);
+        if (smartcard_certificates) {
+            GStrv certs_strv;
+            certs_strv = g_strsplit(smartcard_certificates, ",", -1);
+            if (certs_strv)
+                g_object_set(session, "smartcard-certificates", certs_strv, NULL);
+            g_strfreev(certs_strv);
+        }
+        if (smartcard_db)
+            g_object_set(session, "smartcard-db", smartcard_db, NULL);
+    }
+    if (usbredir_auto_redirect_filter) {
+        SpiceUsbDeviceManager *m = spice_usb_device_manager_get(session, NULL);
+        if (m)
+            g_object_set(m, "auto-connect-filter",
+                         usbredir_auto_redirect_filter, NULL);
+    }
+    if (usbredir_redirect_on_connect) {
+        SpiceUsbDeviceManager *m = spice_usb_device_manager_get(session, NULL);
+        if (m)
+            g_object_set(m, "redirect-on-connect",
+                         usbredir_redirect_on_connect, NULL);
+    }
+    if (disable_usbredir)
+        g_object_set(session, "enable-usbredir", FALSE, NULL);
+    if (disable_audio)
+        g_object_set(session, "enable-audio", FALSE, NULL);
+    if (cache_size)
+        g_object_set(session, "cache-size", cache_size, NULL);
+    if (glz_window_size)
+        g_object_set(session, "glz-window-size", glz_window_size, NULL);
+    if (shared_dir)
+        g_object_set(session, "shared-dir", shared_dir, NULL);
+}
diff --git a/src/spice-option.h b/src/spice-option.h
new file mode 100644
index 0000000..ce24f65
--- /dev/null
+++ b/src/spice-option.h
@@ -0,0 +1,31 @@
+/* -*- 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_OPTION_H
+#define SPICE_OPTION_H
+
+#include <glib.h>
+#include "spice-session.h"
+
+G_BEGIN_DECLS
+
+GOptionGroup* spice_get_option_group(void);
+void spice_set_session_option(SpiceSession *session);
+
+G_END_DECLS
+
+#endif /* SPICE_OPTION_H */
diff --git a/src/spice-pulse.c b/src/spice-pulse.c
new file mode 100644
index 0000000..22db893
--- /dev/null
+++ b/src/spice-pulse.c
@@ -0,0 +1,1354 @@
+/* -*- 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-pulse.h"
+#include "spice-common.h"
+#include "spice-session-priv.h"
+#include "spice-channel-priv.h"
+#include "spice-util-priv.h"
+#include "glib-compat.h"
+
+#include <pulse/glib-mainloop.h>
+#include <pulse/pulseaudio.h>
+#include <pulse/ext-stream-restore.h>
+
+#define SPICE_PULSE_GET_PRIVATE(obj)                                  \
+    (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_PULSE, SpicePulsePrivate))
+
+struct async_task {
+    SpicePulse                 *pulse;
+    SpiceMainChannel           *main_channel;
+    GSimpleAsyncResult         *res;
+    GAsyncReadyCallback        callback;
+    gpointer                   user_data;
+    gboolean                   is_playback;
+    pa_operation               *pa_op;
+    gulong                     cancel_id;
+    GCancellable               *cancellable;
+};
+
+struct stream {
+    pa_sample_spec             spec;
+    pa_stream                  *stream;
+    int                        state;
+    pa_operation               *uncork_op;
+    pa_operation               *cork_op;
+    gboolean                   started;
+    guint                      num_underflow;
+    gboolean                   info_updated;
+    gchar                      *name;
+    pa_ext_stream_restore_info info;
+};
+
+struct _SpicePulsePrivate {
+    SpiceChannel            *pchannel;
+    SpiceChannel            *rchannel;
+
+    pa_glib_mainloop        *mainloop;
+    pa_context              *context;
+    int                     state;
+    struct stream           playback;
+    struct stream           record;
+    guint                   last_delay;
+    guint                   target_delay;
+    struct async_task       *pending_restore_task;
+    GList                   *results;
+};
+
+G_DEFINE_TYPE(SpicePulse, spice_pulse, SPICE_TYPE_AUDIO)
+
+static const char *stream_state_names[] = {
+    [ PA_STREAM_UNCONNECTED ] = "unconnected",
+    [ PA_STREAM_CREATING    ] = "creating",
+    [ PA_STREAM_READY       ] = "ready",
+    [ PA_STREAM_FAILED      ] = "failed",
+    [ PA_STREAM_TERMINATED  ] = "terminated",
+};
+
+static const char *context_state_names[] = {
+    [ PA_CONTEXT_UNCONNECTED  ] = "unconnected",
+    [ PA_CONTEXT_CONNECTING   ] = "connecting",
+    [ PA_CONTEXT_AUTHORIZING  ] = "authorizing",
+    [ PA_CONTEXT_SETTING_NAME ] = "setting_name",
+    [ PA_CONTEXT_READY        ] = "ready",
+    [ PA_CONTEXT_FAILED       ] = "failed",
+    [ PA_CONTEXT_TERMINATED   ] = "terminated",
+};
+#define STATE_NAME(array, state) \
+    ((state < G_N_ELEMENTS(array)) ? array[state] : NULL)
+
+static void stream_stop(SpicePulse *pulse, struct stream *s);
+static gboolean connect_channel(SpiceAudio *audio, SpiceChannel *channel);
+static void channel_weak_notified(gpointer data, GObject *where_the_object_was);
+static void spice_pulse_get_playback_volume_info_async(SpiceAudio *audio, GCancellable *cancellable,
+        SpiceMainChannel *main_channel, GAsyncReadyCallback callback, gpointer user_data);
+static gboolean spice_pulse_get_playback_volume_info_finish(SpiceAudio *audio, GAsyncResult *res,
+        gboolean *mute, guint8 *nchannels, guint16 **volume, GError **error);
+static void spice_pulse_get_record_volume_info_async(SpiceAudio *audio, GCancellable *cancellable,
+        SpiceMainChannel *main_channel, GAsyncReadyCallback callback, gpointer user_data);
+static gboolean spice_pulse_get_record_volume_info_finish(SpiceAudio *audio,GAsyncResult *res,
+        gboolean *mute, guint8 *nchannels, guint16 **volume, GError **error);
+static void stream_restore_read_cb(pa_context *context,
+        const pa_ext_stream_restore_info *info, int eol, void *userdata);
+static void spice_pulse_complete_async_task(struct async_task *task, const gchar *err_msg);
+static void spice_pulse_complete_all_async_tasks(SpicePulse *pulse, const gchar *err_msg);
+
+static void spice_pulse_finalize(GObject *obj)
+{
+    SpicePulse *pulse = SPICE_PULSE(obj);
+    SpicePulsePrivate *p;
+
+    p = pulse->priv;
+
+    if (p->context != NULL)
+        pa_context_unref(p->context);
+
+    if (p->mainloop != NULL)
+        pa_glib_mainloop_free(p->mainloop);
+
+    G_OBJECT_CLASS(spice_pulse_parent_class)->finalize(obj);
+}
+
+static void spice_pulse_dispose(GObject *obj)
+{
+    SpicePulse *pulse = SPICE_PULSE(obj);
+    SpicePulsePrivate *p;
+
+    SPICE_DEBUG("%s", __FUNCTION__);
+    p = pulse->priv;
+
+    if (p->playback.uncork_op)
+        pa_operation_unref(p->playback.uncork_op);
+    p->playback.uncork_op = NULL;
+
+    if (p->playback.cork_op)
+        pa_operation_unref(p->playback.cork_op);
+    p->playback.cork_op = NULL;
+
+    if (p->record.uncork_op)
+        pa_operation_unref(p->record.uncork_op);
+    p->record.uncork_op = NULL;
+
+    if (p->record.cork_op)
+        pa_operation_unref(p->record.cork_op);
+    p->record.cork_op = NULL;
+
+    if (p->results != NULL)
+        spice_pulse_complete_all_async_tasks(pulse, "PulseAudio is being dispose");
+
+    g_clear_pointer(&p->playback.name, g_free);
+    g_clear_pointer(&p->record.name, g_free);
+
+    if (p->pchannel)
+        g_object_weak_unref(G_OBJECT(p->pchannel), channel_weak_notified, pulse);
+    p->pchannel = NULL;
+
+    if (p->rchannel)
+        g_object_weak_unref(G_OBJECT(p->rchannel), channel_weak_notified, pulse);
+    p->rchannel = NULL;
+
+    G_OBJECT_CLASS(spice_pulse_parent_class)->dispose(obj);
+}
+
+static void spice_pulse_init(SpicePulse *pulse)
+{
+    pulse->priv = SPICE_PULSE_GET_PRIVATE(pulse);
+}
+
+static void spice_pulse_class_init(SpicePulseClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+    SpiceAudioClass *audio_class = SPICE_AUDIO_CLASS(klass);
+
+    audio_class->connect_channel = connect_channel;
+    audio_class->get_playback_volume_info_async = spice_pulse_get_playback_volume_info_async;
+    audio_class->get_playback_volume_info_finish = spice_pulse_get_playback_volume_info_finish;
+    audio_class->get_record_volume_info_async = spice_pulse_get_record_volume_info_async;
+    audio_class->get_record_volume_info_finish = spice_pulse_get_record_volume_info_finish;
+
+    gobject_class->finalize = spice_pulse_finalize;
+    gobject_class->dispose = spice_pulse_dispose;
+
+    g_type_class_add_private(klass, sizeof(SpicePulsePrivate));
+}
+
+/* ------------------------------------------------------------------ */
+static void pulse_uncork_cb(pa_stream *pastream, int success, void *data)
+{
+    struct stream *s = data;
+
+    if (!success)
+        g_warning("pulseaudio uncork operation failed");
+
+    pa_operation_unref(s->uncork_op);
+    s->uncork_op = NULL;
+}
+
+static void stream_uncork(SpicePulse *pulse, struct stream *s)
+{
+    SpicePulsePrivate *p = pulse->priv;
+    pa_operation *o = NULL;
+
+    g_return_if_fail(s->stream);
+
+    if (s->cork_op) {
+        pa_operation_cancel(s->cork_op);
+        pa_operation_unref(s->cork_op);
+        s->cork_op = NULL;
+    }
+
+    if (pa_stream_is_corked(s->stream) && !s->uncork_op) {
+        if (!(o = pa_stream_cork(s->stream, 0, pulse_uncork_cb, s))) {
+            g_warning("pa_stream_uncork() failed: %s",
+                      pa_strerror(pa_context_errno(p->context)));
+        }
+        s->uncork_op = o;
+    }
+}
+
+static void pulse_flush_cb(pa_stream *pastream, int success, void *data)
+{
+    struct stream *s = data;
+
+    if (!success)
+        g_warning("pulseaudio flush operation failed");
+
+    pa_operation_unref(s->cork_op);
+    s->cork_op = NULL;
+}
+
+static void pulse_cork_flush_cb(pa_stream *pastream, int success, void *data)
+{
+    struct stream *s = data;
+
+    if (!success)
+        g_warning("pulseaudio cork operation failed");
+
+    pa_operation_unref(s->cork_op);
+
+    if (!(s->cork_op = pa_stream_flush(s->stream, pulse_flush_cb, s))) {
+        g_warning("pa_stream_flush() failed");
+    }
+}
+
+static void pulse_cork_cb(pa_stream *pastream, int success, void *data)
+{
+    struct stream *s = data;
+
+    SPICE_DEBUG("%s: cork started", __FUNCTION__);
+    if (!success)
+        g_warning("pulseaudio cork operation failed");
+
+    pa_operation_unref(s->cork_op);
+    s->cork_op = NULL;
+}
+
+static void stream_cork(SpicePulse *pulse, struct stream *s, gboolean with_flush)
+{
+    SpicePulsePrivate *p = pulse->priv;
+    pa_operation *o = NULL;
+
+    if (s->uncork_op) {
+        pa_operation_cancel(s->uncork_op);
+        pa_operation_unref(s->uncork_op);
+        s->uncork_op = NULL;
+    }
+
+    if (!pa_stream_is_corked(s->stream) && !s->cork_op) {
+        if (!(o = pa_stream_cork(s->stream, 1,
+                                 with_flush ? pulse_cork_flush_cb :
+                                              pulse_cork_cb,
+                                 s))) {
+            g_warning("pa_stream_cork() failed: %s",
+                      pa_strerror(pa_context_errno(p->context)));
+        }
+        s->cork_op = o;
+    }
+}
+
+static void stream_stop(SpicePulse *pulse, struct stream *s)
+{
+    SpicePulsePrivate *p = pulse->priv;
+
+    if (pa_stream_disconnect(s->stream) < 0) {
+        g_warning("pa_stream_disconnect() failed: %s",
+                  pa_strerror(pa_context_errno(p->context)));
+    }
+    pa_stream_unref(s->stream);
+    s->stream = NULL;
+}
+
+static void stream_state_callback(pa_stream *s, void *userdata)
+{
+    SpicePulse *pulse = userdata;
+    SpicePulsePrivate *p;
+
+    p = pulse->priv;
+
+    g_return_if_fail(p != NULL);
+    g_return_if_fail(s != NULL);
+
+    switch (pa_stream_get_state(s)) {
+        case PA_STREAM_CREATING:
+        case PA_STREAM_TERMINATED:
+        case PA_STREAM_READY:
+            break;
+        case PA_STREAM_FAILED:
+        default:
+            g_warning("Stream error: %s", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
+    }
+}
+
+static void stream_underflow_cb(pa_stream *s, void *userdata)
+{
+    SpicePulse *pulse = userdata;
+    SpicePulsePrivate *p;
+
+    SPICE_DEBUG("PA stream underflow!!");
+
+    p = pulse->priv;
+    g_return_if_fail(p != NULL);
+    p->playback.num_underflow++;
+#ifdef PULSE_ADJUST_LATENCY
+    const pa_buffer_attr *buffer_attr;
+    pa_buffer_attr new_buffer_attr;
+    pa_operation *op;
+
+    buffer_attr = pa_stream_get_buffer_attr(s);
+    g_return_if_fail(buffer_attr != NULL);
+
+    new_buffer_attr = *buffer_attr;
+    new_buffer_attr.tlength *= 2;
+    new_buffer_attr.minreq *= 2;
+    op = pa_stream_set_buffer_attr(s, &new_buffer_attr, NULL, NULL);
+    pa_operation_unref(op);
+#endif
+}
+
+static void stream_update_latency_callback(pa_stream *s, void *userdata)
+{
+    SpicePulse *pulse = userdata;
+    pa_usec_t usec;
+    int negative = 0;
+    SpicePulsePrivate *p;
+
+    p = pulse->priv;
+
+    g_return_if_fail(s != NULL);
+    g_return_if_fail(p != NULL);
+
+    if (!p->playback.stream || !p->playback.started)
+        return;
+
+    if (pa_stream_get_latency(s, &usec, &negative) < 0) {
+        g_warning("Failed to get latency: %s", pa_strerror(pa_context_errno(p->context)));
+        return;
+    }
+
+    g_return_if_fail(negative == FALSE);
+    p->last_delay = usec / PA_USEC_PER_MSEC;
+    spice_playback_channel_set_delay(SPICE_PLAYBACK_CHANNEL(p->pchannel), usec / 1000);
+    if (pa_stream_is_corked(p->playback.stream)) {
+        if (p->last_delay >= p->target_delay) {
+            SPICE_DEBUG("%s: uncork playback. delay %u target %u",  __FUNCTION__, p->last_delay, p->target_delay);
+            stream_uncork(pulse, &p->playback);
+        } else {
+            SPICE_DEBUG("%s: still corked. delay %u target %u",  __FUNCTION__, p->last_delay, p->target_delay);
+        }
+    }
+}
+
+static void create_playback(SpicePulse *pulse)
+{
+    SpicePulsePrivate *p = pulse->priv;
+    pa_stream_flags_t flags;
+    pa_buffer_attr buffer_attr = { 0, };
+
+    g_return_if_fail(p != NULL);
+    g_return_if_fail(p->context != NULL);
+    g_return_if_fail(p->playback.stream == NULL);
+    g_return_if_fail(pa_context_get_state(p->context) == PA_CONTEXT_READY);
+
+    p->playback.state = PA_STREAM_READY;
+    p->playback.stream = pa_stream_new(p->context, "playback",
+                                       &p->playback.spec, NULL);
+    pa_stream_set_state_callback(p->playback.stream, stream_state_callback, pulse);
+    pa_stream_set_underflow_callback(p->playback.stream, stream_underflow_cb, pulse);
+    pa_stream_set_latency_update_callback(p->playback.stream, stream_update_latency_callback, pulse);
+
+    buffer_attr.maxlength = -1;
+    buffer_attr.tlength = pa_usec_to_bytes(p->target_delay * PA_USEC_PER_MSEC, &p->playback.spec);
+    buffer_attr.prebuf = -1;
+    buffer_attr.minreq = -1;
+    flags = PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE;
+
+    if (pa_stream_connect_playback(p->playback.stream,
+                                   NULL, &buffer_attr, flags, NULL, NULL) < 0) {
+        g_warning("pa_stream_connect_playback() failed: %s",
+                  pa_strerror(pa_context_errno(p->context)));
+    }
+}
+
+static void playback_start(SpicePlaybackChannel *channel, gint format, gint channels,
+                           gint frequency, gpointer data)
+{
+    SpicePulse *pulse = data;
+    SpicePulsePrivate *p = pulse->priv;
+    pa_context_state_t state;
+    guint latency;
+
+    g_return_if_fail(p != NULL);
+
+    p->playback.started = TRUE;
+    p->playback.num_underflow = 0;
+    g_object_get(p->pchannel, "min-latency", &latency, NULL);
+
+    if (p->playback.stream &&
+        (p->playback.spec.rate != frequency ||
+         p->playback.spec.channels != channels ||
+         p->target_delay != latency)) {
+        stream_stop(pulse, &p->playback);
+    }
+
+    g_return_if_fail(format == SPICE_AUDIO_FMT_S16);
+    p->playback.spec.format   = PA_SAMPLE_S16LE;
+    p->playback.spec.rate     = frequency;
+    p->playback.spec.channels = channels;
+    p->target_delay = latency;
+    p->last_delay = 0;
+
+    state = pa_context_get_state(p->context);
+    switch (state) {
+    case PA_CONTEXT_READY:
+        if (p->state != state) {
+            SPICE_DEBUG("%s: pulse context ready", __FUNCTION__);
+        }
+        if (p->playback.stream == NULL) {
+            create_playback(pulse);
+        } else
+            stream_uncork(pulse, &p->playback);
+        break;
+    default:
+        if (p->state != state) {
+            SPICE_DEBUG("%s: pulse context not ready (%s)",
+                        __FUNCTION__, STATE_NAME(context_state_names, state));
+        }
+        break;
+    }
+    p->state = state;
+}
+
+static void playback_data(SpicePlaybackChannel *channel,
+                          gpointer *audio, gint size,
+                          gpointer data)
+{
+    SpicePulse *pulse = data;
+    SpicePulsePrivate *p = pulse->priv;
+    pa_stream_state_t state;
+
+    if (!p->playback.stream)
+        return;
+
+    state = pa_stream_get_state(p->playback.stream);
+    switch (state) {
+    case PA_STREAM_CREATING:
+        SPICE_DEBUG("stream creating, dropping data");
+        break;
+    case PA_STREAM_READY:
+        if (p->playback.state != state) {
+            SPICE_DEBUG("%s: pulse playback stream ready", __FUNCTION__);
+        }
+        if (pa_stream_write(p->playback.stream, audio, size, NULL, 0, PA_SEEK_RELATIVE) < 0) {
+            g_warning("pa_stream_write() failed: %s",
+                      pa_strerror(pa_context_errno(p->context)));
+        }
+        break;
+    default:
+        if (p->playback.state != state) {
+            SPICE_DEBUG("%s: pulse playback stream not ready (%s)",
+                        __FUNCTION__, STATE_NAME(stream_state_names, state));
+        }
+        break;
+    }
+    p->playback.state = state;
+}
+
+static void playback_stop(SpicePulse *pulse)
+{
+    SpicePulsePrivate *p = pulse->priv;
+
+    SPICE_DEBUG("%s: #underflow %u", __FUNCTION__, p->playback.num_underflow);
+
+    p->playback.started = FALSE;
+    if (!p->playback.stream)
+        return;
+
+    stream_cork(pulse, &p->playback, TRUE);
+}
+
+static void stream_read_callback(pa_stream *s, size_t length, void *data)
+{
+    SpicePulse *pulse = data;
+    SpicePulsePrivate *p = pulse->priv;
+
+    g_return_if_fail(p != NULL);
+
+    while (pa_stream_readable_size(s) > 0) {
+        const void *snddata;
+
+        if (pa_stream_peek(s, &snddata, &length) < 0) {
+            g_warning("pa_stream_peek() failed: %s",
+                      pa_strerror(pa_context_errno(p->context)));
+            return;
+        }
+
+        g_return_if_fail(snddata);
+        g_return_if_fail(length > 0);
+
+        if (p->rchannel != NULL)
+            spice_record_send_data(SPICE_RECORD_CHANNEL(p->rchannel),
+                                   /* FIXME: server side doesn't care about ts?
+                                      what is the unit? ms apparently */
+                                   (gpointer)snddata, length, 0);
+
+        if (pa_stream_drop(s) < 0) {
+            g_warning("pa_stream_drop() failed: %s",
+                      pa_strerror(pa_context_errno(p->context)));
+            return;
+        }
+    }
+}
+
+static void create_record(SpicePulse *pulse)
+{
+    SpicePulsePrivate *p = pulse->priv;
+    pa_buffer_attr buffer_attr = { 0, };
+    pa_stream_flags_t flags;
+
+    g_return_if_fail(p != NULL);
+    g_return_if_fail(p->context != NULL);
+    g_return_if_fail(p->record.stream == NULL);
+    g_return_if_fail(pa_context_get_state(p->context) == PA_CONTEXT_READY);
+
+    p->record.state = PA_STREAM_READY;
+    p->record.stream = pa_stream_new(p->context, "record",
+                                     &p->record.spec, NULL);
+    pa_stream_set_read_callback(p->record.stream, stream_read_callback, pulse);
+    pa_stream_set_state_callback(p->record.stream, stream_state_callback, pulse);
+
+    /* FIXME: we might want customizable latency */
+    buffer_attr.maxlength = -1;
+    buffer_attr.prebuf = -1;
+    buffer_attr.fragsize = buffer_attr.tlength = pa_usec_to_bytes(20 * PA_USEC_PER_MSEC, &p->record.spec);
+    buffer_attr.minreq = (uint32_t) -1;
+    flags = PA_STREAM_ADJUST_LATENCY;
+
+    if (pa_stream_connect_record(p->record.stream, NULL, &buffer_attr, flags) < 0) {
+        g_warning("pa_stream_connect_record() failed: %s",
+                  pa_strerror(pa_context_errno(p->context)));
+    }
+}
+
+static void record_start(SpiceRecordChannel *channel, gint format, gint channels,
+                         gint frequency, gpointer data)
+{
+    SpicePulse *pulse = data;
+    SpicePulsePrivate *p = pulse->priv;
+    pa_context_state_t state;
+
+    p->record.started = TRUE;
+
+    if (p->record.stream &&
+        (p->record.spec.rate != frequency ||
+         p->record.spec.channels != channels)) {
+        stream_stop(pulse, &p->record);
+    }
+
+    g_return_if_fail(format == SPICE_AUDIO_FMT_S16);
+    p->record.spec.format = PA_SAMPLE_S16LE;
+    p->record.spec.rate = frequency;
+    p->record.spec.channels = channels;
+
+    state = pa_context_get_state(p->context);
+    switch (state) {
+    case PA_CONTEXT_READY:
+        if (p->state != state) {
+            SPICE_DEBUG("%s: pulse context ready", __FUNCTION__);
+        }
+        if (p->record.stream == NULL) {
+            create_record(pulse);
+        } else
+            stream_uncork(pulse, &p->record);
+        break;
+    default:
+        if (p->state != state) {
+            g_warning("%s: pulse context not ready (%s)",
+                      __FUNCTION__, STATE_NAME(context_state_names, state));
+        }
+        break;
+    }
+    p->state = state;
+}
+
+static void record_stop(SpicePulse *pulse)
+{
+    SpicePulsePrivate *p = pulse->priv;
+
+    SPICE_DEBUG("%s", __FUNCTION__);
+
+    p->record.started = FALSE;
+    if (!p->record.stream)
+        return;
+
+    stream_stop(pulse, &p->record);
+}
+
+static void playback_volume_changed(GObject *object, GParamSpec *pspec, gpointer data)
+{
+    SpicePulse *pulse = data;
+    SpicePulsePrivate *p = pulse->priv;
+    guint16 *volume;
+    guint nchannels;
+    pa_operation *op;
+    pa_cvolume v;
+    guint i;
+
+    g_object_get(object,
+                 "volume", &volume,
+                 "nchannels", &nchannels,
+                 NULL);
+
+    pa_cvolume_init(&v);
+    v.channels = p->playback.spec.channels;
+    for (i = 0; i < nchannels; ++i) {
+        v.values[i] = (PA_VOLUME_NORM - PA_VOLUME_MUTED) * volume[i] / G_MAXUINT16;
+        SPICE_DEBUG("playback volume changed %u", v.values[i]);
+    }
+
+    if (!p->playback.stream ||
+        pa_stream_get_index(p->playback.stream) == PA_INVALID_INDEX)
+        return;
+
+    op = pa_context_set_sink_input_volume(p->context,
+        pa_stream_get_index(p->playback.stream),
+        &v, NULL, NULL);
+    if (!op)
+        g_warning("set_sink_input_volume() failed: %s",
+                  pa_strerror(pa_context_errno(p->context)));
+    else
+        pa_operation_unref(op);
+}
+
+static void playback_mute_changed(GObject *object, GParamSpec *pspec, gpointer data)
+{
+    SpicePulse *pulse = data;
+    SpicePulsePrivate *p = pulse->priv;
+    gboolean mute;
+    pa_operation *op;
+
+    g_object_get(object, "mute", &mute, NULL);
+    SPICE_DEBUG("playback mute changed %u", mute);
+
+    if (!p->playback.stream ||
+        pa_stream_get_index(p->playback.stream) == PA_INVALID_INDEX)
+        return;
+
+    op = pa_context_set_sink_input_mute(p->context,
+        pa_stream_get_index(p->playback.stream),
+        mute, NULL, NULL);
+    if (!op)
+        g_warning("set_sink_input_mute() failed: %s",
+                  pa_strerror(pa_context_errno(p->context)));
+    else
+        pa_operation_unref(op);
+}
+
+static void playback_min_latency_changed(GObject *object, GParamSpec *pspec, gpointer data)
+{
+
+    SpicePulse *pulse = data;
+    SpicePulsePrivate *p = pulse->priv;
+    guint min_latency;
+
+    g_object_get(object, "min-latency", &min_latency, NULL);
+    p->target_delay = min_latency;
+
+    if (p->last_delay < p->target_delay) {
+        spice_debug("%s: corking", __FUNCTION__);
+        if (p->playback.stream)
+            stream_cork(pulse, &p->playback, FALSE);
+    } else {
+        spice_debug("%s: not corking. The current delay satisfies the requirement", __FUNCTION__);
+    }
+}
+
+static void record_mute_changed(GObject *object, GParamSpec *pspec, gpointer data)
+{
+    SpicePulse *pulse = data;
+    SpicePulsePrivate *p = pulse->priv;
+    gboolean mute;
+    pa_operation *op;
+
+    g_object_get(object, "mute", &mute, NULL);
+    SPICE_DEBUG("record mute changed %u", mute);
+
+    if (!p->record.stream ||
+        pa_stream_get_device_index(p->record.stream) == PA_INVALID_INDEX)
+        return;
+
+#if PA_CHECK_VERSION(1,0,0)
+    op = pa_context_set_source_output_mute(p->context,
+        pa_stream_get_index(p->record.stream),
+#else
+    op = pa_context_set_source_mute_by_index(p->context,
+        pa_stream_get_device_index(p->record.stream),
+#endif
+        mute, NULL, NULL);
+    if (!op)
+        g_warning("set_source_output_mute() failed: %s",
+                  pa_strerror(pa_context_errno(p->context)));
+    else
+        pa_operation_unref(op);
+}
+
+static void record_volume_changed(GObject *object, GParamSpec *pspec, gpointer data)
+{
+    SpicePulse *pulse = data;
+    SpicePulsePrivate *p = pulse->priv;
+    guint16 *volume;
+    guint nchannels;
+    pa_operation *op;
+    pa_cvolume v;
+    guint i;
+
+    g_object_get(object,
+                 "volume", &volume,
+                 "nchannels", &nchannels,
+                 NULL);
+
+    pa_cvolume_init(&v);
+    v.channels = p->record.spec.channels;
+    for (i = 0; i < nchannels; ++i) {
+        v.values[i] = (PA_VOLUME_NORM - PA_VOLUME_MUTED) * volume[i] / G_MAXUINT16;
+        SPICE_DEBUG("record volume changed %u", v.values[i]);
+    }
+
+    if (!p->record.stream ||
+        pa_stream_get_device_index(p->record.stream) == PA_INVALID_INDEX)
+        return;
+
+#if PA_CHECK_VERSION(1,0,0)
+    op = pa_context_set_source_output_volume(p->context,
+        pa_stream_get_index(p->record.stream),
+#else
+    op = pa_context_set_source_volume_by_index(p->context,
+        pa_stream_get_device_index(p->record.stream),
+#endif
+        &v, NULL, NULL);
+    if (!op)
+        g_warning("set_source_output_volume() failed: %s",
+                  pa_strerror(pa_context_errno(p->context)));
+    else
+        pa_operation_unref(op);
+}
+
+static void
+channel_weak_notified(gpointer data,
+                      GObject *where_the_object_was)
+{
+    SpicePulse *pulse = SPICE_PULSE(data);
+    SpicePulsePrivate *p = pulse->priv;
+
+    if (where_the_object_was == (GObject *)p->pchannel) {
+        SPICE_DEBUG("playback closed");
+        playback_stop(pulse);
+        p->pchannel = NULL;
+    } else if (where_the_object_was == (GObject *)p->rchannel) {
+        SPICE_DEBUG("record closed");
+        record_stop(pulse);
+        p->rchannel = NULL;
+    }
+}
+
+static gboolean connect_channel(SpiceAudio *audio, SpiceChannel *channel)
+{
+    SpicePulse *pulse = SPICE_PULSE(audio);
+    SpicePulsePrivate *p = pulse->priv;
+
+    if (SPICE_IS_PLAYBACK_CHANNEL(channel)) {
+        g_return_val_if_fail(p->pchannel == NULL, FALSE);
+
+        p->pchannel = channel;
+        g_object_weak_ref(G_OBJECT(p->pchannel), channel_weak_notified, audio);
+        spice_g_signal_connect_object(channel, "playback-start",
+                                      G_CALLBACK(playback_start), pulse, 0);
+        spice_g_signal_connect_object(channel, "playback-data",
+                                      G_CALLBACK(playback_data), pulse, 0);
+        spice_g_signal_connect_object(channel, "playback-stop",
+                                      G_CALLBACK(playback_stop), pulse, G_CONNECT_SWAPPED);
+        spice_g_signal_connect_object(channel, "notify::volume",
+                                      G_CALLBACK(playback_volume_changed), pulse, 0);
+        spice_g_signal_connect_object(channel, "notify::mute",
+                                      G_CALLBACK(playback_mute_changed), pulse, 0);
+        spice_g_signal_connect_object(channel, "notify::min-latency",
+                                      G_CALLBACK(playback_min_latency_changed), pulse, 0);
+
+        return TRUE;
+    }
+
+    if (SPICE_IS_RECORD_CHANNEL(channel)) {
+        g_return_val_if_fail(p->rchannel == NULL, FALSE);
+
+        p->rchannel = channel;
+        g_object_weak_ref(G_OBJECT(p->rchannel), channel_weak_notified, audio);
+        spice_g_signal_connect_object(channel, "record-start",
+                                      G_CALLBACK(record_start), pulse, 0);
+        spice_g_signal_connect_object(channel, "record-stop",
+                                      G_CALLBACK(record_stop), pulse, G_CONNECT_SWAPPED);
+        spice_g_signal_connect_object(channel, "notify::volume",
+                                      G_CALLBACK(record_volume_changed), pulse, 0);
+        spice_g_signal_connect_object(channel, "notify::mute",
+                                      G_CALLBACK(record_mute_changed), pulse, 0);
+
+        return TRUE;
+    }
+
+    return FALSE;
+}
+
+static void context_state_callback(pa_context *c, void *userdata)
+{
+    SpicePulse *pulse = userdata;
+    SpicePulsePrivate *p;
+
+    p = pulse->priv;
+
+    g_return_if_fail(p != NULL);
+    g_return_if_fail(c != NULL);
+    switch (pa_context_get_state(c)) {
+    case PA_CONTEXT_CONNECTING:
+    case PA_CONTEXT_AUTHORIZING:
+    case PA_CONTEXT_SETTING_NAME:
+    case PA_CONTEXT_UNCONNECTED:
+        break;
+
+    case PA_CONTEXT_READY: {
+        if (!p->record.stream && p->record.started)
+            create_record(SPICE_PULSE(userdata));
+
+        if (!p->playback.stream && p->playback.started)
+            create_playback(SPICE_PULSE(userdata));
+
+        if (p->pending_restore_task != NULL &&
+                p->pending_restore_task->pa_op == NULL) {
+            pa_operation *op = pa_ext_stream_restore_read(p->context,
+                                                          stream_restore_read_cb,
+                                                          pulse);
+            if (!op)
+                goto context_fail;
+            p->pending_restore_task->pa_op = op;
+        }
+        break;
+    }
+
+    case PA_CONTEXT_FAILED:
+        g_warning("PulseAudio context failed %s",
+                  pa_strerror(pa_context_errno(p->context)));
+        goto context_fail;
+
+    case PA_CONTEXT_TERMINATED:
+    default:
+        SPICE_DEBUG("PulseAudio context terminated");
+        goto context_fail;
+    }
+
+    return;
+
+context_fail:
+    if (p->pending_restore_task != NULL) {
+        const gchar *errmsg = pa_strerror(pa_context_errno(p->context));
+        errmsg = (errmsg != NULL) ? errmsg : "PulseAudio context terminated";
+        spice_pulse_complete_all_async_tasks(pulse, errmsg);
+    }
+}
+
+SpicePulse *spice_pulse_new(SpiceSession *session, GMainContext *context,
+                            const char *name)
+{
+    SpicePulse *pulse;
+    SpicePulsePrivate *p;
+
+    pulse = g_object_new(SPICE_TYPE_PULSE,
+                         "session", session,
+                         "main-context", context,
+                         NULL);
+    p = pulse->priv;
+
+    p->mainloop = pa_glib_mainloop_new(context);
+    p->state = PA_CONTEXT_READY;
+    p->context = pa_context_new(pa_glib_mainloop_get_api(p->mainloop), name);
+    pa_context_set_state_callback(p->context, context_state_callback, pulse);
+    if (pa_context_connect(p->context, NULL, 0, NULL) < 0) {
+        g_warning("pa_context_connect() failed: %s",
+            pa_strerror(pa_context_errno(p->context)));
+        goto error;
+    }
+
+    p->playback.name = g_strconcat("sink-input-by-application-name:",
+                                   g_get_application_name(), NULL);
+    p->record.name = g_strconcat("source-output-by-application-name:",
+                                 g_get_application_name(), NULL);
+    return pulse;
+
+error:
+    g_object_unref(pulse);
+    return  NULL;
+}
+
+static gboolean free_async_task(gpointer user_data)
+{
+    struct async_task *task = user_data;
+
+    if (task == NULL)
+        return G_SOURCE_REMOVE;
+
+    if (task->pa_op != NULL) {
+        pa_operation_cancel(task->pa_op);
+        pa_operation_unref(task->pa_op);
+        task->pa_op = NULL;
+    }
+
+    if (task->pulse) {
+        if (task->pulse->priv->pending_restore_task == task) {
+            task->pulse->priv->pending_restore_task = NULL;
+        }
+        g_object_unref(task->pulse);
+    }
+
+    if (task->res)
+        g_object_unref(task->res);
+
+    if (task->main_channel)
+        g_object_unref(task->main_channel);
+
+    if (task->pa_op != NULL)
+        pa_operation_unref(task->pa_op);
+
+    if (task->cancel_id != 0) {
+        g_cancellable_disconnect(task->cancellable, task->cancel_id);
+        g_clear_object(&task->cancellable);
+    }
+
+    g_free(task);
+    return G_SOURCE_REMOVE;
+}
+
+static void cancel_task(GCancellable *cancellable, gpointer user_data)
+{
+    struct async_task *task = user_data;
+    g_return_if_fail(task != NULL);
+
+#if GLIB_CHECK_VERSION(2,40,0)
+    free_async_task(task);
+#else
+    /* This must be done now otherwise pulseaudio may return to a
+     * cancelled task operation before free_async_task is called */
+    if (task->pa_op != NULL) {
+        pa_operation_cancel(task->pa_op);
+        pa_operation_unref(task->pa_op);
+        task->pa_op = NULL;
+    }
+
+    /* Clear the pending_restore_task reference to avoid triggering a
+     * pa_operation when context state is in READY state */
+    if (task->pulse->priv->pending_restore_task == task) {
+        task->pulse->priv->pending_restore_task = NULL;
+    }
+
+#if !GLIB_CHECK_VERSION(2,32,0)
+    /* g_simple_async_result_set_check_cancellable is not present. Set an error
+     * in the GSimpleAsyncResult in case of _finish functions is called */
+    g_simple_async_result_set_error(task->res,
+                                    SPICE_CLIENT_ERROR,
+                                    SPICE_CLIENT_ERROR_FAILED,
+                                    "Operation was cancelled");
+#endif
+    /* FIXME: https://bugzilla.gnome.org/show_bug.cgi?id=705395
+     * Free the memory in idle */
+    g_idle_add(free_async_task, task);
+#endif
+}
+
+static void complete_task(SpicePulse *pulse, struct async_task *task, const gchar *err_msg)
+{
+    SpicePulsePrivate *p = pulse->priv;
+
+    /* If we do have any err_msg, we failed */
+    if (err_msg != NULL) {
+        g_simple_async_result_set_op_res_gboolean(task->res, FALSE);
+        g_simple_async_result_set_error(task->res,
+                                        SPICE_CLIENT_ERROR,
+                                        SPICE_CLIENT_ERROR_FAILED,
+                                        "restore-info failed due %s",
+                                        err_msg);
+    /* Volume-info does not change if stream is not found */
+    } else if ((task->is_playback == TRUE && p->playback.info_updated == FALSE) ||
+               (task->is_playback == FALSE && p->record.info_updated == FALSE)) {
+        g_simple_async_result_set_op_res_gboolean(task->res, FALSE);
+        g_simple_async_result_set_error(task->res,
+                                        SPICE_CLIENT_ERROR,
+                                        SPICE_CLIENT_ERROR_FAILED,
+                                        "Stream not found by pulse");
+    } else {
+        g_simple_async_result_set_op_res_gboolean(task->res, TRUE);
+    }
+
+    /* As all async calls to PulseAudio are done with glib mainloop, it is
+     * safe to complete the operation synchronously here. */
+    g_simple_async_result_complete(task->res);
+}
+
+static void spice_pulse_complete_async_task(struct async_task *task, const gchar *err_msg)
+{
+    SpicePulsePrivate *p;
+
+    g_return_if_fail(task != NULL);
+    p = task->pulse->priv;
+
+    complete_task(task->pulse, task, err_msg);
+    if (p->results != NULL) {
+        p->results = g_list_remove(p->results, task);
+        SPICE_DEBUG("Number of async task is %d", g_list_length(p->results));
+    }
+    free_async_task(task);
+}
+
+static void spice_pulse_complete_all_async_tasks(SpicePulse *pulse, const gchar *err_msg)
+{
+    SpicePulsePrivate *p;
+    GList *it;
+
+    g_return_if_fail(pulse != NULL);
+    p = pulse->priv;
+
+    /* Complete all tasks in list */
+    for(it = p->results; it != NULL; it = it->next) {
+        struct async_task *task = it->data;
+        complete_task(pulse, task, err_msg);
+        free_async_task(task);
+    }
+    g_list_free(p->results);
+    p->results = NULL;
+    SPICE_DEBUG("All async tasks completed");
+}
+
+static void stream_restore_read_cb(pa_context *context,
+                                   const pa_ext_stream_restore_info *info,
+                                   int eol,
+                                   void *userdata)
+{
+    SpicePulsePrivate *p = SPICE_PULSE(userdata)->priv;
+    struct stream *pstream = NULL;
+
+    if (eol ||
+            (p->playback.info_updated == TRUE &&
+             p->record.info_updated == TRUE)) {
+        /* We only have one pa_operation running the stream-restore-info
+         * which retrieves volume-info from both Playback and Record channels;
+         * We can complete all async tasks now that this operation ended.
+         * (or we already have the volume-info we want)
+         * Note: the following function cancel the current pa_operation */
+        spice_pulse_complete_all_async_tasks(SPICE_PULSE(userdata), NULL);
+        return;
+    }
+
+    if (g_strcmp0(info->name, p->playback.name) == 0) {
+        pstream = &p->playback;
+    } else if (g_strcmp0(info->name, p->record.name) == 0) {
+        pstream = &p->record;
+    } else {
+        /* This is not the stream you are looking for. */
+        return;
+    }
+
+    if (info->channel_map.channels == 0) {
+        SPICE_DEBUG("Number of channels stored is zero. Ignore. (%s)", info->name);
+        return;
+    }
+
+    pstream->info_updated = TRUE;
+    pstream->info.name = pstream->name;
+    pstream->info.mute = info->mute;
+    pstream->info.channel_map = info->channel_map;
+    pstream->info.volume = info->volume;
+}
+
+#if PA_CHECK_VERSION(1,0,0)
+static void source_output_info_cb(pa_context *context,
+                                  const pa_source_output_info *info,
+                                  int eol,
+                                  void *userdata)
+#else
+static void source_info_cb(pa_context *context,
+                           const pa_source_info *info,
+                           int eol,
+                           void *userdata)
+#endif
+{
+    struct async_task *task = userdata;
+    SpicePulsePrivate *p = task->pulse->priv;
+    struct stream *pstream = &p->record;
+
+    if (eol) {
+        spice_pulse_complete_async_task(task, NULL);
+        return;
+    }
+
+    pstream->info_updated = TRUE;
+    pstream->info.name = pstream->name;
+    pstream->info.mute = info->mute;
+    pstream->info.channel_map = info->channel_map;
+    pstream->info.volume = info->volume;
+}
+
+static void sink_input_info_cb(pa_context *context,
+                               const pa_sink_input_info *info,
+                               int eol,
+                               void *userdata)
+{
+    struct async_task *task = userdata;
+    SpicePulsePrivate *p = task->pulse->priv;
+    struct stream *pstream = &p->playback;
+
+    if (eol) {
+        spice_pulse_complete_async_task(task, NULL);
+        return;
+    }
+
+    pstream->info_updated = TRUE;
+    pstream->info.name = pstream->name;
+    pstream->info.mute = info->mute;
+    pstream->info.channel_map = info->channel_map;
+    pstream->info.volume = info->volume;
+}
+
+/* to avoid code duplication */
+static void pulse_stream_restore_info_async(gboolean is_playback,
+                                            SpiceAudio *audio,
+                                            GCancellable *cancellable,
+                                            SpiceMainChannel *main_channel,
+                                            GAsyncReadyCallback callback,
+                                            gpointer user_data)
+{
+    SpicePulsePrivate *p = SPICE_PULSE(audio)->priv;
+    GSimpleAsyncResult *simple;
+    struct async_task *task = g_malloc0(sizeof(struct async_task));
+    pa_operation *op = NULL;
+
+    simple = g_simple_async_result_new(G_OBJECT(audio),
+                                       callback,
+                                       user_data,
+                                       pulse_stream_restore_info_async);
+#if GLIB_CHECK_VERSION(2,32,0)
+    g_simple_async_result_set_check_cancellable (simple, cancellable);
+#endif
+
+    task->res = simple;
+    task->pulse = g_object_ref(audio);
+    task->callback = callback;
+    task->user_data = user_data;
+    task->is_playback = is_playback;
+    task->main_channel = g_object_ref(main_channel);
+    task->pa_op = NULL;
+
+    if (cancellable) {
+        task->cancellable = g_object_ref(cancellable);
+        task->cancel_id = g_cancellable_connect(cancellable, G_CALLBACK(cancel_task), task, NULL);
+    }
+
+    /* If Playback/Record stream is created we use pulse API to get volume-info
+     * from those streams directly. If the stream is not created, retrieve last
+     * volume/mute values from Pulse database using the application name;
+     * If we already have retrieved volume-info from Pulse database then it is
+     * safe to return the volume-info we already have in <stream>info */
+
+    if (is_playback == TRUE &&
+            p->playback.stream != NULL &&
+            pa_stream_get_index(p->playback.stream) != PA_INVALID_INDEX) {
+        SPICE_DEBUG("Playback stream is created - get-sink-input-info");
+        p->playback.info_updated = FALSE;
+        op = pa_context_get_sink_input_info(p->context,
+                                            pa_stream_get_index(p->playback.stream),
+                                            sink_input_info_cb,
+                                            task);
+        if (!op)
+            goto fail;
+        task->pa_op = op;
+
+    } else if (is_playback == FALSE &&
+            p->record.stream != NULL &&
+            pa_stream_get_index(p->record.stream) != PA_INVALID_INDEX) {
+        SPICE_DEBUG("Record stream is created - get-source-output-info");
+        p->record.info_updated = FALSE;
+#if PA_CHECK_VERSION(1,0,0)
+        op = pa_context_get_source_output_info(p->context,
+                                               pa_stream_get_index(p->record.stream),
+                                               source_output_info_cb,
+                                               task);
+#else
+        op = pa_context_get_source_info_by_index(p->context,
+                                                 pa_stream_get_device_index(p->record.stream),
+                                                 source_info_cb,
+                                                 task);
+#endif
+        if (!op)
+            goto fail;
+        task->pa_op = op;
+
+    } else {
+        if (p->playback.info.name != NULL ||
+                p->record.info.name != NULL) {
+            /* If the pstream->info.name is set then we already have updated
+             * volume information. We can complete the request now */
+            SPICE_DEBUG("Return the volume-information we already have");
+            spice_pulse_complete_async_task(task, NULL);
+            return;
+        }
+
+        if (p->results == NULL) {
+            SPICE_DEBUG("Streams are not created - ext-stream-restore");
+            p->playback.info_updated = FALSE;
+            p->record.info_updated = FALSE;
+
+            if (pa_context_get_state(p->context) == PA_CONTEXT_READY) {
+                /* Restore value from pulse db */
+                op = pa_ext_stream_restore_read(p->context, stream_restore_read_cb, audio);
+                if (!op)
+                    goto fail;
+                task->pa_op = op;
+            } else {
+                /* It is possible that we want to get volume-info before the
+                 * context is in READY state. In this case, we wait for the
+                 * context state change to READY. */
+                p->pending_restore_task = task;
+            }
+        }
+    }
+
+    p->results = g_list_append(p->results, task);
+    SPICE_DEBUG ("Number of async task is %d", g_list_length(p->results));
+    return;
+
+fail:
+    if (!op) {
+        g_simple_async_report_error_in_idle(G_OBJECT(audio),
+                                            callback,
+                                            user_data,
+                                            SPICE_CLIENT_ERROR,
+                                            SPICE_CLIENT_ERROR_FAILED,
+                                            "Volume-Info failed: %s",
+                                            pa_strerror(pa_context_errno(p->context)));
+        free_async_task(task);
+    }
+}
+
+/* to avoid code duplication */
+static gboolean pulse_stream_restore_info_finish(gboolean is_playback,
+                                                 SpiceAudio *audio,
+                                                 GAsyncResult *res,
+                                                 gboolean *mute,
+                                                 guint8 *nchannels,
+                                                 guint16 **volume,
+                                                 GError **error)
+{
+    SpicePulsePrivate *p = SPICE_PULSE(audio)->priv;
+    struct stream *pstream = (is_playback) ? &p->playback : &p->record;
+    GSimpleAsyncResult *simple = (GSimpleAsyncResult *) res;
+
+    g_return_val_if_fail(g_simple_async_result_is_valid(res,
+        G_OBJECT(audio), pulse_stream_restore_info_async), FALSE);
+
+    if (g_simple_async_result_propagate_error(simple, error)) {
+        /* set out args that should have new alloc'ed memory to NULL */
+        if (volume != NULL) {
+            *volume = NULL;
+        }
+        return FALSE;
+    }
+
+    if (mute != NULL) {
+        *mute = (pstream->info.mute) ? TRUE : FALSE;
+    }
+
+    if (nchannels != NULL) {
+        *nchannels = pstream->info.channel_map.channels;
+    }
+
+    if (volume != NULL) {
+        gint i;
+        *volume = g_new(guint16, pstream->info.channel_map.channels);
+        for (i = 0; i < pstream->info.channel_map.channels; i++) {
+            (*volume)[i] = MIN(pstream->info.volume.values[i], G_MAXUINT16);
+            SPICE_DEBUG("(%s) volume at channel %d is %u",
+                        (is_playback) ? "playback" : "record", i, (*volume)[i]);
+        }
+    }
+
+    return g_simple_async_result_get_op_res_gboolean(simple);
+}
+
+static void spice_pulse_get_playback_volume_info_async(SpiceAudio *audio,
+                                                       GCancellable *cancellable,
+                                                       SpiceMainChannel *main_channel,
+                                                       GAsyncReadyCallback callback,
+                                                       gpointer user_data)
+{
+    pulse_stream_restore_info_async(TRUE, audio, cancellable, main_channel, callback, user_data);
+}
+
+static gboolean spice_pulse_get_playback_volume_info_finish(SpiceAudio *audio,
+                                                            GAsyncResult *res,
+                                                            gboolean *mute,
+                                                            guint8 *nchannels,
+                                                            guint16 **volume,
+                                                            GError **error)
+{
+    return pulse_stream_restore_info_finish(TRUE, audio, res, mute,
+                                            nchannels, volume, error);
+}
+
+static void spice_pulse_get_record_volume_info_async(SpiceAudio *audio,
+                                                     GCancellable *cancellable,
+                                                     SpiceMainChannel *main_channel,
+                                                     GAsyncReadyCallback callback,
+                                                     gpointer user_data)
+{
+    pulse_stream_restore_info_async(FALSE, audio, cancellable, main_channel, callback, user_data);
+}
+
+static gboolean spice_pulse_get_record_volume_info_finish(SpiceAudio *audio,
+                                                          GAsyncResult *res,
+                                                          gboolean *mute,
+                                                          guint8 *nchannels,
+                                                          guint16 **volume,
+                                                          GError **error)
+{
+    return pulse_stream_restore_info_finish(FALSE, audio, res, mute,
+                                            nchannels, volume, error);
+}
diff --git a/src/spice-pulse.h b/src/spice-pulse.h
new file mode 100644
index 0000000..819647e
--- /dev/null
+++ b/src/spice-pulse.h
@@ -0,0 +1,57 @@
+/* -*- 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_CLIENT_PULSE_H__
+#define __SPICE_CLIENT_PULSE_H__
+
+#include "spice-client.h"
+#include "spice-audio.h"
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_PULSE            (spice_pulse_get_type())
+#define SPICE_PULSE(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), SPICE_TYPE_PULSE, SpicePulse))
+#define SPICE_PULSE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), SPICE_TYPE_PULSE, SpicePulseClass))
+#define SPICE_IS_PULSE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), SPICE_TYPE_PULSE))
+#define SPICE_IS_PULSE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SPICE_TYPE_PULSE))
+#define SPICE_PULSE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), SPICE_TYPE_PULSE, SpicePulseClass))
+
+
+typedef struct _SpicePulse SpicePulse;
+typedef struct _SpicePulseClass SpicePulseClass;
+typedef struct _SpicePulsePrivate SpicePulsePrivate;
+
+struct _SpicePulse {
+    SpiceAudio parent;
+    SpicePulsePrivate *priv;
+    /* Do not add fields to this struct */
+};
+
+struct _SpicePulseClass {
+    SpiceAudioClass parent_class;
+    /* Do not add fields to this struct */
+};
+
+GType           spice_pulse_get_type(void);
+
+SpicePulse *spice_pulse_new(SpiceSession *session,
+                            GMainContext *context,
+                            const char *name);
+
+G_END_DECLS
+
+#endif /* __SPICE_CLIENT_PULSE_H__ */
diff --git a/src/spice-session-priv.h b/src/spice-session-priv.h
new file mode 100644
index 0000000..049973a
--- /dev/null
+++ b/src/spice-session-priv.h
@@ -0,0 +1,104 @@
+/* -*- 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_CLIENT_SESSION_PRIV_H__
+#define __SPICE_CLIENT_SESSION_PRIV_H__
+
+#include "config.h"
+
+#include <glib.h>
+#include <gio/gio.h>
+
+#ifdef USE_PHODAV
+#include <libphodav/phodav.h>
+#else
+typedef struct _PhodavServer PhodavServer;
+#endif
+
+#include "desktop-integration.h"
+#include "spice-session.h"
+#include "spice-gtk-session.h"
+#include "spice-channel-cache.h"
+#include "decode.h"
+
+G_BEGIN_DECLS
+
+#define WEBDAV_MAGIC_SIZE 16
+
+SpiceSession *spice_session_new_from_session(SpiceSession *session);
+
+void spice_session_set_connection_id(SpiceSession *session, int id);
+int spice_session_get_connection_id(SpiceSession *session);
+gboolean spice_session_get_client_provided_socket(SpiceSession *session);
+
+GSocketConnection* spice_session_channel_open_host(SpiceSession *session, SpiceChannel *channel,
+                                                   gboolean *use_tls, GError **error);
+void spice_session_channel_new(SpiceSession *session, SpiceChannel *channel);
+void spice_session_channel_migrate(SpiceSession *session, SpiceChannel *channel);
+
+void spice_session_set_mm_time(SpiceSession *session, guint32 time);
+guint32 spice_session_get_mm_time(SpiceSession *session);
+
+void spice_session_switching_disconnect(SpiceSession *session);
+void spice_session_start_migrating(SpiceSession *session,
+                                   gboolean full_migration);
+void spice_session_abort_migration(SpiceSession *session);
+void spice_session_set_migration_state(SpiceSession *session, SpiceSessionMigration state);
+
+void spice_session_set_port(SpiceSession *session, int port, gboolean tls);
+void spice_session_get_pubkey(SpiceSession *session, guint8 **pubkey, guint *size);
+guint spice_session_get_verify(SpiceSession *session);
+const gchar* spice_session_get_username(SpiceSession *session);
+const gchar* spice_session_get_password(SpiceSession *session);
+const gchar* spice_session_get_host(SpiceSession *session);
+const gchar* spice_session_get_cert_subject(SpiceSession *session);
+const gchar* spice_session_get_ciphers(SpiceSession *session);
+const gchar* spice_session_get_ca_file(SpiceSession *session);
+void spice_session_get_ca(SpiceSession *session, guint8 **ca, guint *size);
+
+void spice_session_set_caches_hints(SpiceSession *session,
+                                    uint32_t pci_ram_size,
+                                    uint32_t n_display_channels);
+void spice_session_get_caches(SpiceSession *session,
+                              display_cache **images,
+                              SpiceGlzDecoderWindow **glz_window);
+void spice_session_palettes_clear(SpiceSession *session);
+void spice_session_images_clear(SpiceSession *session);
+void spice_session_migrate_end(SpiceSession *session);
+gboolean spice_session_migrate_after_main_init(SpiceSession *session);
+SpiceChannel* spice_session_lookup_channel(SpiceSession *session, gint id, gint type);
+void spice_session_set_uuid(SpiceSession *session, guint8 uuid[16]);
+void spice_session_set_name(SpiceSession *session, const gchar *name);
+gboolean spice_session_is_playback_active(SpiceSession *session);
+guint32 spice_session_get_playback_latency(SpiceSession *session);
+void spice_session_sync_playback_latency(SpiceSession *session);
+const gchar* spice_session_get_shared_dir(SpiceSession *session);
+void spice_session_set_shared_dir(SpiceSession *session, const gchar *dir);
+gboolean spice_session_get_audio_enabled(SpiceSession *session);
+gboolean spice_session_get_smartcard_enabled(SpiceSession *session);
+gboolean spice_session_get_usbredir_enabled(SpiceSession *session);
+
+const guint8* spice_session_get_webdav_magic(SpiceSession *session);
+PhodavServer *spice_session_get_webdav_server(SpiceSession *session);
+PhodavServer* channel_webdav_server_new(SpiceSession *session);
+guint spice_session_get_n_display_channels(SpiceSession *session);
+void spice_session_set_main_channel(SpiceSession *session, SpiceChannel *channel);
+gboolean spice_session_set_migration_session(SpiceSession *session, SpiceSession *mig_session);
+SpiceAudio *spice_audio_get(SpiceSession *session, GMainContext *context);
+G_END_DECLS
+
+#endif /* __SPICE_CLIENT_SESSION_PRIV_H__ */
diff --git a/src/spice-session.c b/src/spice-session.c
new file mode 100644
index 0000000..778d82a
--- /dev/null
+++ b/src/spice-session.c
@@ -0,0 +1,2728 @@
+/* -*- 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 <gio/gio.h>
+#include <glib.h>
+#ifdef G_OS_UNIX
+#include <gio/gunixsocketaddress.h>
+#endif
+#include "common/ring.h"
+
+#include "spice-client.h"
+#include "spice-common.h"
+#include "spice-channel-priv.h"
+#include "spice-util-priv.h"
+#include "spice-session-priv.h"
+#include "gio-coroutine.h"
+#include "glib-compat.h"
+#include "wocky-http-proxy.h"
+#include "spice-uri-priv.h"
+#include "channel-playback-priv.h"
+#include "spice-audio.h"
+
+struct channel {
+    SpiceChannel      *channel;
+    RingItem          link;
+};
+
+#define IMAGES_CACHE_SIZE_DEFAULT (1024 * 1024 * 80)
+#define MIN_GLZ_WINDOW_SIZE_DEFAULT (1024 * 1024 * 12)
+#define MAX_GLZ_WINDOW_SIZE_DEFAULT MIN((LZ_MAX_WINDOW_SIZE * 4), 1024 * 1024 * 64)
+
+struct _SpiceSessionPrivate {
+    char              *host;
+    char              *unix_path;
+    char              *port;
+    char              *tls_port;
+    char              *username;
+    char              *password;
+    char              *ca_file;
+    char              *ciphers;
+    GByteArray        *pubkey;
+    GByteArray        *ca;
+    char              *cert_subject;
+    guint             verify;
+    gboolean          read_only;
+    SpiceURI          *proxy;
+    gchar             *shared_dir;
+    gboolean          share_dir_ro;
+
+    /* whether to enable audio */
+    gboolean          audio;
+
+    /* whether to enable smartcard event forwarding to the server */
+    gboolean          smartcard;
+
+    /* list of certificates to use for the software smartcard reader if
+     * enabled. For now, it has to contain exactly 3 certificates for
+     * the software reader to be functional
+     */
+    GStrv             smartcard_certificates;
+
+    /* path to the local certificate database to use to lookup the
+     * certificates stored in 'certificates'. If NULL, libcacard will
+     * fallback to using a default database.
+     */
+    char *            smartcard_db;
+
+    /* whether to enable USB redirection */
+    gboolean          usbredir;
+
+    /* Set when a usbredir channel has requested the keyboard grab to be
+       temporarily released (because it is going to invoke policykit) */
+    gboolean          inhibit_keyboard_grab;
+
+    GStrv             disable_effects;
+    GStrv             secure_channels;
+    gint              color_depth;
+
+    int               connection_id;
+    int               protocol;
+    SpiceChannel      *cmain; /* weak reference */
+    Ring              channels;
+    guint32           mm_time;
+    gboolean          client_provided_sockets;
+    guint64           mm_time_at_clock;
+    SpiceSession      *migration;
+    GList             *migration_left;
+    SpiceSessionMigration migration_state;
+    gboolean          full_migration; /* seamless migration indicator */
+    guint             disconnecting;
+    gboolean          migrate_wait_init;
+    guint             after_main_init;
+    gboolean          for_migration;
+
+    display_cache     *images;
+    display_cache     *palettes;
+    SpiceGlzDecoderWindow *glz_window;
+    int               images_cache_size;
+    int               glz_window_size;
+    uint32_t          pci_ram_size;
+    uint32_t          n_display_channels;
+    guint8            uuid[16];
+    gchar             *name;
+
+    /* associated objects */
+    SpiceAudio        *audio_manager;
+    SpiceUsbDeviceManager *usb_manager;
+    SpicePlaybackChannel *playback_channel;
+    PhodavServer      *webdav;
+};
+
+
+/**
+ * SECTION:spice-session
+ * @short_description: handles connection details, and active channels
+ * @title: Spice Session
+ * @section_id:
+ * @see_also: #SpiceChannel, and the GTK widget #SpiceDisplay
+ * @stability: Stable
+ * @include: spice-session.h
+ *
+ * The #SpiceSession class handles all the #SpiceChannel connections.
+ * It's also the class that contains connections informations, such as
+ * #SpiceSession:host and #SpiceSession:port.
+ *
+ * You can simply set the property #SpiceSession:uri to something like
+ * "spice://127.0.0.1?port=5930" to configure your connection details.
+ *
+ * You may want to connect to #SpiceSession::channel-new signal, to be
+ * informed of the availability of channels and to interact with
+ * them.
+ *
+ * For example, when the #SpiceInputsChannel is available and get the
+ * event #SPICE_CHANNEL_OPENED, you can send key events with
+ * spice_inputs_key_press(). When the #SpiceMainChannel is available,
+ * you can start sharing the clipboard...  .
+ *
+ *
+ * Once #SpiceSession properties set, you can call
+ * spice_session_connect() to start connecting and communicating with
+ * a Spice server.
+ */
+
+/* ------------------------------------------------------------------ */
+/* gobject glue                                                       */
+
+#define SPICE_SESSION_GET_PRIVATE(obj) \
+    (G_TYPE_INSTANCE_GET_PRIVATE ((obj), SPICE_TYPE_SESSION, SpiceSessionPrivate))
+
+G_DEFINE_TYPE (SpiceSession, spice_session, G_TYPE_OBJECT);
+
+/* Properties */
+enum {
+    PROP_0,
+    PROP_HOST,
+    PROP_PORT,
+    PROP_TLS_PORT,
+    PROP_PASSWORD,
+    PROP_CA_FILE,
+    PROP_CIPHERS,
+    PROP_IPV4,
+    PROP_IPV6,
+    PROP_PROTOCOL,
+    PROP_URI,
+    PROP_CLIENT_SOCKETS,
+    PROP_PUBKEY,
+    PROP_CERT_SUBJECT,
+    PROP_VERIFY,
+    PROP_MIGRATION_STATE,
+    PROP_AUDIO,
+    PROP_SMARTCARD,
+    PROP_SMARTCARD_CERTIFICATES,
+    PROP_SMARTCARD_DB,
+    PROP_USBREDIR,
+    PROP_INHIBIT_KEYBOARD_GRAB,
+    PROP_DISABLE_EFFECTS,
+    PROP_COLOR_DEPTH,
+    PROP_READ_ONLY,
+    PROP_CACHE_SIZE,
+    PROP_GLZ_WINDOW_SIZE,
+    PROP_UUID,
+    PROP_NAME,
+    PROP_CA,
+    PROP_PROXY,
+    PROP_SECURE_CHANNELS,
+    PROP_SHARED_DIR,
+    PROP_SHARE_DIR_RO,
+    PROP_USERNAME,
+    PROP_UNIX_PATH,
+};
+
+/* signals */
+enum {
+    SPICE_SESSION_CHANNEL_NEW,
+    SPICE_SESSION_CHANNEL_DESTROY,
+    SPICE_SESSION_MM_TIME_RESET,
+    SPICE_SESSION_LAST_SIGNAL,
+};
+
+static guint signals[SPICE_SESSION_LAST_SIGNAL];
+
+static void spice_session_channel_destroy(SpiceSession *session, SpiceChannel *channel);
+
+static void update_proxy(SpiceSession *self, const gchar *str)
+{
+    SpiceSessionPrivate *s = self->priv;
+    SpiceURI *proxy = NULL;
+    GError *error = NULL;
+
+    if (str == NULL)
+        str = g_getenv("SPICE_PROXY");
+    if (str == NULL || *str == 0) {
+        g_clear_object(&s->proxy);
+        return;
+    }
+
+    proxy = spice_uri_new();
+    if (!spice_uri_parse(proxy, str, &error))
+        g_clear_object(&proxy);
+    if (error) {
+        g_warning("%s", error->message);
+        g_clear_error(&error);
+    }
+
+    if (proxy != NULL) {
+        g_clear_object(&s->proxy);
+        s->proxy = proxy;
+    }
+}
+
+static void spice_session_init(SpiceSession *session)
+{
+    SpiceSessionPrivate *s;
+    gchar *channels;
+
+    SPICE_DEBUG("New session (compiled from package " PACKAGE_STRING ")");
+    s = session->priv = SPICE_SESSION_GET_PRIVATE(session);
+
+    channels = spice_channel_supported_string();
+    SPICE_DEBUG("Supported channels: %s", channels);
+    g_free(channels);
+
+    ring_init(&s->channels);
+    s->images = cache_new((GDestroyNotify)pixman_image_unref);
+    s->glz_window = glz_decoder_window_new();
+    update_proxy(session, NULL);
+}
+
+static void
+session_disconnect(SpiceSession *self, gboolean keep_main)
+{
+    SpiceSessionPrivate *s;
+    struct channel *item;
+    RingItem *ring, *next;
+
+    s = self->priv;
+
+    for (ring = ring_get_head(&s->channels); ring != NULL; ring = next) {
+        next = ring_next(&s->channels, ring);
+        item = SPICE_CONTAINEROF(ring, struct channel, link);
+
+        if (keep_main && item->channel == s->cmain) {
+            spice_channel_disconnect(item->channel, SPICE_CHANNEL_NONE);
+        } else {
+            spice_session_channel_destroy(self, item->channel);
+        }
+    }
+
+    s->connection_id = 0;
+
+    g_free(s->name);
+    s->name = NULL;
+    memset(s->uuid, 0, sizeof(s->uuid));
+
+    spice_session_abort_migration(self);
+}
+
+static void
+spice_session_dispose(GObject *gobject)
+{
+    SpiceSession *session = SPICE_SESSION(gobject);
+    SpiceSessionPrivate *s = session->priv;
+
+    SPICE_DEBUG("session dispose");
+
+    session_disconnect(session, FALSE);
+
+    g_warn_if_fail(s->migration == NULL);
+    g_warn_if_fail(s->migration_left == NULL);
+    g_warn_if_fail(s->after_main_init == 0);
+    g_warn_if_fail(s->disconnecting == 0);
+
+    g_clear_object(&s->audio_manager);
+    g_clear_object(&s->usb_manager);
+    g_clear_object(&s->proxy);
+    g_clear_object(&s->webdav);
+
+    /* Chain up to the parent class */
+    if (G_OBJECT_CLASS(spice_session_parent_class)->dispose)
+        G_OBJECT_CLASS(spice_session_parent_class)->dispose(gobject);
+}
+
+static void
+spice_session_finalize(GObject *gobject)
+{
+    SpiceSession *session = SPICE_SESSION(gobject);
+    SpiceSessionPrivate *s = session->priv;
+
+    /* release stuff */
+    g_free(s->unix_path);
+    g_free(s->host);
+    g_free(s->port);
+    g_free(s->tls_port);
+    g_free(s->username);
+    g_free(s->password);
+    g_free(s->ca_file);
+    g_free(s->ciphers);
+    g_free(s->cert_subject);
+    g_strfreev(s->smartcard_certificates);
+    g_free(s->smartcard_db);
+    g_strfreev(s->disable_effects);
+    g_strfreev(s->secure_channels);
+    g_free(s->shared_dir);
+
+    g_clear_pointer(&s->images, cache_unref);
+    glz_decoder_window_destroy(s->glz_window);
+
+    g_clear_pointer(&s->pubkey, g_byte_array_unref);
+    g_clear_pointer(&s->ca, g_byte_array_unref);
+
+    /* Chain up to the parent class */
+    if (G_OBJECT_CLASS(spice_session_parent_class)->finalize)
+        G_OBJECT_CLASS(spice_session_parent_class)->finalize(gobject);
+}
+
+#define URI_SCHEME_SPICE "spice://"
+#define URI_SCHEME_SPICE_UNIX "spice+unix://"
+#define URI_QUERY_START ";?"
+#define URI_QUERY_SEP   ";&"
+
+static gchar* spice_uri_create(SpiceSession *session)
+{
+    SpiceSessionPrivate *s = session->priv;
+
+    if (s->unix_path != NULL) {
+        return g_strdup_printf(URI_SCHEME_SPICE_UNIX "%s", s->unix_path);
+    } else if (s->host != NULL) {
+        g_return_val_if_fail(s->port != NULL || s->tls_port != NULL, NULL);
+
+        GString *str = g_string_new(URI_SCHEME_SPICE);
+
+        g_string_append(str, s->host);
+        g_string_append(str, "?");
+        if (s->port != NULL) {
+            g_string_append_printf(str, "port=%s&", s->port);
+        }
+        if (s->tls_port != NULL) {
+            g_string_append_printf(str, "tls-port=%s", s->tls_port);
+        }
+        return g_string_free(str, FALSE);
+    }
+
+    g_return_val_if_reached(NULL);
+}
+
+static int spice_parse_uri(SpiceSession *session, const char *original_uri)
+{
+    SpiceSessionPrivate *s = session->priv;
+    gchar *host = NULL, *port = NULL, *tls_port = NULL, *uri = NULL, *username = NULL, *password = NULL;
+    gchar *path = NULL;
+    gchar *unescaped_path = NULL;
+    gchar *authority = NULL;
+    gchar *query = NULL;
+    gchar *tmp = NULL;
+
+    g_return_val_if_fail(original_uri != NULL, -1);
+
+    uri = g_strdup(original_uri);
+
+    if (g_str_has_prefix(uri, URI_SCHEME_SPICE_UNIX)) {
+        path = uri + strlen(URI_SCHEME_SPICE_UNIX);
+        goto end;
+    }
+
+    /* Break up the URI into its various parts, scheme, authority,
+     * path (ignored) and query
+     */
+    if (!g_str_has_prefix(uri, URI_SCHEME_SPICE)) {
+        g_warning("Expected a URI scheme of '%s' in URI '%s'",
+                  URI_SCHEME_SPICE, uri);
+        goto fail;
+    }
+    authority = uri + strlen(URI_SCHEME_SPICE);
+
+    tmp = strchr(authority, '@');
+    if (tmp) {
+        tmp[0] = '\0';
+        username = g_uri_unescape_string(authority, NULL);
+        authority = ++tmp;
+        tmp = NULL;
+    }
+
+    path = strchr(authority, '/');
+    if (path) {
+        path[0] = '\0';
+        path++;
+    }
+
+    if (path) {
+        size_t prefix = strcspn(path, URI_QUERY_START);
+        query = path + prefix;
+    } else {
+        size_t prefix = strcspn(authority, URI_QUERY_START);
+        query = authority + prefix;
+    }
+
+    if (query && query[0]) {
+        query[0] = '\0';
+        query++;
+    }
+
+    /* Now process the individual parts */
+
+    if (authority[0] == '[') {
+        tmp = strchr(authority, ']');
+        if (!tmp) {
+            g_warning("Missing closing ']' in authority for URI '%s'", uri);
+            goto fail;
+        }
+        tmp[0] = '\0';
+        tmp++;
+        host = g_strdup(authority + 1);
+        if (tmp[0] == ':')
+            port = g_strdup(tmp + 1);
+    } else {
+        tmp = strchr(authority, ':');
+        if (tmp) {
+            *tmp = '\0';
+            tmp++;
+            port = g_strdup(tmp);
+        }
+        host = g_uri_unescape_string(authority, NULL);
+    }
+
+    if (path && !(g_str_equal(path, "") ||
+                  g_str_equal(path, "/"))) {
+        g_warning("Unexpected path data '%s' for URI '%s'", path, uri);
+        /* don't fail, just ignore */
+    }
+    unescaped_path = g_uri_unescape_string(path, NULL);
+    path = NULL;
+
+    while (query && query[0] != '\0') {
+        gchar key[32], value[128];
+        gchar **target_key;
+
+        int len;
+        if (sscanf(query, "%31[-a-zA-Z0-9]=%n", key, &len) != 1) {
+            spice_warning("Failed to parse key in URI '%s'", query);
+            goto fail;
+        }
+
+        query += len;
+        if (*query == '\0') {
+            spice_warning ("key '%s' without value", key);
+            break;
+        } else if (*query == ';' || *query == '&') {
+            /* another argument */
+            query++;
+            continue;
+        }
+
+        if (sscanf(query, "%127[^;&]%n", value, &len) != 1) {
+            spice_warning("Failed to parse value of key '%s' in URI '%s'", key, query);
+            goto fail;
+        }
+
+        query += len;
+        if (*query)
+            query++;
+
+        target_key = NULL;
+        if (g_str_equal(key, "port")) {
+            target_key = &port;
+        } else if (g_str_equal(key, "tls-port")) {
+            target_key = &tls_port;
+        } else if (g_str_equal(key, "password")) {
+            target_key = &password;
+            g_warning("password may be visible in process listings");
+        } else {
+            g_warning("unknown key in spice URI parsing: '%s'", key);
+            goto fail;
+        }
+        if (target_key) {
+            if (*target_key) {
+                g_warning("Double set of '%s' in URI '%s'", key, uri);
+                goto fail;
+            }
+            *target_key = g_uri_unescape_string(value, NULL);
+        }
+    }
+
+    if (port == NULL && tls_port == NULL) {
+        g_warning("Missing port or tls-port in spice URI '%s'", uri);
+        goto fail;
+    }
+
+end:
+    /* parsed ok -> apply */
+    g_free(uri);
+    g_free(unescaped_path);
+    g_free(s->unix_path);
+    g_free(s->host);
+    g_free(s->port);
+    g_free(s->tls_port);
+    g_free(s->username);
+    g_free(s->password);
+    s->unix_path = g_strdup(path);
+    s->host = host;
+    s->port = port;
+    s->tls_port = tls_port;
+    s->username = username;
+    s->password = password;
+    return 0;
+
+fail:
+    g_free(uri);
+    g_free(unescaped_path);
+    g_free(host);
+    g_free(port);
+    g_free(tls_port);
+    g_free(username);
+    g_free(password);
+    return -1;
+}
+
+static void spice_session_get_property(GObject    *gobject,
+                                       guint       prop_id,
+                                       GValue     *value,
+                                       GParamSpec *pspec)
+{
+    SpiceSession *session = SPICE_SESSION(gobject);
+    SpiceSessionPrivate *s = session->priv;
+
+    switch (prop_id) {
+    case PROP_HOST:
+        g_value_set_string(value, s->host);
+	break;
+    case PROP_UNIX_PATH:
+        g_value_set_string(value, s->unix_path);
+        break;
+    case PROP_PORT:
+        g_value_set_string(value, s->port);
+	break;
+    case PROP_TLS_PORT:
+        g_value_set_string(value, s->tls_port);
+	break;
+    case PROP_USERNAME:
+        g_value_set_string(value, s->username);
+	break;
+    case PROP_PASSWORD:
+        g_value_set_string(value, s->password);
+	break;
+    case PROP_CA_FILE:
+        g_value_set_string(value, s->ca_file);
+	break;
+    case PROP_CIPHERS:
+        g_value_set_string(value, s->ciphers);
+	break;
+    case PROP_PROTOCOL:
+        g_value_set_int(value, s->protocol);
+	break;
+    case PROP_URI:
+        g_value_take_string(value, spice_uri_create(session));
+        break;
+    case PROP_CLIENT_SOCKETS:
+        g_value_set_boolean(value, s->client_provided_sockets);
+	break;
+    case PROP_PUBKEY:
+        g_value_set_boxed(value, s->pubkey);
+	break;
+    case PROP_CA:
+        g_value_set_boxed(value, s->ca);
+	break;
+    case PROP_CERT_SUBJECT:
+        g_value_set_string(value, s->cert_subject);
+	break;
+    case PROP_VERIFY:
+        g_value_set_flags(value, s->verify);
+        break;
+    case PROP_MIGRATION_STATE:
+        g_value_set_enum(value, s->migration_state);
+        break;
+    case PROP_SMARTCARD:
+        g_value_set_boolean(value, s->smartcard);
+        break;
+    case PROP_SMARTCARD_CERTIFICATES:
+        g_value_set_boxed(value, s->smartcard_certificates);
+        break;
+    case PROP_SMARTCARD_DB:
+        g_value_set_string(value, s->smartcard_db);
+        break;
+    case PROP_USBREDIR:
+        g_value_set_boolean(value, s->usbredir);
+        break;
+    case PROP_INHIBIT_KEYBOARD_GRAB:
+        g_value_set_boolean(value, s->inhibit_keyboard_grab);
+        break;
+    case PROP_DISABLE_EFFECTS:
+        g_value_set_boxed(value, s->disable_effects);
+        break;
+    case PROP_SECURE_CHANNELS:
+        g_value_set_boxed(value, s->secure_channels);
+        break;
+    case PROP_COLOR_DEPTH:
+        g_value_set_int(value, s->color_depth);
+        break;
+    case PROP_AUDIO:
+        g_value_set_boolean(value, s->audio);
+        break;
+    case PROP_READ_ONLY:
+        g_value_set_boolean(value, s->read_only);
+        break;
+    case PROP_CACHE_SIZE:
+        g_value_set_int(value, s->images_cache_size);
+        break;
+    case PROP_GLZ_WINDOW_SIZE:
+        g_value_set_int(value, s->glz_window_size);
+        break;
+    case PROP_NAME:
+        g_value_set_string(value, s->name);
+	break;
+    case PROP_UUID:
+        g_value_set_pointer(value, s->uuid);
+	break;
+    case PROP_PROXY:
+        g_value_take_string(value, spice_uri_to_string(s->proxy));
+	break;
+    case PROP_SHARED_DIR:
+        g_value_set_string(value, spice_session_get_shared_dir(session));
+        break;
+    case PROP_SHARE_DIR_RO:
+        g_value_set_boolean(value, s->share_dir_ro);
+        break;
+    default:
+	G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
+	break;
+    }
+}
+
+static void spice_session_set_property(GObject      *gobject,
+                                       guint         prop_id,
+                                       const GValue *value,
+                                       GParamSpec   *pspec)
+{
+    SpiceSession *session = SPICE_SESSION(gobject);
+    SpiceSessionPrivate *s = session->priv;
+    const char *str;
+
+    switch (prop_id) {
+    case PROP_HOST:
+        g_free(s->host);
+        s->host = g_value_dup_string(value);
+        break;
+    case PROP_UNIX_PATH:
+        g_free(s->unix_path);
+        s->unix_path = g_value_dup_string(value);
+        break;
+    case PROP_PORT:
+        g_free(s->port);
+        s->port = g_value_dup_string(value);
+        break;
+    case PROP_TLS_PORT:
+        g_free(s->tls_port);
+        s->tls_port = g_value_dup_string(value);
+        break;
+    case PROP_USERNAME:
+        g_free(s->username);
+        s->username = g_value_dup_string(value);
+        break;
+    case PROP_PASSWORD:
+        g_free(s->password);
+        s->password = g_value_dup_string(value);
+        break;
+    case PROP_CA_FILE:
+        g_free(s->ca_file);
+        s->ca_file = g_value_dup_string(value);
+        break;
+    case PROP_CIPHERS:
+        g_free(s->ciphers);
+        s->ciphers = g_value_dup_string(value);
+        break;
+    case PROP_PROTOCOL:
+        s->protocol = g_value_get_int(value);
+        break;
+    case PROP_URI:
+        str = g_value_get_string(value);
+        if (str != NULL)
+            spice_parse_uri(session, str);
+        break;
+    case PROP_CLIENT_SOCKETS:
+        s->client_provided_sockets = g_value_get_boolean(value);
+        break;
+    case PROP_PUBKEY:
+        if (s->pubkey)
+            g_byte_array_unref(s->pubkey);
+        s->pubkey = g_value_dup_boxed(value);
+        if (s->pubkey)
+            s->verify |= SPICE_SESSION_VERIFY_PUBKEY;
+        else
+            s->verify &= ~SPICE_SESSION_VERIFY_PUBKEY;
+	break;
+    case PROP_CERT_SUBJECT:
+        g_free(s->cert_subject);
+        s->cert_subject = g_value_dup_string(value);
+        if (s->cert_subject)
+            s->verify |= SPICE_SESSION_VERIFY_SUBJECT;
+        else
+            s->verify &= ~SPICE_SESSION_VERIFY_SUBJECT;
+        break;
+    case PROP_VERIFY:
+        s->verify = g_value_get_flags(value);
+        break;
+    case PROP_MIGRATION_STATE:
+        s->migration_state = g_value_get_enum(value);
+        break;
+    case PROP_SMARTCARD:
+        s->smartcard = g_value_get_boolean(value);
+        break;
+    case PROP_SMARTCARD_CERTIFICATES:
+        g_strfreev(s->smartcard_certificates);
+        s->smartcard_certificates = g_value_dup_boxed(value);
+        break;
+    case PROP_SMARTCARD_DB:
+        g_free(s->smartcard_db);
+        s->smartcard_db = g_value_dup_string(value);
+        break;
+    case PROP_USBREDIR:
+        s->usbredir = g_value_get_boolean(value);
+        break;
+    case PROP_INHIBIT_KEYBOARD_GRAB:
+        s->inhibit_keyboard_grab = g_value_get_boolean(value);
+        break;
+    case PROP_DISABLE_EFFECTS:
+        g_strfreev(s->disable_effects);
+        s->disable_effects = g_value_dup_boxed(value);
+        break;
+    case PROP_SECURE_CHANNELS:
+        g_strfreev(s->secure_channels);
+        s->secure_channels = g_value_dup_boxed(value);
+        break;
+    case PROP_COLOR_DEPTH:
+        s->color_depth = g_value_get_int(value);
+        break;
+    case PROP_AUDIO:
+        s->audio = g_value_get_boolean(value);
+        break;
+    case PROP_READ_ONLY:
+        s->read_only = g_value_get_boolean(value);
+        g_coroutine_object_notify(gobject, "read-only");
+        break;
+    case PROP_CACHE_SIZE:
+        s->images_cache_size = g_value_get_int(value);
+        break;
+    case PROP_GLZ_WINDOW_SIZE:
+        s->glz_window_size = g_value_get_int(value);
+        break;
+    case PROP_CA:
+        g_clear_pointer(&s->ca, g_byte_array_unref);
+        s->ca = g_value_dup_boxed(value);
+        break;
+    case PROP_PROXY:
+        update_proxy(session, g_value_get_string(value));
+        break;
+    case PROP_SHARED_DIR:
+        spice_session_set_shared_dir(session, g_value_get_string(value));
+        break;
+    case PROP_SHARE_DIR_RO:
+        s->share_dir_ro = g_value_get_boolean(value);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
+        break;
+    }
+}
+
+static void spice_session_class_init(SpiceSessionClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+
+    _wocky_http_proxy_get_type();
+    _wocky_https_proxy_get_type();
+
+    gobject_class->dispose      = spice_session_dispose;
+    gobject_class->finalize     = spice_session_finalize;
+    gobject_class->get_property = spice_session_get_property;
+    gobject_class->set_property = spice_session_set_property;
+
+    /**
+     * SpiceSession:host:
+     *
+     * URL of the SPICE host to connect to
+     *
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_HOST,
+         g_param_spec_string("host",
+                             "Host",
+                             "Remote host",
+                             "localhost",
+                             G_PARAM_READWRITE |
+                             G_PARAM_CONSTRUCT |
+                             G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceSession:unix-path:
+     *
+     * Path of the Unix socket to connect to
+     *
+     * Since: 0.28
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_UNIX_PATH,
+         g_param_spec_string("unix-path",
+                             "Unix path",
+                             "Unix path",
+                             NULL,
+                             G_PARAM_READWRITE |
+                             G_PARAM_CONSTRUCT |
+                             G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceSession:port:
+     *
+     * Port to connect to for unencrypted sessions
+     *
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_PORT,
+         g_param_spec_string("port",
+                             "Port",
+                             "Remote port (plaintext)",
+                             NULL,
+                             G_PARAM_READWRITE |
+                             G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceSession:tls-port:
+     *
+     * Port to connect to for TLS sessions
+     *
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_TLS_PORT,
+         g_param_spec_string("tls-port",
+                             "TLS port",
+                             "Remote port (encrypted)",
+                             NULL,
+                             G_PARAM_READWRITE |
+                             G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceSession:username:
+     *
+     * Username to use
+     *
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_USERNAME,
+         g_param_spec_string("username",
+                             "Username",
+                             "Username used for SASL connections",
+                             NULL,
+                             G_PARAM_READWRITE |
+                             G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceSession:password:
+     *
+     * TLS password to use
+     *
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_PASSWORD,
+         g_param_spec_string("password",
+                             "Password",
+                             "",
+                             NULL,
+                             G_PARAM_READWRITE |
+                             G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceSession:ca-file:
+     *
+     * File holding the CA certificates for the host the client is
+     * connecting to
+     *
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_CA_FILE,
+         g_param_spec_string("ca-file",
+                             "CA file",
+                             "File holding the CA certificates",
+                             NULL,
+                             G_PARAM_READWRITE |
+                             G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceSession:ciphers:
+     *
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_CIPHERS,
+         g_param_spec_string("ciphers",
+                             "Ciphers",
+                             "SSL cipher list",
+                             NULL,
+                             G_PARAM_READWRITE |
+                             G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceSession:protocol:
+     *
+     * Version of the SPICE protocol to use
+     *
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_PROTOCOL,
+         g_param_spec_int("protocol",
+                          "Protocol",
+                          "Spice protocol major version",
+                          1, 2, 2,
+                          G_PARAM_READWRITE |
+                          G_PARAM_CONSTRUCT |
+                          G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceSession:uri:
+     *
+     * URI of the SPICE host to connect to. The URI is of the form
+     * spice://hostname?port=XXX or spice://hostname?tls_port=XXX
+     *
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_URI,
+         g_param_spec_string("uri",
+                             "URI",
+                             "Spice connection URI",
+                             NULL,
+                             G_PARAM_READWRITE |
+                             G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceSession:client-sockets:
+     *
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_CLIENT_SOCKETS,
+         g_param_spec_boolean("client-sockets",
+                          "Client sockets",
+                          "Sockets are provided by the client",
+                          FALSE,
+                          G_PARAM_READWRITE |
+                          G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceSession:pubkey:
+     *
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_PUBKEY,
+         g_param_spec_boxed("pubkey",
+                            "Pub Key",
+                            "Public key to check",
+                            G_TYPE_BYTE_ARRAY,
+                            G_PARAM_READWRITE |
+                            G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceSession:cert-subject:
+     *
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_CERT_SUBJECT,
+         g_param_spec_string("cert-subject",
+                             "Cert Subject",
+                             "Certificate subject to check",
+                             NULL,
+                             G_PARAM_READWRITE |
+                             G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceSession:verify:
+     *
+     * #SpiceSessionVerify bit field indicating which parts of the peer
+     * certificate should be checked
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_VERIFY,
+         g_param_spec_flags("verify",
+                            "Verify",
+                            "Certificate verification parameters",
+                            SPICE_TYPE_SESSION_VERIFY,
+                            SPICE_SESSION_VERIFY_HOSTNAME,
+                            G_PARAM_READWRITE |
+                            G_PARAM_CONSTRUCT |
+                            G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceSession:migration-state:
+     *
+     * #SpiceSessionMigration bit field indicating if a migration is in
+     * progress
+     *
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_MIGRATION_STATE,
+         g_param_spec_enum("migration-state",
+                           "Migration state",
+                           "Migration state",
+                           SPICE_TYPE_SESSION_MIGRATION,
+                           SPICE_SESSION_MIGRATION_NONE,
+                           G_PARAM_READABLE |
+                           G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceSession:disable-effects:
+     *
+     * A string array of effects to disable. The settings will
+     * be applied on new display channels. The following effets can be
+     * disabled "wallpaper", "font-smooth", "animation", and "all",
+     * which will disable all the effects. If NULL, don't apply changes.
+     *
+     * Since: 0.7
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_DISABLE_EFFECTS,
+         g_param_spec_boxed ("disable-effects",
+                             "Disable effects",
+                             "Comma-separated effects to disable",
+                             G_TYPE_STRV,
+                             G_PARAM_READWRITE |
+                             G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceSession:color-depth:
+     *
+     * Display color depth to set on new display channels. If 0, don't set.
+     *
+     * Since: 0.7
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_COLOR_DEPTH,
+         g_param_spec_int("color-depth",
+                          "Color depth",
+                          "Display channel color depth",
+                          0, 32, 0,
+                          G_PARAM_READWRITE |
+                          G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceSession:enable-smartcard:
+     *
+     * If set to TRUE, the smartcard channel will be enabled and smartcard
+     * events will be forwarded to the guest
+     *
+     * Since: 0.7
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_SMARTCARD,
+         g_param_spec_boolean("enable-smartcard",
+                          "Enable smartcard event forwarding",
+                          "Forward smartcard events to the SPICE server",
+                          FALSE,
+                          G_PARAM_READWRITE |
+                          G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceSession:enable-audio:
+     *
+     * If set to TRUE, the audio channels will be enabled for
+     * playback and recording.
+     *
+     * Since: 0.8
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_AUDIO,
+         g_param_spec_boolean("enable-audio",
+                          "Enable audio channels",
+                          "Enable audio channels",
+                          TRUE,
+                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
+                          G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceSession:smartcard-certificates:
+     *
+     * This property is used when one wants to simulate a smartcard with no
+     * hardware smartcard reader. If it's set to a NULL-terminated string
+     * array containing the names of 3 valid certificates, these will be
+     * used to simulate a smartcard in the guest
+     * See also spice_smartcard_manager_insert_card()
+     *
+     * Since: 0.7
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_SMARTCARD_CERTIFICATES,
+         g_param_spec_boxed("smartcard-certificates",
+                            "Smartcard certificates",
+                            "Smartcard certificates for software-based smartcards",
+                            G_TYPE_STRV,
+                            G_PARAM_READABLE |
+                            G_PARAM_WRITABLE |
+                            G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceSession:smartcard-db:
+     *
+     * Path to the NSS certificate database containing the certificates to
+     * use to simulate a software smartcard
+     *
+     * Since: 0.7
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_SMARTCARD_DB,
+         g_param_spec_string("smartcard-db",
+                              "Smartcard certificate database",
+                              "Path to the database for smartcard certificates",
+                              NULL,
+                              G_PARAM_READABLE |
+                              G_PARAM_WRITABLE |
+                              G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceSession:enable-usbredir:
+     *
+     * If set to TRUE, the usbredir channel will be enabled and USB devices
+     * can be redirected to the guest
+     *
+     * Since: 0.8
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_USBREDIR,
+         g_param_spec_boolean("enable-usbredir",
+                          "Enable USB device redirection",
+                          "Forward USB devices to the SPICE server",
+                          TRUE,
+                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
+                          G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceSession::inhibit-keyboard-grab:
+     *
+     * This boolean is set by the usbredir channel to indicate to #SpiceDisplay
+     * that the keyboard grab should be temporarily released, because it is
+     * going to invoke policykit. It will get reset when the usbredir channel
+     * is done with polickit.
+     *
+     * Since: 0.8
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_INHIBIT_KEYBOARD_GRAB,
+         g_param_spec_boolean("inhibit-keyboard-grab",
+                        "Inhibit Keyboard Grab",
+                        "Request that SpiceDisplays don't grab the keyboard",
+                        FALSE,
+                        G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceSession:ca:
+     *
+     * CA certificates in PEM format. The text data can contain
+     * several CA certificates identified by:
+     *
+     *  -----BEGIN CERTIFICATE-----
+     *  ... (CA certificate in base64 encoding) ...
+     *  -----END CERTIFICATE-----
+     *
+     * Since: 0.15
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_CA,
+         g_param_spec_boxed("ca",
+                            "CA",
+                            "The CA certificates data",
+                            G_TYPE_BYTE_ARRAY,
+                            G_PARAM_READWRITE |
+                            G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceSession:secure-channels:
+     *
+     * A string array of channel types to be secured.
+     *
+     * Since: 0.20
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_SECURE_CHANNELS,
+         g_param_spec_boxed ("secure-channels",
+                             "Secure channels",
+                             "Array of channel type to secure",
+                             G_TYPE_STRV,
+                             G_PARAM_READWRITE |
+                             G_PARAM_STATIC_STRINGS));
+
+
+    /**
+     * SpiceSession::channel-new:
+     * @session: the session that emitted the signal
+     * @channel: the new #SpiceChannel
+     *
+     * The #SpiceSession::channel-new signal is emitted each time a #SpiceChannel is created.
+     **/
+    signals[SPICE_SESSION_CHANNEL_NEW] =
+        g_signal_new("channel-new",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_FIRST,
+                     G_STRUCT_OFFSET(SpiceSessionClass, channel_new),
+                     NULL, NULL,
+                     g_cclosure_marshal_VOID__OBJECT,
+                     G_TYPE_NONE,
+                     1,
+                     SPICE_TYPE_CHANNEL);
+
+    /**
+     * SpiceSession::channel-destroy:
+     * @session: the session that emitted the signal
+     * @channel: the destroyed #SpiceChannel
+     *
+     * The #SpiceSession::channel-destroy signal is emitted each time a #SpiceChannel is destroyed.
+     **/
+    signals[SPICE_SESSION_CHANNEL_DESTROY] =
+        g_signal_new("channel-destroy",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_FIRST,
+                     G_STRUCT_OFFSET(SpiceSessionClass, channel_destroy),
+                     NULL, NULL,
+                     g_cclosure_marshal_VOID__OBJECT,
+                     G_TYPE_NONE,
+                     1,
+                     SPICE_TYPE_CHANNEL);
+
+    /**
+     * SpiceSession::mm-time-reset:
+     * @session: the session that emitted the signal
+     *
+     * The #SpiceSession::mm-time-reset is emitted when we identify discontinuity in mm-time
+     *
+     * Since 0.20
+     **/
+    signals[SPICE_SESSION_MM_TIME_RESET] =
+        g_signal_new("mm-time-reset",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_FIRST,
+                     0, NULL, NULL,
+                     g_cclosure_marshal_VOID__VOID,
+                     G_TYPE_NONE,
+                     0);
+
+    /**
+     * SpiceSession:read-only:
+     *
+     * Whether this connection is read-only mode.
+     *
+     * Since: 0.8
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_READ_ONLY,
+         g_param_spec_boolean("read-only", "Read-only",
+                              "Whether this connection is read-only mode",
+                              FALSE,
+                              G_PARAM_READWRITE |
+                              G_PARAM_CONSTRUCT |
+                              G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceSession:cache-size:
+     *
+     * Images cache size. If 0, don't set.
+     *
+     * Since: 0.9
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_CACHE_SIZE,
+         g_param_spec_int("cache-size",
+                          "Cache size",
+                          "Images cache size (bytes)",
+                          0, G_MAXINT, 0,
+                          G_PARAM_READWRITE |
+                          G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceSession:glz-window-size:
+     *
+     * Glz window size. If 0, don't set.
+     *
+     * Since: 0.9
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_GLZ_WINDOW_SIZE,
+         g_param_spec_int("glz-window-size",
+                          "Glz window size",
+                          "Glz window size (bytes)",
+                          0, LZ_MAX_WINDOW_SIZE * 4, 0,
+                          G_PARAM_READWRITE |
+                          G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceSession:name:
+     *
+     * Spice server name.
+     *
+     * Since: 0.11
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_NAME,
+         g_param_spec_string("name",
+                             "Name",
+                             "Spice server name",
+                             NULL,
+                             G_PARAM_READABLE |
+                             G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceSession:uuid:
+     *
+     * Spice server uuid.
+     *
+     * Since: 0.11
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_UUID,
+         g_param_spec_pointer("uuid",
+                              "UUID",
+                              "Spice server uuid",
+                              G_PARAM_READABLE |
+                              G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceSession:proxy:
+     *
+     * URI to the proxy server to use when doing network connection.
+     * of the form <![CDATA[ [protocol://]<host>[:port] ]]>
+     *
+     * Since: 0.17
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_PROXY,
+         g_param_spec_string("proxy",
+                             "Proxy",
+                             "The proxy server",
+                             NULL,
+                             G_PARAM_READWRITE |
+                             G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceSession:shared-dir:
+     *
+     * Location of the shared directory
+     *
+     * Since: 0.24
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_SHARED_DIR,
+         g_param_spec_string("shared-dir",
+                             "Shared directory",
+                             "Shared directory",
+                             g_get_user_special_dir(G_USER_DIRECTORY_PUBLIC_SHARE),
+                             G_PARAM_READWRITE |
+                             G_PARAM_CONSTRUCT |
+                             G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceSession:share-dir-ro:
+     *
+     * Whether to share the directory read-only.
+     *
+     * Since: 0.28
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_SHARE_DIR_RO,
+         g_param_spec_boolean("share-dir-ro",
+                              "Share directory read-only",
+                              "Share directory read-only",
+                              FALSE,
+                              G_PARAM_READWRITE |
+                              G_PARAM_CONSTRUCT |
+                              G_PARAM_STATIC_STRINGS));
+
+    g_type_class_add_private(klass, sizeof(SpiceSessionPrivate));
+}
+
+/* ------------------------------------------------------------------ */
+/* public functions                                                   */
+
+/**
+ * spice_session_new:
+ *
+ * Creates a new Spice session.
+ *
+ * Returns: a new #SpiceSession
+ **/
+SpiceSession *spice_session_new(void)
+{
+    return SPICE_SESSION(g_object_new(SPICE_TYPE_SESSION, NULL));
+}
+
+G_GNUC_INTERNAL
+SpiceSession *spice_session_new_from_session(SpiceSession *session)
+{
+    g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
+
+    SpiceSessionPrivate *s = session->priv;
+    SpiceSession *copy;
+    SpiceSessionPrivate *c;
+
+    if (s->client_provided_sockets) {
+        g_warning("migration with client provided fd is not supported yet");
+        return NULL;
+    }
+
+    copy = SPICE_SESSION(g_object_new(SPICE_TYPE_SESSION,
+                                      "host", NULL,
+                                      "ca-file", NULL,
+                                      NULL));
+    c = copy->priv;
+    g_clear_object(&c->proxy);
+
+    g_warn_if_fail(c->host == NULL);
+    g_warn_if_fail(c->unix_path == NULL);
+    g_warn_if_fail(c->tls_port == NULL);
+    g_warn_if_fail(c->username == NULL);
+    g_warn_if_fail(c->password == NULL);
+    g_warn_if_fail(c->ca_file == NULL);
+    g_warn_if_fail(c->ciphers == NULL);
+    g_warn_if_fail(c->cert_subject == NULL);
+    g_warn_if_fail(c->pubkey == NULL);
+    g_warn_if_fail(c->pubkey == NULL);
+    g_warn_if_fail(c->proxy == NULL);
+
+    g_object_get(session,
+                 "host", &c->host,
+                 "unix-path", &c->unix_path,
+                 "tls-port", &c->tls_port,
+                 "username", &c->username,
+                 "password", &c->password,
+                 "ca-file", &c->ca_file,
+                 "ciphers", &c->ciphers,
+                 "cert-subject", &c->cert_subject,
+                 "pubkey", &c->pubkey,
+                 "verify", &c->verify,
+                 "smartcard-certificates", &c->smartcard_certificates,
+                 "smartcard-db", &c->smartcard_db,
+                 "enable-smartcard", &c->smartcard,
+                 "enable-audio", &c->audio,
+                 "enable-usbredir", &c->usbredir,
+                 "ca", &c->ca,
+                 NULL);
+
+    c->client_provided_sockets = s->client_provided_sockets;
+    c->protocol = s->protocol;
+    c->connection_id = s->connection_id;
+    if (s->proxy)
+        c->proxy = g_object_ref(s->proxy);
+
+    return copy;
+}
+
+/**
+ * spice_session_connect:
+ * @session:
+ *
+ * Open the session using the #SpiceSession:host and
+ * #SpiceSession:port.
+ *
+ * Returns: %FALSE if the connection failed.
+ **/
+gboolean spice_session_connect(SpiceSession *session)
+{
+    SpiceSessionPrivate *s;
+
+    g_return_val_if_fail(SPICE_IS_SESSION(session), FALSE);
+
+    s = session->priv;
+    g_return_val_if_fail(!s->disconnecting, FALSE);
+
+    session_disconnect(session, TRUE);
+
+    s->client_provided_sockets = FALSE;
+
+    if (s->cmain == NULL)
+        s->cmain = spice_channel_new(session, SPICE_CHANNEL_MAIN, 0);
+
+    glz_decoder_window_clear(s->glz_window);
+    return spice_channel_connect(s->cmain);
+}
+
+/**
+ * spice_session_open_fd:
+ * @session:
+ * @fd: a file descriptor (socket) or -1
+ *
+ * Open the session using the provided @fd socket file
+ * descriptor. This is useful if you create the fd yourself, for
+ * example to setup a SSH tunnel.
+ *
+ * Note however that additional sockets will be needed by all the channels
+ * created for @session so users of this API should hook into
+ * SpiceChannel::open-fd signal for each channel they are interested in, and
+ * create and pass a new socket to the channel using #spice_channel_open_fd, in
+ * the signal callback.
+ *
+ * If @fd is -1, a valid fd will be requested later via the
+ * SpiceChannel::open-fd signal. Typically, you would want to just pass -1 as
+ * @fd this call since you will have to hook to SpiceChannel::open-fd signal
+ * anyway.
+ *
+ * Returns:
+ **/
+gboolean spice_session_open_fd(SpiceSession *session, int fd)
+{
+    SpiceSessionPrivate *s;
+
+    g_return_val_if_fail(SPICE_IS_SESSION(session), FALSE);
+    g_return_val_if_fail(fd >= -1, FALSE);
+
+    s = session->priv;
+    g_return_val_if_fail(!s->disconnecting, FALSE);
+
+    session_disconnect(session, TRUE);
+
+    s->client_provided_sockets = TRUE;
+
+    if (s->cmain == NULL)
+        s->cmain = spice_channel_new(session, SPICE_CHANNEL_MAIN, 0);
+
+    glz_decoder_window_clear(s->glz_window);
+    return spice_channel_open_fd(s->cmain, fd);
+}
+
+G_GNUC_INTERNAL
+gboolean spice_session_get_client_provided_socket(SpiceSession *session)
+{
+    g_return_val_if_fail(SPICE_IS_SESSION(session), FALSE);
+
+    SpiceSessionPrivate *s = session->priv;
+
+    return s->client_provided_sockets;
+}
+
+static void cache_clear_all(SpiceSession *self)
+{
+    SpiceSessionPrivate *s = self->priv;
+
+    cache_clear(s->images);
+    glz_decoder_window_clear(s->glz_window);
+}
+
+G_GNUC_INTERNAL
+void spice_session_switching_disconnect(SpiceSession *self)
+{
+    g_return_if_fail(SPICE_IS_SESSION(self));
+
+    SpiceSessionPrivate *s = self->priv;
+    struct channel *item;
+    RingItem *ring, *next;
+
+    g_return_if_fail(s->cmain != NULL);
+
+    /* disconnect/destroy all but main channel */
+
+    for (ring = ring_get_head(&s->channels); ring != NULL; ring = next) {
+        next = ring_next(&s->channels, ring);
+        item = SPICE_CONTAINEROF(ring, struct channel, link);
+
+        if (item->channel == s->cmain)
+            continue;
+        spice_session_channel_destroy(self, item->channel);
+    }
+
+    g_warn_if_fail(!ring_is_empty(&s->channels)); /* ring_get_length() == 1 */
+
+    cache_clear_all(self);
+    s->connection_id = 0;
+}
+
+#define SWAP_STR(x, y) G_STMT_START { \
+    const gchar *tmp;                 \
+    const gchar *a = x;               \
+    const gchar *b = y;               \
+    tmp = a;                          \
+    a = b;                            \
+    b = tmp;                          \
+} G_STMT_END
+
+G_GNUC_INTERNAL
+void spice_session_start_migrating(SpiceSession *session,
+                                   gboolean full_migration)
+{
+    g_return_if_fail(SPICE_IS_SESSION(session));
+
+    SpiceSessionPrivate *s = session->priv;
+    SpiceSessionPrivate *m;
+
+    g_return_if_fail(s->migration != NULL);
+    m = s->migration->priv;
+    g_return_if_fail(m->migration_state == SPICE_SESSION_MIGRATION_CONNECTING);
+
+
+    s->full_migration = full_migration;
+    spice_session_set_migration_state(session, SPICE_SESSION_MIGRATION_MIGRATING);
+
+    /* swapping connection details happens after MIGRATION_CONNECTING state */
+    SWAP_STR(s->host, m->host);
+    SWAP_STR(s->port, m->port);
+    SWAP_STR(s->tls_port, m->tls_port);
+    SWAP_STR(s->unix_path, m->unix_path);
+
+    g_warn_if_fail(ring_get_length(&s->channels) == ring_get_length(&m->channels));
+
+    SPICE_DEBUG("migration channels left:%d (in migration:%d)",
+                ring_get_length(&s->channels), ring_get_length(&m->channels));
+    s->migration_left = spice_session_get_channels(session);
+}
+#undef SWAP_STR
+
+G_GNUC_INTERNAL
+SpiceChannel* spice_session_lookup_channel(SpiceSession *session, gint id, gint type)
+{
+    g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
+
+    RingItem *ring, *next;
+    SpiceSessionPrivate *s = session->priv;
+    struct channel *c;
+
+    for (ring = ring_get_head(&s->channels);
+         ring != NULL; ring = next) {
+        next = ring_next(&s->channels, ring);
+        c = SPICE_CONTAINEROF(ring, struct channel, link);
+        if (c == NULL || c->channel == NULL) {
+            g_warn_if_reached();
+            continue;
+        }
+
+        if (id == spice_channel_get_channel_id(c->channel) &&
+            type == spice_channel_get_channel_type(c->channel))
+            break;
+    }
+    g_return_val_if_fail(ring != NULL, NULL);
+
+    return c->channel;
+}
+
+G_GNUC_INTERNAL
+void spice_session_abort_migration(SpiceSession *session)
+{
+    g_return_if_fail(SPICE_IS_SESSION(session));
+
+    SpiceSessionPrivate *s = session->priv;
+    RingItem *ring, *next;
+    struct channel *c;
+
+    if (s->migration == NULL) {
+        SPICE_DEBUG("no migration in progress");
+        return;
+    }
+
+    SPICE_DEBUG("migration: abort");
+    if (s->migration_state != SPICE_SESSION_MIGRATION_MIGRATING)
+        goto end;
+
+    for (ring = ring_get_head(&s->channels);
+         ring != NULL; ring = next) {
+        next = ring_next(&s->channels, ring);
+        c = SPICE_CONTAINEROF(ring, struct channel, link);
+
+        if (g_list_find(s->migration_left, c->channel))
+            continue;
+
+        spice_channel_swap(c->channel,
+            spice_session_lookup_channel(s->migration,
+                                         spice_channel_get_channel_id(c->channel),
+                                         spice_channel_get_channel_type(c->channel)),
+                                         !s->full_migration);
+    }
+
+end:
+    g_list_free(s->migration_left);
+    s->migration_left = NULL;
+    session_disconnect(s->migration, FALSE);
+    g_object_unref(s->migration);
+    s->migration = NULL;
+
+    s->migrate_wait_init = FALSE;
+    if (s->after_main_init) {
+        g_source_remove(s->after_main_init);
+        s->after_main_init = 0;
+    }
+
+    spice_session_set_migration_state(session, SPICE_SESSION_MIGRATION_NONE);
+}
+
+G_GNUC_INTERNAL
+void spice_session_channel_migrate(SpiceSession *session, SpiceChannel *channel)
+{
+    g_return_if_fail(SPICE_IS_SESSION(session));
+
+    SpiceSessionPrivate *s = session->priv;
+    SpiceChannel *c;
+    gint id, type;
+
+    g_return_if_fail(s->migration != NULL);
+    g_return_if_fail(SPICE_IS_CHANNEL(channel));
+
+    id = spice_channel_get_channel_id(channel);
+    type = spice_channel_get_channel_type(channel);
+    CHANNEL_DEBUG(channel, "migrating channel id:%d type:%d", id, type);
+
+    c = spice_session_lookup_channel(s->migration, id, type);
+    g_return_if_fail(c != NULL);
+
+    if (!g_queue_is_empty(&c->priv->xmit_queue) && s->full_migration) {
+        CHANNEL_DEBUG(channel, "mig channel xmit queue is not empty. type %s", c->priv->name);
+    }
+    spice_channel_swap(channel, c, !s->full_migration);
+    s->migration_left = g_list_remove(s->migration_left, channel);
+
+    if (g_list_length(s->migration_left) == 0) {
+        CHANNEL_DEBUG(channel, "migration: all channel migrated, success");
+        session_disconnect(s->migration, FALSE);
+        g_object_unref(s->migration);
+        s->migration = NULL;
+        spice_session_set_migration_state(session, SPICE_SESSION_MIGRATION_NONE);
+    }
+}
+
+/* main context */
+static gboolean after_main_init(gpointer data)
+{
+    SpiceSession *self = data;
+    SpiceSessionPrivate *s = self->priv;
+    GList *l;
+
+    for (l = s->migration_left; l != NULL; ) {
+        SpiceChannel *channel = l->data;
+        l = l->next;
+
+        spice_session_channel_migrate(self, channel);
+        channel->priv->state = SPICE_CHANNEL_STATE_READY;
+        spice_channel_up(channel);
+    }
+
+    s->after_main_init = 0;
+    return FALSE;
+}
+
+/* coroutine context */
+G_GNUC_INTERNAL
+gboolean spice_session_migrate_after_main_init(SpiceSession *self)
+{
+    g_return_val_if_fail(SPICE_IS_SESSION(self), FALSE);
+
+    SpiceSessionPrivate *s = self->priv;
+
+    if (!s->migrate_wait_init)
+        return FALSE;
+
+    g_return_val_if_fail(g_list_length(s->migration_left) != 0, FALSE);
+    g_return_val_if_fail(s->after_main_init == 0, FALSE);
+
+    s->migrate_wait_init = FALSE;
+    s->after_main_init = g_idle_add(after_main_init, self);
+
+    return TRUE;
+}
+
+/* main context */
+G_GNUC_INTERNAL
+void spice_session_migrate_end(SpiceSession *self)
+{
+    g_return_if_fail(SPICE_IS_SESSION(self));
+
+    SpiceSessionPrivate *s = self->priv;
+    SpiceMsgOut *out;
+    GList *l;
+
+    g_return_if_fail(s->migration);
+    g_return_if_fail(s->migration->priv->cmain);
+    g_return_if_fail(g_list_length(s->migration_left) != 0);
+
+    /* disconnect and reset all channels */
+    for (l = s->migration_left; l != NULL; ) {
+        SpiceChannel *channel = l->data;
+        l = l->next;
+
+        if (!SPICE_IS_MAIN_CHANNEL(channel)) {
+            /* freeze other channels */
+            channel->priv->state = SPICE_CHANNEL_STATE_MIGRATING;
+        }
+
+        /* reset for migration, disconnect */
+        spice_channel_reset(channel, TRUE);
+
+        if (SPICE_IS_MAIN_CHANNEL(channel)) {
+            /* migrate main to target, so we can start talking */
+            spice_session_channel_migrate(self, channel);
+        }
+    }
+
+    cache_clear_all(self);
+
+    /* send MIGRATE_END to target */
+    out = spice_msg_out_new(s->cmain, SPICE_MSGC_MAIN_MIGRATE_END);
+    spice_msg_out_send(out);
+
+    /* now wait after main init for the rest of channels migration */
+    s->migrate_wait_init = TRUE;
+}
+
+/**
+ * spice_session_get_read_only:
+ * @session: a #SpiceSession
+ *
+ * Returns: wether the @session is in read-only mode.
+ **/
+gboolean spice_session_get_read_only(SpiceSession *self)
+{
+    g_return_val_if_fail(SPICE_IS_SESSION(self), FALSE);
+
+    return self->priv->read_only;
+}
+
+static gboolean session_disconnect_idle(SpiceSession *self)
+{
+    SpiceSessionPrivate *s = self->priv;
+
+    session_disconnect(self, FALSE);
+    s->disconnecting = 0;
+
+    g_object_unref(self);
+
+    return FALSE;
+}
+
+/**
+ * spice_session_disconnect:
+ * @session:
+ *
+ * Disconnect the @session, and destroy all channels.
+ **/
+void spice_session_disconnect(SpiceSession *session)
+{
+    SpiceSessionPrivate *s;
+
+    g_return_if_fail(SPICE_IS_SESSION(session));
+
+    s = session->priv;
+
+    SPICE_DEBUG("session: disconnecting %d", s->disconnecting);
+    if (s->disconnecting != 0)
+        return;
+
+    g_object_ref(session);
+    s->disconnecting = g_idle_add((GSourceFunc)session_disconnect_idle, session);
+}
+
+/**
+ * spice_session_get_channels:
+ * @session: a #SpiceSession
+ *
+ * Get the list of current channels associated with this @session.
+ *
+ * Returns: (element-type SpiceChannel) (transfer container): a #GList
+ *          of unowned #SpiceChannel channels.
+ **/
+GList *spice_session_get_channels(SpiceSession *session)
+{
+    SpiceSessionPrivate *s;
+    struct channel *item;
+    GList *list = NULL;
+    RingItem *ring;
+
+    g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
+    g_return_val_if_fail(session->priv != NULL, NULL);
+
+    s = session->priv;
+
+    for (ring = ring_get_head(&s->channels);
+         ring != NULL;
+         ring = ring_next(&s->channels, ring)) {
+        item = SPICE_CONTAINEROF(ring, struct channel, link);
+        list = g_list_append(list, item->channel);
+    }
+    return list;
+}
+
+/**
+ * spice_session_has_channel_type:
+ * @session: a #SpiceSession
+ *
+ * See if there is a @type channel in the channels associated with this
+ * @session.
+ *
+ * Returns: TRUE if a @type channel is available otherwise FALSE.
+ **/
+gboolean spice_session_has_channel_type(SpiceSession *session, gint type)
+{
+    SpiceSessionPrivate *s;
+    struct channel *item;
+    RingItem *ring;
+
+    g_return_val_if_fail(SPICE_IS_SESSION(session), FALSE);
+    g_return_val_if_fail(session->priv != NULL, FALSE);
+
+    s = session->priv;
+
+    for (ring = ring_get_head(&s->channels);
+         ring != NULL;
+         ring = ring_next(&s->channels, ring)) {
+        item = SPICE_CONTAINEROF(ring, struct channel, link);
+        if (spice_channel_get_channel_type(item->channel) == type) {
+            return TRUE;
+        }
+    }
+    return FALSE;
+}
+
+/* ------------------------------------------------------------------ */
+/* private functions                                                  */
+
+typedef struct spice_open_host spice_open_host;
+
+struct spice_open_host {
+    struct coroutine *from;
+    SpiceSession *session;
+    SpiceChannel *channel;
+    SpiceURI *proxy;
+    int port;
+    GCancellable *cancellable;
+    GError *error;
+    GSocketConnection *connection;
+    GSocketClient *client;
+};
+
+static void socket_client_connect_ready(GObject *source_object, GAsyncResult *result,
+                                        gpointer data)
+{
+    GSocketClient *client = G_SOCKET_CLIENT(source_object);
+    spice_open_host *open_host = data;
+    GSocketConnection *connection = NULL;
+
+    CHANNEL_DEBUG(open_host->channel, "connect ready");
+    connection = g_socket_client_connect_finish(client, result, &open_host->error);
+    if (connection == NULL) {
+        g_warn_if_fail(open_host->error != NULL);
+        goto end;
+    }
+
+    open_host->connection = connection;
+
+end:
+    coroutine_yieldto(open_host->from, NULL);
+}
+
+/* main context */
+static void open_host_connectable_connect(spice_open_host *open_host, GSocketConnectable *connectable)
+{
+    CHANNEL_DEBUG(open_host->channel, "connecting %p...", open_host);
+
+    g_socket_client_connect_async(open_host->client, connectable,
+                                  open_host->cancellable,
+                                  socket_client_connect_ready, open_host);
+}
+
+/* main context */
+static void proxy_lookup_ready(GObject *source_object, GAsyncResult *result,
+                               gpointer data)
+{
+    spice_open_host *open_host = data;
+    SpiceSession *session = open_host->session;
+    SpiceSessionPrivate *s = session->priv;
+    GList *addresses = NULL, *it;
+    GSocketAddress *address;
+
+    SPICE_DEBUG("proxy lookup ready");
+    addresses = g_resolver_lookup_by_name_finish(G_RESOLVER(source_object),
+                                                 result, &open_host->error);
+    if (addresses == NULL || open_host->error) {
+        g_prefix_error(&open_host->error, "SPICE proxy: ");
+        coroutine_yieldto(open_host->from, NULL);
+        return;
+    }
+
+    for (it = addresses; it != NULL; it = it->next) {
+        address = g_proxy_address_new(G_INET_ADDRESS(it->data),
+                                      spice_uri_get_port(open_host->proxy),
+                                      spice_uri_get_scheme(open_host->proxy),
+                                      s->host, open_host->port,
+                                      spice_uri_get_user(open_host->proxy),
+                                      spice_uri_get_password(open_host->proxy));
+        if (address != NULL)
+            break;
+    }
+
+    open_host_connectable_connect(open_host, G_SOCKET_CONNECTABLE(address));
+    g_resolver_free_addresses(addresses);
+    g_object_unref(address);
+}
+
+/* main context */
+static gboolean open_host_idle_cb(gpointer data)
+{
+    spice_open_host *open_host = data;
+    SpiceSessionPrivate *s;
+
+    g_return_val_if_fail(open_host != NULL, FALSE);
+    g_return_val_if_fail(open_host->connection == NULL, FALSE);
+
+    if (spice_channel_get_session(open_host->channel) != open_host->session)
+        return FALSE;
+
+    s = open_host->session->priv;
+    open_host->proxy = s->proxy;
+    if (open_host->error != NULL) {
+        coroutine_yieldto(open_host->from, NULL);
+        return FALSE;
+    }
+
+    if (open_host->proxy) {
+        g_resolver_lookup_by_name_async(g_resolver_get_default(),
+                                        spice_uri_get_hostname(open_host->proxy),
+                                        open_host->cancellable,
+                                        proxy_lookup_ready, open_host);
+    } else {
+        GSocketConnectable *address = NULL;
+
+        if (s->unix_path) {
+            SPICE_DEBUG("open unix path %s", s->unix_path);
+#ifdef G_OS_UNIX
+            address = G_SOCKET_CONNECTABLE(g_unix_socket_address_new(s->unix_path));
+#else
+            g_set_error_literal(&open_host->error, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                                "Unix path unsupported on this platform");
+#endif
+        } else {
+            SPICE_DEBUG("open host %s:%d", s->host, open_host->port);
+            address = g_network_address_new(s->host, open_host->port);
+        }
+
+        if (address == NULL || open_host->error != NULL) {
+            coroutine_yieldto(open_host->from, NULL);
+            return FALSE;
+        }
+
+        open_host_connectable_connect(open_host, address);
+        g_object_unref(address);
+    }
+
+    if (open_host->proxy != NULL) {
+        gchar *str = spice_uri_to_string(open_host->proxy);
+        SPICE_DEBUG("(with proxy %s)", str);
+        g_free(str);
+    }
+
+    return FALSE;
+}
+
+#define SOCKET_TIMEOUT 10
+
+/* coroutine context */
+G_GNUC_INTERNAL
+GSocketConnection* spice_session_channel_open_host(SpiceSession *session, SpiceChannel *channel,
+                                                   gboolean *use_tls, GError **error)
+{
+    g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
+
+    SpiceSessionPrivate *s = session->priv;
+    SpiceChannelPrivate *c = channel->priv;
+    spice_open_host open_host = { 0, };
+    gchar *port, *endptr;
+
+    // FIXME: make open_host() cancellable
+    open_host.from = coroutine_self();
+    open_host.session = session;
+    open_host.channel = channel;
+
+    const char *name = spice_channel_type_to_string(c->channel_type);
+    if (spice_strv_contains(s->secure_channels, "all") ||
+        spice_strv_contains(s->secure_channels, name))
+        *use_tls = TRUE;
+
+    if (s->unix_path) {
+        if (*use_tls) {
+            CHANNEL_DEBUG(channel, "No TLS for Unix sockets");
+            return NULL;
+        }
+    } else {
+        port = *use_tls ? s->tls_port : s->port;
+        if (port == NULL) {
+            g_debug("Missing port value, not attempting %s connection.",
+                    *use_tls?"TLS":"unencrypted");
+            return NULL;
+        }
+
+        open_host.port = strtol(port, &endptr, 10);
+        if (*port == '\0' || *endptr != '\0' ||
+            open_host.port <= 0 || open_host.port > G_MAXUINT16) {
+            g_warning("Invalid port value %s", port);
+            return NULL;
+        }
+    }
+    if (*use_tls) {
+        CHANNEL_DEBUG(channel, "Using TLS, port %d", open_host.port);
+    } else {
+        CHANNEL_DEBUG(channel, "Using plain text, port %d", open_host.port);
+    }
+
+    open_host.client = g_socket_client_new();
+    g_socket_client_set_enable_proxy(open_host.client, s->proxy != NULL);
+    g_socket_client_set_timeout(open_host.client, SOCKET_TIMEOUT);
+
+    g_idle_add(open_host_idle_cb, &open_host);
+    /* switch to main loop and wait for connection */
+    coroutine_yield(NULL);
+
+    if (open_host.error != NULL) {
+        CHANNEL_DEBUG(channel, "open host: %s", open_host.error->message);
+        g_propagate_error(error, open_host.error);
+    } else if (open_host.connection != NULL) {
+        GSocket *socket;
+        socket = g_socket_connection_get_socket(open_host.connection);
+        g_socket_set_timeout(socket, 0);
+        g_socket_set_blocking(socket, FALSE);
+        g_socket_set_keepalive(socket, TRUE);
+    }
+
+    g_clear_object(&open_host.client);
+    return open_host.connection;
+}
+
+
+G_GNUC_INTERNAL
+void spice_session_channel_new(SpiceSession *session, SpiceChannel *channel)
+{
+    g_return_if_fail(SPICE_IS_SESSION(session));
+    g_return_if_fail(SPICE_IS_CHANNEL(channel));
+
+    SpiceSessionPrivate *s = session->priv;
+    struct channel *item;
+
+
+    item = g_new0(struct channel, 1);
+    item->channel = channel;
+    ring_add(&s->channels, &item->link);
+
+    if (SPICE_IS_MAIN_CHANNEL(channel)) {
+        gboolean all = spice_strv_contains(s->disable_effects, "all");
+
+        g_object_set(channel,
+                     "disable-wallpaper", all || spice_strv_contains(s->disable_effects, "wallpaper"),
+                     "disable-font-smooth", all || spice_strv_contains(s->disable_effects, "font-smooth"),
+                     "disable-animation", all || spice_strv_contains(s->disable_effects, "animation"),
+                     NULL);
+        if (s->color_depth != 0)
+            g_object_set(channel, "color-depth", s->color_depth, NULL);
+
+        CHANNEL_DEBUG(channel, "new main channel, switching");
+        s->cmain = channel;
+    } else if (SPICE_IS_PLAYBACK_CHANNEL(channel)) {
+        g_warn_if_fail(s->playback_channel == NULL);
+        s->playback_channel = SPICE_PLAYBACK_CHANNEL(channel);
+    }
+
+    g_signal_emit(session, signals[SPICE_SESSION_CHANNEL_NEW], 0, channel);
+}
+
+static void spice_session_channel_destroy(SpiceSession *session, SpiceChannel *channel)
+{
+    g_return_if_fail(SPICE_IS_SESSION(session));
+    g_return_if_fail(SPICE_IS_CHANNEL(channel));
+
+    SpiceSessionPrivate *s = session->priv;
+    struct channel *item = NULL;
+    RingItem *ring;
+
+    if (s->migration_left)
+        s->migration_left = g_list_remove(s->migration_left, channel);
+
+    for (ring = ring_get_head(&s->channels); ring != NULL;
+         ring = ring_next(&s->channels, ring)) {
+        item = SPICE_CONTAINEROF(ring, struct channel, link);
+        if (item->channel == channel)
+            break;
+    }
+
+    g_return_if_fail(ring != NULL);
+
+    if (channel == s->cmain) {
+        CHANNEL_DEBUG(channel, "the session lost the main channel");
+        s->cmain = NULL;
+    }
+
+    ring_remove(&item->link);
+    free(item);
+
+    g_signal_emit(session, signals[SPICE_SESSION_CHANNEL_DESTROY], 0, channel);
+
+    g_clear_object(&channel->priv->session);
+    spice_channel_disconnect(channel, SPICE_CHANNEL_NONE);
+    g_object_unref(channel);
+}
+
+G_GNUC_INTERNAL
+void spice_session_set_connection_id(SpiceSession *session, int id)
+{
+    g_return_if_fail(SPICE_IS_SESSION(session));
+
+    SpiceSessionPrivate *s = session->priv;
+
+    s->connection_id = id;
+}
+
+G_GNUC_INTERNAL
+int spice_session_get_connection_id(SpiceSession *session)
+{
+    g_return_val_if_fail(SPICE_IS_SESSION(session), -1);
+
+    SpiceSessionPrivate *s = session->priv;
+
+    return s->connection_id;
+}
+
+G_GNUC_INTERNAL
+guint32 spice_session_get_mm_time(SpiceSession *session)
+{
+    g_return_val_if_fail(SPICE_IS_SESSION(session), 0);
+
+    SpiceSessionPrivate *s = session->priv;
+
+    /* FIXME: we may want to estimate the drift of clocks, and well,
+       do something better than this trivial approach */
+    return s->mm_time + (g_get_monotonic_time() - s->mm_time_at_clock) / 1000;
+}
+
+#define MM_TIME_DIFF_RESET_THRESH 500 // 0.5 sec
+
+G_GNUC_INTERNAL
+void spice_session_set_mm_time(SpiceSession *session, guint32 time)
+{
+    g_return_if_fail(SPICE_IS_SESSION(session));
+
+    SpiceSessionPrivate *s = session->priv;
+    guint32 old_time;
+
+    old_time = spice_session_get_mm_time(session);
+
+    s->mm_time = time;
+    s->mm_time_at_clock = g_get_monotonic_time();
+    SPICE_DEBUG("set mm time: %u", spice_session_get_mm_time(session));
+    if (time > old_time + MM_TIME_DIFF_RESET_THRESH ||
+        time < old_time) {
+        SPICE_DEBUG("%s: mm-time-reset, old %u, new %u", __FUNCTION__, old_time, s->mm_time);
+        g_coroutine_signal_emit(session, signals[SPICE_SESSION_MM_TIME_RESET], 0);
+    }
+}
+
+G_GNUC_INTERNAL
+void spice_session_set_port(SpiceSession *session, int port, gboolean tls)
+{
+    const char *prop = tls ? "tls-port" : "port";
+    char *tmp;
+
+    g_return_if_fail(SPICE_IS_SESSION(session));
+
+    /* old spicec client doesn't accept port == 0, see Migrate::start */
+    tmp = port > 0 ? g_strdup_printf("%d", port) : NULL;
+    g_object_set(session, prop, tmp, NULL);
+    g_free(tmp);
+}
+
+G_GNUC_INTERNAL
+void spice_session_get_pubkey(SpiceSession *session, guint8 **pubkey, guint *size)
+{
+    g_return_if_fail(SPICE_IS_SESSION(session));
+    g_return_if_fail(pubkey != NULL);
+    g_return_if_fail(size != NULL);
+
+    SpiceSessionPrivate *s = session->priv;
+
+    *pubkey = s->pubkey ? s->pubkey->data : NULL;
+    *size = s->pubkey ? s->pubkey->len : 0;
+}
+
+G_GNUC_INTERNAL
+void spice_session_get_ca(SpiceSession *session, guint8 **ca, guint *size)
+{
+    g_return_if_fail(SPICE_IS_SESSION(session));
+    g_return_if_fail(ca != NULL);
+    g_return_if_fail(size != NULL);
+
+    SpiceSessionPrivate *s = session->priv;
+
+    *ca = s->ca ? s->ca->data : NULL;
+    *size = s->ca ? s->ca->len : 0;
+}
+
+G_GNUC_INTERNAL
+guint spice_session_get_verify(SpiceSession *session)
+{
+    g_return_val_if_fail(SPICE_IS_SESSION(session), 0);
+
+    SpiceSessionPrivate *s = session->priv;
+
+    return s->verify;
+}
+
+G_GNUC_INTERNAL
+void spice_session_set_migration_state(SpiceSession *session, SpiceSessionMigration state)
+{
+    g_return_if_fail(SPICE_IS_SESSION(session));
+
+    SpiceSessionPrivate *s = session->priv;
+
+    if (state == SPICE_SESSION_MIGRATION_CONNECTING)
+        s->for_migration = true;
+
+    s->migration_state = state;
+    g_coroutine_object_notify(G_OBJECT(session), "migration-state");
+}
+
+G_GNUC_INTERNAL
+const gchar* spice_session_get_username(SpiceSession *session)
+{
+    g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
+
+    SpiceSessionPrivate *s = session->priv;
+
+    return s->username;
+}
+
+G_GNUC_INTERNAL
+const gchar* spice_session_get_password(SpiceSession *session)
+{
+    g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
+
+    SpiceSessionPrivate *s = session->priv;
+
+    return s->password;
+}
+
+G_GNUC_INTERNAL
+const gchar* spice_session_get_host(SpiceSession *session)
+{
+    g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
+
+    SpiceSessionPrivate *s = session->priv;
+
+    return s->host;
+}
+
+G_GNUC_INTERNAL
+const gchar* spice_session_get_cert_subject(SpiceSession *session)
+{
+    g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
+
+    SpiceSessionPrivate *s = session->priv;
+
+    return s->cert_subject;
+}
+
+G_GNUC_INTERNAL
+const gchar* spice_session_get_ciphers(SpiceSession *session)
+{
+    g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
+
+    SpiceSessionPrivate *s = session->priv;
+
+    return s->ciphers;
+}
+
+G_GNUC_INTERNAL
+const gchar* spice_session_get_ca_file(SpiceSession *session)
+{
+    g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
+
+    SpiceSessionPrivate *s = session->priv;
+
+    return s->ca_file;
+}
+
+G_GNUC_INTERNAL
+void spice_session_get_caches(SpiceSession *session,
+                              display_cache **images,
+                              SpiceGlzDecoderWindow **glz_window)
+{
+    g_return_if_fail(SPICE_IS_SESSION(session));
+
+    SpiceSessionPrivate *s = session->priv;
+
+    if (images)
+        *images = s->images;
+    if (glz_window)
+        *glz_window = s->glz_window;
+}
+
+G_GNUC_INTERNAL
+void spice_session_set_caches_hints(SpiceSession *session,
+                                    uint32_t pci_ram_size,
+                                    uint32_t n_display_channels)
+{
+    g_return_if_fail(SPICE_IS_SESSION(session));
+
+    SpiceSessionPrivate *s = session->priv;
+
+    s->pci_ram_size = pci_ram_size;
+    s->n_display_channels = n_display_channels;
+
+    /* TODO: when setting cache and window size, we should consider the client's
+     *       available memory and the number of display channels */
+    if (s->images_cache_size == 0) {
+        s->images_cache_size = IMAGES_CACHE_SIZE_DEFAULT;
+    }
+
+    if (s->glz_window_size == 0) {
+        s->glz_window_size = MIN(MAX_GLZ_WINDOW_SIZE_DEFAULT, pci_ram_size / 2);
+        s->glz_window_size = MAX(MIN_GLZ_WINDOW_SIZE_DEFAULT, s->glz_window_size);
+    }
+}
+
+G_GNUC_INTERNAL
+guint spice_session_get_n_display_channels(SpiceSession *session)
+{
+    g_return_val_if_fail(session != NULL, 0);
+
+    return session->priv->n_display_channels;
+}
+
+G_GNUC_INTERNAL
+void spice_session_set_uuid(SpiceSession *session, guint8 uuid[16])
+{
+    g_return_if_fail(SPICE_IS_SESSION(session));
+
+    SpiceSessionPrivate *s = session->priv;
+
+    memcpy(s->uuid, uuid, sizeof(s->uuid));
+
+    g_coroutine_object_notify(G_OBJECT(session), "uuid");
+}
+
+G_GNUC_INTERNAL
+void spice_session_set_name(SpiceSession *session, const gchar *name)
+{
+    g_return_if_fail(SPICE_IS_SESSION(session));
+
+    SpiceSessionPrivate *s = session->priv;
+
+    g_free(s->name);
+    s->name = g_strdup(name);
+
+    g_coroutine_object_notify(G_OBJECT(session), "name");
+}
+
+G_GNUC_INTERNAL
+void spice_session_sync_playback_latency(SpiceSession *session)
+{
+    g_return_if_fail(SPICE_IS_SESSION(session));
+
+    SpiceSessionPrivate *s = session->priv;
+
+    if (s->playback_channel &&
+        spice_playback_channel_is_active(s->playback_channel)) {
+        spice_playback_channel_sync_latency(s->playback_channel);
+    } else {
+        SPICE_DEBUG("%s: not implemented when there isn't audio playback", __FUNCTION__);
+    }
+}
+
+G_GNUC_INTERNAL
+gboolean spice_session_is_playback_active(SpiceSession *session)
+{
+    g_return_val_if_fail(SPICE_IS_SESSION(session), FALSE);
+
+    SpiceSessionPrivate *s = session->priv;
+
+    return (s->playback_channel &&
+        spice_playback_channel_is_active(s->playback_channel));
+}
+
+G_GNUC_INTERNAL
+guint32 spice_session_get_playback_latency(SpiceSession *session)
+{
+    g_return_val_if_fail(SPICE_IS_SESSION(session), 0);
+
+    SpiceSessionPrivate *s = session->priv;
+
+    if (s->playback_channel &&
+        spice_playback_channel_is_active(s->playback_channel)) {
+        return spice_playback_channel_get_latency(s->playback_channel);
+    } else {
+        SPICE_DEBUG("%s: not implemented when there isn't audio playback", __FUNCTION__);
+        return 0;
+    }
+}
+
+G_GNUC_INTERNAL
+const gchar* spice_session_get_shared_dir(SpiceSession *session)
+{
+    g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
+
+    SpiceSessionPrivate *s = session->priv;
+
+    return s->shared_dir;
+}
+
+G_GNUC_INTERNAL
+void spice_session_set_shared_dir(SpiceSession *session, const gchar *dir)
+{
+    g_return_if_fail(SPICE_IS_SESSION(session));
+
+    SpiceSessionPrivate *s = session->priv;
+
+    g_free(s->shared_dir);
+    s->shared_dir = g_strdup(dir);
+}
+
+/**
+ * spice_session_get_proxy_uri:
+ * @session: a #SpiceSession
+ *
+ * Returns: (transfer none): the session proxy #SpiceURI or %NULL.
+ * Since: 0.24
+ **/
+SpiceURI *spice_session_get_proxy_uri(SpiceSession *session)
+{
+    SpiceSessionPrivate *s;
+
+    g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
+    g_return_val_if_fail(session->priv != NULL, NULL);
+
+    s = session->priv;
+
+    return s->proxy;
+}
+
+/**
+ * spice_audio_get:
+ * @session: the #SpiceSession to connect to
+ * @context: (allow-none): a #GMainContext to attach to (or %NULL for default).
+ *
+ * Gets the #SpiceAudio associated with the passed in #SpiceSession.
+ * A new #SpiceAudio instance will be created the first time this
+ * function is called for a certain #SpiceSession.
+ *
+ * Note that this function returns a weak reference, which should not be used
+ * after the #SpiceSession itself has been unref-ed by the caller.
+ *
+ * Returns: (transfer none): a weak reference to a #SpiceAudio
+ * instance or %NULL if failed.
+ **/
+SpiceAudio *spice_audio_get(SpiceSession *session, GMainContext *context)
+{
+    static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
+    SpiceAudio *self;
+
+    g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
+
+    g_static_mutex_lock(&mutex);
+    self = session->priv->audio_manager;
+    if (self == NULL) {
+        self = spice_audio_new(session, context, NULL);
+        session->priv->audio_manager = self;
+    }
+    g_static_mutex_unlock(&mutex);
+
+    return self;
+}
+
+/**
+ * spice_usb_device_manager_get:
+ * @session: #SpiceSession for which to get the #SpiceUsbDeviceManager
+ *
+ * Gets the #SpiceUsbDeviceManager associated with the passed in #SpiceSession.
+ * A new #SpiceUsbDeviceManager instance will be created the first time this
+ * function is called for a certain #SpiceSession.
+ *
+ * Note that this function returns a weak reference, which should not be used
+ * after the #SpiceSession itself has been unref-ed by the caller.
+ *
+ * Returns: (transfer none): a weak reference to the #SpiceUsbDeviceManager associated with the passed in #SpiceSession
+ */
+SpiceUsbDeviceManager *spice_usb_device_manager_get(SpiceSession *session,
+                                                    GError **err)
+{
+    SpiceUsbDeviceManager *self;
+    static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
+
+    g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
+    g_return_val_if_fail(err == NULL || *err == NULL, NULL);
+
+    g_static_mutex_lock(&mutex);
+    self = session->priv->usb_manager;
+    if (self == NULL) {
+        self = g_initable_new(SPICE_TYPE_USB_DEVICE_MANAGER, NULL, err,
+                              "session", session, NULL);
+        session->priv->usb_manager = self;
+    }
+    g_static_mutex_unlock(&mutex);
+
+    return self;
+}
+
+G_GNUC_INTERNAL
+gboolean spice_session_get_audio_enabled(SpiceSession *session)
+{
+    g_return_val_if_fail(SPICE_IS_SESSION(session), FALSE);
+
+    return session->priv->audio;
+}
+
+G_GNUC_INTERNAL
+gboolean spice_session_get_usbredir_enabled(SpiceSession *session)
+{
+    g_return_val_if_fail(SPICE_IS_SESSION(session), FALSE);
+
+    return session->priv->usbredir;
+}
+
+G_GNUC_INTERNAL
+gboolean spice_session_get_smartcard_enabled(SpiceSession *session)
+{
+    g_return_val_if_fail(SPICE_IS_SESSION(session), FALSE);
+
+    return session->priv->smartcard;
+}
+
+G_GNUC_INTERNAL
+PhodavServer* spice_session_get_webdav_server(SpiceSession *session)
+{
+    SpiceSessionPrivate *priv;
+
+    g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
+    priv = session->priv;
+
+#ifdef USE_PHODAV
+    static GMutex mutex;
+
+    const gchar *shared_dir = spice_session_get_shared_dir(session);
+    if (shared_dir == NULL) {
+        g_debug("No shared dir set, not creating webdav server");
+        return NULL;
+    }
+
+    g_mutex_lock(&mutex);
+
+    if (priv->webdav)
+        goto end;
+
+    priv->webdav = phodav_server_new(shared_dir);
+    g_object_bind_property(session,  "share-dir-ro",
+                           priv->webdav, "read-only",
+                           G_BINDING_SYNC_CREATE|G_BINDING_BIDIRECTIONAL);
+    g_object_bind_property(session,  "shared-dir",
+                           priv->webdav, "root",
+                           G_BINDING_SYNC_CREATE|G_BINDING_BIDIRECTIONAL);
+
+end:
+    g_mutex_unlock(&mutex);
+#endif
+
+    return priv->webdav;
+}
+
+/**
+ * spice_session_is_for_migration:
+ * @session: a Spice session
+ *
+ * During seamless migration, channels may be created to establish a
+ * connection with the target, but they are temporary and should only
+ * handle migration steps. In order to avoid other interactions with
+ * the client, channels should check this value.
+ *
+ * Returns: %TRUE if the session is a copy created during migration
+ * Since: 0.27
+ **/
+gboolean spice_session_is_for_migration(SpiceSession *session)
+{
+    g_return_val_if_fail(SPICE_IS_SESSION(session), FALSE);
+
+    return session->priv->for_migration;
+}
+
+G_GNUC_INTERNAL
+void spice_session_set_main_channel(SpiceSession *session, SpiceChannel *channel)
+{
+    g_return_if_fail(SPICE_IS_SESSION(session));
+    g_return_if_fail(SPICE_IS_CHANNEL(channel));
+    g_return_if_fail(session->priv->cmain == NULL);
+
+    session->priv->cmain = channel;
+}
+
+G_GNUC_INTERNAL
+gboolean spice_session_set_migration_session(SpiceSession *session, SpiceSession *mig_session)
+{
+    g_return_val_if_fail(SPICE_IS_SESSION(session), FALSE);
+    g_return_val_if_fail(SPICE_IS_SESSION(mig_session), FALSE);
+    g_return_val_if_fail(session->priv->migration == NULL, FALSE);
+
+    session->priv->migration = mig_session;
+
+    return TRUE;
+}
diff --git a/src/spice-session.h b/src/spice-session.h
new file mode 100644
index 0000000..750af29
--- /dev/null
+++ b/src/spice-session.h
@@ -0,0 +1,103 @@
+/* -*- 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_CLIENT_SESSION_H__
+#define __SPICE_CLIENT_SESSION_H__
+
+#include <glib-object.h>
+#include "spice-types.h"
+#include "spice-uri.h"
+#include "spice-glib-enums.h"
+#include "spice-util.h"
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_SESSION            (spice_session_get_type ())
+#define SPICE_SESSION(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), SPICE_TYPE_SESSION, SpiceSession))
+#define SPICE_SESSION_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SPICE_TYPE_SESSION, SpiceSessionClass))
+#define SPICE_IS_SESSION(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SPICE_TYPE_SESSION))
+#define SPICE_IS_SESSION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SPICE_TYPE_SESSION))
+#define SPICE_SESSION_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SPICE_TYPE_SESSION, SpiceSessionClass))
+
+/**
+ * SpiceSessionVerify:
+ * @SPICE_SESSION_VERIFY_PUBKEY: verify certificate public key matching
+ * @SPICE_SESSION_VERIFY_HOSTNAME: verify certificate hostname matching
+ * @SPICE_SESSION_VERIFY_SUBJECT: verify certificate subject matching
+ *
+ * Peer certificate verification parameters flags.
+ **/
+typedef enum {
+    SPICE_SESSION_VERIFY_PUBKEY   = (1 << 0),
+    SPICE_SESSION_VERIFY_HOSTNAME = (1 << 1),
+    SPICE_SESSION_VERIFY_SUBJECT  = (1 << 2),
+} SpiceSessionVerify;
+
+/**
+ * SpiceSessionMigration:
+ * @SPICE_SESSION_MIGRATION_NONE: no migration going on
+ * @SPICE_SESSION_MIGRATION_SWITCHING: the session is switching host (destroy and reconnect)
+ * @SPICE_SESSION_MIGRATION_MIGRATING: the session is migrating seamlessly (reconnect)
+ * @SPICE_SESSION_MIGRATION_CONNECTING: the migration is connecting to destination (Since: 0.27)
+ *
+ * Session migration state.
+ **/
+typedef enum {
+    SPICE_SESSION_MIGRATION_NONE,
+    SPICE_SESSION_MIGRATION_SWITCHING,
+    SPICE_SESSION_MIGRATION_MIGRATING,
+    SPICE_SESSION_MIGRATION_CONNECTING,
+} SpiceSessionMigration;
+
+struct _SpiceSession
+{
+    GObject parent;
+    SpiceSessionPrivate *priv;
+    /* Do not add fields to this struct */
+};
+
+struct _SpiceSessionClass
+{
+    GObjectClass parent_class;
+
+    /* signals */
+    void (*channel_new)(SpiceSession *session, SpiceChannel *channel);
+    void (*channel_destroy)(SpiceSession *session, SpiceChannel *channel);
+
+    /*< private >*/
+    /*
+     * If adding fields to this struct, remove corresponding
+     * amount of padding to avoid changing overall struct size
+     */
+    gchar _spice_reserved[SPICE_RESERVED_PADDING];
+};
+
+GType spice_session_get_type(void);
+
+SpiceSession *spice_session_new(void);
+gboolean spice_session_connect(SpiceSession *session);
+gboolean spice_session_open_fd(SpiceSession *session, int fd);
+void spice_session_disconnect(SpiceSession *session);
+GList *spice_session_get_channels(SpiceSession *session);
+gboolean spice_session_has_channel_type(SpiceSession *session, gint type);
+gboolean spice_session_get_read_only(SpiceSession *session);
+SpiceURI *spice_session_get_proxy_uri(SpiceSession *session);
+gboolean spice_session_is_for_migration(SpiceSession *session);
+
+G_END_DECLS
+
+#endif /* __SPICE_CLIENT_SESSION_H__ */
diff --git a/src/spice-types.h b/src/spice-types.h
new file mode 100644
index 0000000..f149094
--- /dev/null
+++ b/src/spice-types.h
@@ -0,0 +1,35 @@
+/* -*- 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_CLIENT_TYPES_H__
+#define __SPICE_CLIENT_TYPES_H__
+
+G_BEGIN_DECLS
+
+/* SpiceSession */
+typedef struct _SpiceSession SpiceSession;
+typedef struct _SpiceSessionClass SpiceSessionClass;
+typedef struct _SpiceSessionPrivate SpiceSessionPrivate;
+
+/* SpiceChannel */
+typedef struct _SpiceChannel SpiceChannel;
+typedef struct _SpiceChannelClass SpiceChannelClass;
+typedef struct _SpiceChannelPrivate SpiceChannelPrivate;
+
+G_END_DECLS
+
+#endif /* __SPICE_CLIENT_TYPES_H__ */
diff --git a/src/spice-uri-priv.h b/src/spice-uri-priv.h
new file mode 100644
index 0000000..54351de
--- /dev/null
+++ b/src/spice-uri-priv.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+  Copyright (C) 2012 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_URI_PRIV_H__
+#define __SPICE_URI_PRIV_H__
+
+#include "spice-uri.h"
+
+G_BEGIN_DECLS
+
+SpiceURI* spice_uri_new(void);
+gboolean spice_uri_parse(SpiceURI* self, const gchar* uri, GError** error);
+
+G_END_DECLS
+
+#endif /* __SPICE_URI_PRIV_H__ */
diff --git a/src/spice-uri.c b/src/spice-uri.c
new file mode 100644
index 0000000..82aefdb
--- /dev/null
+++ b/src/spice-uri.c
@@ -0,0 +1,462 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2012 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 <stdlib.h>
+#include <string.h>
+
+#include "glib-compat.h"
+#include "spice-client.h"
+#include "spice-uri.h"
+
+/**
+ * SECTION:spice-uri
+ * @short_description: URIs handling
+ * @title: SpiceURI
+ * @section_id:
+ * @stability: Stable
+ * @include: spice-uri.h
+ *
+ * A SpiceURI represents a (parsed) URI.
+ * Since: 0.24
+ */
+
+struct _SpiceURI {
+    GObject parent_instance;
+    gchar *scheme;
+    gchar *hostname;
+    guint port;
+    gchar *user;
+    gchar *password;
+};
+
+struct _SpiceURIClass {
+    GObjectClass parent_class;
+};
+
+G_DEFINE_TYPE(SpiceURI, spice_uri, G_TYPE_OBJECT);
+
+enum  {
+    SPICE_URI_DUMMY_PROPERTY,
+    SPICE_URI_SCHEME,
+    SPICE_URI_USER,
+    SPICE_URI_PASSWORD,
+    SPICE_URI_HOSTNAME,
+    SPICE_URI_PORT
+};
+
+G_GNUC_INTERNAL
+SpiceURI* spice_uri_new(void)
+{
+    SpiceURI * self = NULL;
+    self = (SpiceURI*)g_object_new(SPICE_TYPE_URI, NULL);
+    return self;
+}
+
+G_GNUC_INTERNAL
+gboolean spice_uri_parse(SpiceURI *self, const gchar *_uri, GError **error)
+{
+    gchar *dup, *uri;
+    gboolean success = FALSE;
+    size_t len;
+
+    g_return_val_if_fail(self != NULL, FALSE);
+    g_return_val_if_fail(_uri != NULL, FALSE);
+
+    uri = dup = g_strdup(_uri);
+    /* FIXME: use GUri when it is ready... only support http atm */
+    /* the code is voluntarily not parsing thoroughly the uri */
+    if (g_ascii_strncasecmp("http://", uri, 7) == 0) {
+        uri += 7;
+        spice_uri_set_scheme(self, "http");
+        spice_uri_set_port(self, 3128);
+    } else if (g_ascii_strncasecmp("https://", uri, 8) == 0) {
+        uri += 8;
+        spice_uri_set_scheme(self, "https");
+        spice_uri_set_port(self, 3129);
+    } else {
+        spice_uri_set_scheme(self, "http");
+        spice_uri_set_port(self, 3128);
+    }
+    /* remove trailing slash */
+    len = strlen(uri);
+    for (; len > 0; len--)
+        if (uri[len-1] == '/')
+            uri[len-1] = '\0';
+        else
+            break;
+
+
+    /* yes, that parser is bad, we need GUri... */
+    if (strstr(uri, "@")) {
+        gchar *saveptr = NULL, *saveptr2 = NULL;
+        gchar *next = strstr(uri, "@") + 1;
+        gchar *auth = strtok_r(uri, "@", &saveptr);
+        const gchar *user = strtok_r(auth, ":", &saveptr2);
+        const gchar *pass = strtok_r(NULL, ":", &saveptr2);
+        spice_uri_set_user(self, user);
+        spice_uri_set_password(self, pass);
+        uri = next;
+    }
+
+    /* max 2 parts, host:port */
+    gchar **uriv = g_strsplit(uri, ":", 2);
+    const gchar *uri_port = NULL;
+
+    if (uriv[0] == NULL || strlen(uriv[0]) == 0) {
+        g_set_error(error, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                    "Invalid hostname in uri address");
+        goto end;
+    }
+
+    spice_uri_set_hostname(self, uriv[0]);
+    if (uriv[0] != NULL)
+        uri_port = uriv[1];
+
+    if (uri_port != NULL) {
+        char *endptr;
+        guint port = strtoul(uri_port, &endptr, 10);
+        if (*endptr != '\0') {
+            g_set_error(error, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                        "Invalid uri port: %s", uri_port);
+            goto end;
+        }
+        spice_uri_set_port(self, port);
+    }
+
+    success = TRUE;
+
+end:
+    g_free(dup);
+    g_strfreev(uriv);
+    return success;
+}
+
+/**
+ * spice_uri_get_scheme:
+ * @uri: a #SpiceURI
+ *
+ * Gets @uri's scheme.
+ *
+ * Returns: @uri's scheme.
+ * Since: 0.24
+ **/
+const gchar* spice_uri_get_scheme(SpiceURI *self)
+{
+    g_return_val_if_fail(SPICE_IS_URI(self), NULL);
+    return self->scheme;
+}
+
+/**
+ * spice_uri_set_scheme:
+ * @uri: a #SpiceURI
+ * @scheme: the scheme
+ *
+ * Sets @uri's scheme to @scheme.
+ * Since: 0.24
+ **/
+void spice_uri_set_scheme(SpiceURI *self, const gchar *scheme)
+{
+    g_return_if_fail(SPICE_IS_URI(self));
+
+    g_free(self->scheme);
+    self->scheme = g_strdup(scheme);
+    g_object_notify((GObject *)self, "scheme");
+}
+
+/**
+ * spice_uri_get_hostname:
+ * @uri: a #SpiceURI
+ *
+ * Gets @uri's hostname.
+ *
+ * Returns: @uri's hostname.
+ * Since: 0.24
+ **/
+const gchar* spice_uri_get_hostname(SpiceURI *self)
+{
+    g_return_val_if_fail(SPICE_IS_URI(self), NULL);
+    return self->hostname;
+}
+
+
+/**
+ * spice_uri_set_hostname:
+ * @uri: a #SpiceURI
+ * @hostname: the hostname
+ *
+ * Sets @uri's hostname to @hostname.
+ * Since: 0.24
+ **/
+void spice_uri_set_hostname(SpiceURI *self, const gchar *hostname)
+{
+    g_return_if_fail(SPICE_IS_URI(self));
+
+    g_free(self->hostname);
+    self->hostname = g_strdup(hostname);
+    g_object_notify((GObject *)self, "hostname");
+}
+
+/**
+ * spice_uri_get_port:
+ * @uri: a #SpiceURI
+ *
+ * Gets @uri's port.
+ *
+ * Returns: @uri's port.
+ * Since: 0.24
+ **/
+guint spice_uri_get_port(SpiceURI *self)
+{
+    g_return_val_if_fail(SPICE_IS_URI(self), 0);
+    return self->port;
+}
+
+/**
+ * spice_uri_set_port:
+ * @uri: a #SpiceURI
+ * @port: the port
+ *
+ * Sets @uri's port to @port.
+ * Since: 0.24
+ **/
+void spice_uri_set_port(SpiceURI *self, guint port)
+{
+    g_return_if_fail(SPICE_IS_URI(self));
+    self->port = port;
+    g_object_notify((GObject *)self, "port");
+}
+
+static void spice_uri_get_property(GObject *object, guint property_id,
+                                     GValue *value, GParamSpec *pspec)
+{
+    SpiceURI *self;
+    self = G_TYPE_CHECK_INSTANCE_CAST(object, SPICE_TYPE_URI, SpiceURI);
+
+    switch (property_id) {
+    case SPICE_URI_SCHEME:
+        g_value_set_string(value, spice_uri_get_scheme(self));
+        break;
+    case SPICE_URI_HOSTNAME:
+        g_value_set_string(value, spice_uri_get_hostname(self));
+        break;
+    case SPICE_URI_PORT:
+        g_value_set_uint(value, spice_uri_get_port(self));
+        break;
+    case SPICE_URI_USER:
+        g_value_set_string(value, spice_uri_get_user(self));
+        break;
+    case SPICE_URI_PASSWORD:
+        g_value_set_string(value, spice_uri_get_password(self));
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+        break;
+    }
+}
+
+
+static void spice_uri_set_property(GObject *object, guint property_id,
+                                     const GValue *value, GParamSpec *pspec)
+{
+    SpiceURI * self;
+    self = G_TYPE_CHECK_INSTANCE_CAST(object, SPICE_TYPE_URI, SpiceURI);
+
+    switch (property_id) {
+    case SPICE_URI_SCHEME:
+        spice_uri_set_scheme(self, g_value_get_string(value));
+        break;
+    case SPICE_URI_HOSTNAME:
+        spice_uri_set_hostname(self, g_value_get_string(value));
+        break;
+    case SPICE_URI_USER:
+        spice_uri_set_user(self, g_value_get_string(value));
+        break;
+    case SPICE_URI_PASSWORD:
+        spice_uri_set_password(self, g_value_get_string(value));
+        break;
+    case SPICE_URI_PORT:
+        spice_uri_set_port(self, g_value_get_uint(value));
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+        break;
+    }
+}
+
+static void spice_uri_finalize(GObject* obj)
+{
+    SpiceURI *self;
+
+    self = G_TYPE_CHECK_INSTANCE_CAST(obj, SPICE_TYPE_URI, SpiceURI);
+    g_free(self->scheme);
+    g_free(self->hostname);
+    g_free(self->user);
+    g_free(self->password);
+
+    G_OBJECT_CLASS (spice_uri_parent_class)->finalize (obj);
+}
+
+static void spice_uri_init (SpiceURI *self)
+{
+}
+
+
+static void spice_uri_class_init(SpiceURIClass *klass)
+{
+    spice_uri_parent_class = g_type_class_peek_parent (klass);
+
+    G_OBJECT_CLASS (klass)->get_property = spice_uri_get_property;
+    G_OBJECT_CLASS (klass)->set_property = spice_uri_set_property;
+    G_OBJECT_CLASS (klass)->finalize = spice_uri_finalize;
+
+    g_object_class_install_property(G_OBJECT_CLASS (klass),
+                                    SPICE_URI_SCHEME,
+                                    g_param_spec_string ("scheme",
+                                                         "scheme",
+                                                         "scheme",
+                                                         NULL,
+                                                         G_PARAM_STATIC_STRINGS |
+                                                         G_PARAM_READWRITE));
+
+    g_object_class_install_property(G_OBJECT_CLASS (klass),
+                                    SPICE_URI_HOSTNAME,
+                                    g_param_spec_string ("hostname",
+                                                         "hostname",
+                                                         "hostname",
+                                                         NULL,
+                                                         G_PARAM_STATIC_STRINGS |
+                                                         G_PARAM_READWRITE));
+
+    g_object_class_install_property(G_OBJECT_CLASS (klass),
+                                    SPICE_URI_PORT,
+                                    g_param_spec_uint ("port",
+                                                       "port",
+                                                       "port",
+                                                       0, G_MAXUINT, 0,
+                                                       G_PARAM_STATIC_STRINGS |
+                                                       G_PARAM_READWRITE));
+
+    g_object_class_install_property(G_OBJECT_CLASS (klass),
+                                    SPICE_URI_USER,
+                                    g_param_spec_string ("user",
+                                                         "user",
+                                                         "user",
+                                                         NULL,
+                                                         G_PARAM_STATIC_STRINGS |
+                                                         G_PARAM_READWRITE));
+
+    g_object_class_install_property(G_OBJECT_CLASS (klass),
+                                    SPICE_URI_PASSWORD,
+                                    g_param_spec_string ("password",
+                                                         "password",
+                                                         "password",
+                                                         NULL,
+                                                         G_PARAM_STATIC_STRINGS |
+                                                         G_PARAM_READWRITE));
+}
+
+/**
+ * spice_uri_to_string:
+ * @uri: a #SpiceURI
+ *
+ * Returns a string representing @uri.
+ *
+ * Returns: a string representing @uri, which the caller must free.
+ * Since: 0.24
+ **/
+gchar* spice_uri_to_string(SpiceURI* self)
+{
+    g_return_val_if_fail(SPICE_IS_URI(self), NULL);
+
+    if (self->scheme == NULL || self->hostname == NULL)
+        return NULL;
+
+    if (self->user || self->password)
+        return g_strdup_printf("%s://%s:%s@%s:%u",
+                               self->scheme,
+                               self->user, self->password,
+                               self->hostname, self->port);
+    else
+        return g_strdup_printf("%s://%s:%u",
+                               self->scheme, self->hostname, self->port);
+}
+
+/**
+ * spice_uri_get_user:
+ * @uri: a #SpiceURI
+ *
+ * Gets @uri's user.
+ *
+ * Returns: @uri's user.
+ * Since: 0.24
+ **/
+const gchar* spice_uri_get_user(SpiceURI *self)
+{
+    g_return_val_if_fail(SPICE_IS_URI(self), NULL);
+    return self->user;
+}
+
+/**
+ * spice_uri_set_user:
+ * @uri: a #SpiceURI
+ * @user: the user, or %NULL.
+ *
+ * Sets @uri's user to @user.
+ * Since: 0.24
+ **/
+void spice_uri_set_user(SpiceURI *self, const gchar *user)
+{
+    g_return_if_fail(SPICE_IS_URI(self));
+
+    g_free(self->user);
+    self->user = g_strdup(user);
+    g_object_notify((GObject *)self, "user");
+}
+
+/**
+ * spice_uri_get_password:
+ * @uri: a #SpiceURI
+ *
+ * Gets @uri's password.
+ *
+ * Returns: @uri's password.
+ * Since: 0.24
+ **/
+const gchar* spice_uri_get_password(SpiceURI *self)
+{
+    g_return_val_if_fail(SPICE_IS_URI(self), NULL);
+    return self->password;
+}
+
+/**
+ * spice_uri_set_password:
+ * @uri: a #SpiceURI
+ * @password: the password, or %NULL.
+ *
+ * Sets @uri's password to @password.
+ * Since: 0.24
+ **/
+void spice_uri_set_password(SpiceURI *self, const gchar *password)
+{
+    g_return_if_fail(SPICE_IS_URI(self));
+
+    g_free(self->password);
+    self->password = g_strdup(password);
+    g_object_notify((GObject *)self, "password");
+}
diff --git a/src/spice-uri.h b/src/spice-uri.h
new file mode 100644
index 0000000..9e8d590
--- /dev/null
+++ b/src/spice-uri.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2012 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_URI_H__
+#define __SPICE_URI_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_URI (spice_uri_get_type ())
+#define SPICE_URI(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SPICE_TYPE_URI, SpiceURI))
+#define SPICE_URI_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SPICE_TYPE_URI, SpiceURIClass))
+#define SPICE_IS_URI(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SPICE_TYPE_URI))
+#define SPICE_IS_URI_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SPICE_TYPE_URI))
+#define SPICE_URI_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SPICE_TYPE_URI, SpiceURIClass))
+
+typedef struct _SpiceURI SpiceURI;
+typedef struct _SpiceURIClass SpiceURIClass;
+typedef struct _SpiceURIPrivate SpiceURIPrivate;
+
+GType spice_uri_get_type(void) G_GNUC_CONST;
+
+const gchar* spice_uri_get_scheme(SpiceURI* uri);
+void spice_uri_set_scheme(SpiceURI* uri, const gchar* scheme);
+const gchar* spice_uri_get_hostname(SpiceURI* uri);
+void spice_uri_set_hostname(SpiceURI* uri, const gchar* hostname);
+guint spice_uri_get_port(SpiceURI* uri);
+void spice_uri_set_port(SpiceURI* uri, guint port);
+gchar *spice_uri_to_string(SpiceURI* uri);
+const gchar* spice_uri_get_user(SpiceURI* uri);
+void spice_uri_set_user(SpiceURI* uri, const gchar* user);
+const gchar* spice_uri_get_password(SpiceURI* uri);
+void spice_uri_set_password(SpiceURI* uri, const gchar* password);
+
+G_END_DECLS
+
+#endif /* __SPICE_URI_H__ */
diff --git a/src/spice-util-priv.h b/src/spice-util-priv.h
new file mode 100644
index 0000000..c0ea8d9
--- /dev/null
+++ b/src/spice-util-priv.h
@@ -0,0 +1,52 @@
+/* -*- 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_UTIL_PRIV_H
+#define SPICE_UTIL_PRIV_H
+
+#include <glib.h>
+#include "spice-util.h"
+
+G_BEGIN_DECLS
+
+#define UUID_FMT "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x"
+
+gboolean spice_strv_contains(const GStrv strv, const gchar *str);
+const gchar* spice_yes_no(gboolean value);
+guint16 spice_make_scancode(guint scancode, gboolean release);
+gchar* spice_unix2dos(const gchar *str, gssize len, GError **error);
+gchar* spice_dos2unix(const gchar *str, gssize len, GError **error);
+void spice_mono_edge_highlight(unsigned width, unsigned hight,
+                               const guint8 *and, const guint8 *xor, guint8 *dest);
+
+#if GLIB_CHECK_VERSION(2,32,0)
+#define STATIC_MUTEX            GMutex
+#define STATIC_MUTEX_INIT(m)    g_mutex_init(&(m))
+#define STATIC_MUTEX_CLEAR(m)   g_mutex_clear(&(m))
+#define STATIC_MUTEX_LOCK(m)    g_mutex_lock(&(m))
+#define STATIC_MUTEX_UNLOCK(m)  g_mutex_unlock(&(m))
+#else
+#define STATIC_MUTEX            GStaticMutex
+#define STATIC_MUTEX_INIT(m)    g_static_mutex_init(&(m))
+#define STATIC_MUTEX_CLEAR(m)   g_static_mutex_free(&(m))
+#define STATIC_MUTEX_LOCK(m)    g_static_mutex_lock(&(m))
+#define STATIC_MUTEX_UNLOCK(m)  g_static_mutex_unlock(&(m))
+#endif
+
+G_END_DECLS
+
+#endif /* SPICE_UTIL_PRIV_H */
diff --git a/src/spice-util.c b/src/spice-util.c
new file mode 100644
index 0000000..bec237b
--- /dev/null
+++ b/src/spice-util.c
@@ -0,0 +1,497 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2010 Red Hat, Inc.
+   Copyright © 2006-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+
+   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 <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+#include <glib-object.h>
+#include "spice-util-priv.h"
+#include "spice-util.h"
+#include "spice-util-priv.h"
+
+/**
+ * SECTION:spice-util
+ * @short_description: version and debugging functions
+ * @title: Utilities
+ * @section_id:
+ * @stability: Stable
+ * @include: spice-util.h
+ *
+ * Various functions for debugging and informational purposes.
+ */
+
+static GOnce debug_once = G_ONCE_INIT;
+
+static void spice_util_enable_debug_messages(void)
+{
+#if GLIB_CHECK_VERSION(2, 31, 0)
+    const gchar *doms = g_getenv("G_MESSAGES_DEBUG");
+    if (!doms) {
+        g_setenv("G_MESSAGES_DEBUG", G_LOG_DOMAIN, 1);
+    } else if (g_str_equal(doms, "all")) {
+	return;
+    } else if (!strstr(doms, G_LOG_DOMAIN)) {
+        gchar *newdoms = g_strdup_printf("%s %s", doms, G_LOG_DOMAIN);
+        g_setenv("G_MESSAGES_DEBUG", newdoms, 1);
+        g_free(newdoms);
+    }
+#endif
+}
+
+/**
+ * spice_util_set_debug:
+ * @enabled: %TRUE or %FALSE
+ *
+ * Enable or disable Spice-GTK debugging messages.
+ **/
+void spice_util_set_debug(gboolean enabled)
+{
+    /* Make sure debug_once has been initialised
+     * with the value of SPICE_DEBUG already, otherwise
+     * spice_util_get_debug() may overwrite the value
+     * that was just set using spice_util_set_debug()
+     */
+    spice_util_get_debug();
+
+    if (enabled) {
+        spice_util_enable_debug_messages();
+    }
+
+    debug_once.retval = GINT_TO_POINTER(enabled);
+}
+
+static gpointer getenv_debug(gpointer data)
+{
+    gboolean debug;
+
+    debug = (g_getenv("SPICE_DEBUG") != NULL);
+    if (debug)
+        spice_util_enable_debug_messages();
+
+    return GINT_TO_POINTER(debug);
+}
+
+gboolean spice_util_get_debug(void)
+{
+    g_once(&debug_once, getenv_debug, NULL);
+
+    return GPOINTER_TO_INT(debug_once.retval);
+}
+
+/**
+ * spice_util_get_version_string:
+ *
+ * Returns: Spice-GTK version as a const string.
+ **/
+const gchar *spice_util_get_version_string(void)
+{
+    return VERSION;
+}
+
+G_GNUC_INTERNAL
+gboolean spice_strv_contains(const GStrv strv, const gchar *str)
+{
+    int i;
+
+    if (strv == NULL)
+        return FALSE;
+
+    for (i = 0; strv[i] != NULL; i++)
+        if (g_str_equal(strv[i], str))
+            return TRUE;
+
+    return FALSE;
+}
+
+/**
+ * spice_uuid_to_string:
+ * @uuid: UUID byte array
+ *
+ * Creates a string representation of @uuid, of the form
+ * "06e023d5-86d8-420e-8103-383e4566087a"
+ *
+ * Returns: A string that should be freed with g_free().
+ * Since: 0.22
+ **/
+gchar* spice_uuid_to_string(const guint8 uuid[16])
+{
+    return g_strdup_printf(UUID_FMT, uuid[0], uuid[1],
+                           uuid[2], uuid[3], uuid[4], uuid[5],
+                           uuid[6], uuid[7], uuid[8], uuid[9],
+                           uuid[10], uuid[11], uuid[12], uuid[13],
+                           uuid[14], uuid[15]);
+}
+
+typedef struct {
+    GObject *instance;
+    GObject *observer;
+    GClosure *closure;
+    gulong handler_id;
+} WeakHandlerCtx;
+
+static WeakHandlerCtx *
+whc_new (GObject *instance,
+         GObject *observer)
+{
+    WeakHandlerCtx *ctx = g_slice_new0 (WeakHandlerCtx);
+
+    ctx->instance = instance;
+    ctx->observer = observer;
+
+    return ctx;
+}
+
+static void
+whc_free (WeakHandlerCtx *ctx)
+{
+    g_slice_free (WeakHandlerCtx, ctx);
+}
+
+static void observer_destroyed_cb (gpointer, GObject *);
+static void closure_invalidated_cb (gpointer, GClosure *);
+
+/*
+ * If signal handlers are removed before the object is destroyed, this
+ * callback will never get triggered.
+ */
+static void
+instance_destroyed_cb (gpointer ctx_,
+                       GObject *where_the_instance_was)
+{
+    WeakHandlerCtx *ctx = ctx_;
+
+    /* No need to disconnect the signal here, the instance has gone away. */
+    g_object_weak_unref (ctx->observer, observer_destroyed_cb, ctx);
+    g_closure_remove_invalidate_notifier (ctx->closure, ctx,
+                                          closure_invalidated_cb);
+    whc_free (ctx);
+}
+
+/* Triggered when the observer is destroyed. */
+static void
+observer_destroyed_cb (gpointer ctx_,
+                       GObject *where_the_observer_was)
+{
+    WeakHandlerCtx *ctx = ctx_;
+
+    g_closure_remove_invalidate_notifier (ctx->closure, ctx,
+                                          closure_invalidated_cb);
+    g_signal_handler_disconnect (ctx->instance, ctx->handler_id);
+    g_object_weak_unref (ctx->instance, instance_destroyed_cb, ctx);
+    whc_free (ctx);
+}
+
+/* Triggered when either object is destroyed or the handler is disconnected. */
+static void
+closure_invalidated_cb (gpointer ctx_,
+                        GClosure *where_the_closure_was)
+{
+    WeakHandlerCtx *ctx = ctx_;
+
+    g_object_weak_unref (ctx->instance, instance_destroyed_cb, ctx);
+    g_object_weak_unref (ctx->observer, observer_destroyed_cb, ctx);
+    whc_free (ctx);
+}
+
+/* Copied from tp_g_signal_connect_object. See documentation. */
+/**
+  * spice_g_signal_connect_object: (skip)
+  * @instance: the instance to connect to.
+  * @detailed_signal: a string of the form "signal-name::detail".
+  * @c_handler: the #GCallback to connect.
+  * @gobject: the object to pass as data to @c_handler.
+  * @connect_flags: a combination of #GConnectFlags.
+  *
+  * Similar to g_signal_connect_object() but will delete connection
+  * when any of the objects is destroyed.
+  *
+  * Returns: the handler id.
+  */
+gulong spice_g_signal_connect_object (gpointer instance,
+                                      const gchar *detailed_signal,
+                                      GCallback c_handler,
+                                      gpointer gobject,
+                                      GConnectFlags connect_flags)
+{
+    GObject *instance_obj = G_OBJECT (instance);
+    WeakHandlerCtx *ctx = whc_new (instance_obj, gobject);
+
+    g_return_val_if_fail (G_TYPE_CHECK_INSTANCE (instance), 0);
+    g_return_val_if_fail (detailed_signal != NULL, 0);
+    g_return_val_if_fail (c_handler != NULL, 0);
+    g_return_val_if_fail (G_IS_OBJECT (gobject), 0);
+    g_return_val_if_fail (
+                          (connect_flags & ~(G_CONNECT_AFTER|G_CONNECT_SWAPPED)) == 0, 0);
+
+    if (connect_flags & G_CONNECT_SWAPPED)
+        ctx->closure = g_cclosure_new_object_swap (c_handler, gobject);
+    else
+        ctx->closure = g_cclosure_new_object (c_handler, gobject);
+
+    ctx->handler_id = g_signal_connect_closure (instance, detailed_signal,
+                                                ctx->closure, (connect_flags & G_CONNECT_AFTER) ? TRUE : FALSE);
+
+    g_object_weak_ref (instance_obj, instance_destroyed_cb, ctx);
+    g_object_weak_ref (gobject, observer_destroyed_cb, ctx);
+    g_closure_add_invalidate_notifier (ctx->closure, ctx,
+                                       closure_invalidated_cb);
+
+    return ctx->handler_id;
+}
+
+G_GNUC_INTERNAL
+const gchar* spice_yes_no(gboolean value)
+{
+    return value ? "yes" : "no";
+}
+
+G_GNUC_INTERNAL
+guint16 spice_make_scancode(guint scancode, gboolean release)
+{
+    SPICE_DEBUG("%s: %s scancode %d",
+                __FUNCTION__, release ? "release" : "", scancode);
+
+    if (release) {
+        if (scancode < 0x100)
+            return scancode | 0x80;
+        else
+            return 0x80e0 | ((scancode - 0x100) << 8);
+    } else {
+        if (scancode < 0x100)
+            return scancode;
+        else
+            return 0xe0 | ((scancode - 0x100) << 8);
+    }
+
+    g_return_val_if_reached(0);
+}
+
+typedef enum {
+    NEWLINE_TYPE_LF,
+    NEWLINE_TYPE_CR_LF
+} NewlineType;
+
+static gssize get_line(const gchar *str, gsize len,
+                       NewlineType type, gsize *nl_len,
+                       GError **error)
+{
+    const gchar *p, *endl;
+    gsize nl = 0;
+
+    endl = (type == NEWLINE_TYPE_CR_LF) ? "\r\n" : "\n";
+    p = g_strstr_len(str, len, endl);
+    if (p) {
+        len = p - str;
+        nl = strlen(endl);
+    }
+
+    *nl_len = nl;
+    return len;
+}
+
+
+static gchar* spice_convert_newlines(const gchar *str, gssize len,
+                                     NewlineType from,
+                                     NewlineType to,
+                                     GError **error)
+{
+    GError *err = NULL;
+    gssize length;
+    gsize nl;
+    GString *output;
+    gboolean free_segment = FALSE;
+    gint i;
+
+    g_return_val_if_fail(str != NULL, NULL);
+    g_return_val_if_fail(len >= -1, NULL);
+    g_return_val_if_fail(error == NULL || *error == NULL, NULL);
+    /* only 2 supported combinations */
+    g_return_val_if_fail((from == NEWLINE_TYPE_LF &&
+                          to == NEWLINE_TYPE_CR_LF) ||
+                         (from == NEWLINE_TYPE_CR_LF &&
+                          to == NEWLINE_TYPE_LF), NULL);
+
+    if (len == -1)
+        len = strlen(str);
+    /* sometime we get \0 terminated strings, skip that, or it fails
+       to utf8 validate line with \0 end */
+    else if (len > 0 && str[len-1] == 0)
+        len -= 1;
+
+    /* allocate worst case, if it's small enough, we don't care much,
+     * if it's big, malloc will put us in mmap'd region, and we can
+     * over allocate.
+     */
+    output = g_string_sized_new(len * 2 + 1);
+
+    for (i = 0; i < len; i += length + nl) {
+        length = get_line(str + i, len - i, from, &nl, &err);
+        if (length < 0)
+            break;
+
+        g_string_append_len(output, str + i, length);
+
+        if (nl) {
+            /* let's not double \r if it's already in the line */
+            if (to == NEWLINE_TYPE_CR_LF &&
+                output->str[output->len - 1] != '\r')
+                g_string_append_c(output, '\r');
+
+            g_string_append_c(output, '\n');
+        }
+    }
+
+    if (err) {
+        g_propagate_error(error, err);
+        free_segment = TRUE;
+    }
+
+    return g_string_free(output, free_segment);
+}
+
+G_GNUC_INTERNAL
+gchar* spice_dos2unix(const gchar *str, gssize len, GError **error)
+{
+    return spice_convert_newlines(str, len,
+                                  NEWLINE_TYPE_CR_LF,
+                                  NEWLINE_TYPE_LF,
+                                  error);
+}
+
+G_GNUC_INTERNAL
+gchar* spice_unix2dos(const gchar *str, gssize len, GError **error)
+{
+    return spice_convert_newlines(str, len,
+                                  NEWLINE_TYPE_LF,
+                                  NEWLINE_TYPE_CR_LF,
+                                  error);
+}
+
+static bool buf_is_ones(unsigned size, const guint8 *data)
+{
+    int i;
+
+    for (i = 0 ; i < size; ++i) {
+        if (data[i] != 0xff) {
+            return false;
+        }
+    }
+    return true;
+}
+
+static bool is_edge_helper(const guint8 *xor, int bpl, int x, int y)
+{
+    return (xor[bpl * y + (x / 8)] & (0x80 >> (x % 8))) > 0;
+}
+
+static bool is_edge(unsigned width, unsigned height, const guint8 *xor, int bpl, int x, int y)
+{
+    if (x == 0 || x == width -1 || y == 0 || y == height - 1) {
+        return 0;
+    }
+#define P(x, y) is_edge_helper(xor, bpl, x, y)
+    return !P(x, y) && (P(x - 1, y + 1) || P(x, y + 1) || P(x + 1, y + 1) ||
+                        P(x - 1, y)     ||                P(x + 1, y)     ||
+                        P(x - 1, y - 1) || P(x, y - 1) || P(x + 1, y - 1));
+#undef P
+}
+
+/* Mono cursors have two places, "and" and "xor". If a bit is 1 in both, it
+ * means invertion of the corresponding pixel in the display. Since X11 (and
+ * gdk) doesn't do invertion, instead we do edge detection and turn the
+ * sorrounding edge pixels black, and the invert-me pixels white. To
+ * illustrate:
+ *
+ *  and   xor      dest RGB (1=0xffffff, 0=0x000000)
+ *
+ *                        dest alpha (1=0xff, 0=0x00)
+ *
+ * 11111 00000     00000  00000
+ * 11111 00000     00000  01110
+ * 11111 00100 =>  00100  01110
+ * 11111 00100     00100  01110
+ * 11111 00000     00000  01110
+ * 11111 00000     00000  00000
+ *
+ * See tests/util.c for more tests
+ *
+ * Notes:
+ *  Assumes width >= 8 (i.e. bytes per line is at least 1)
+ *  Assumes edges are not on the boundary (first/last line/column) for simplicity
+ *
+ */
+G_GNUC_INTERNAL
+void spice_mono_edge_highlight(unsigned width, unsigned height,
+                               const guint8 *and, const guint8 *xor, guint8 *dest)
+{
+    int bpl = (width + 7) / 8;
+    bool and_ones = buf_is_ones(height * bpl, and);
+    int x, y, bit;
+    const guint8 *xor_base = xor;
+
+    for (y = 0; y < height; y++) {
+        bit = 0x80;
+        for (x = 0; x < width; x++, dest += 4) {
+            if (is_edge(width, height, xor_base, bpl, x, y) && and_ones) {
+                dest[0] = 0x00;
+                dest[1] = 0x00;
+                dest[2] = 0x00;
+                dest[3] = 0xff;
+                goto next_bit;
+            }
+            if (and[x/8] & bit) {
+                if (xor[x/8] & bit) {
+                    dest[0] = 0xff;
+                    dest[1] = 0xff;
+                    dest[2] = 0xff;
+                    dest[3] = 0xff;
+                } else {
+                    /* unchanged -> transparent */
+                    dest[0] = 0x00;
+                    dest[1] = 0x00;
+                    dest[2] = 0x00;
+                    dest[3] = 0x00;
+                }
+            } else {
+                if (xor[x/8] & bit) {
+                    /* set -> white */
+                    dest[0] = 0xff;
+                    dest[1] = 0xff;
+                    dest[2] = 0xff;
+                    dest[3] = 0xff;
+                } else {
+                    /* clear -> black */
+                    dest[0] = 0x00;
+                    dest[1] = 0x00;
+                    dest[2] = 0x00;
+                    dest[3] = 0xff;
+                }
+            }
+        next_bit:
+            bit >>= 1;
+            if (bit == 0) {
+                bit = 0x80;
+            }
+        }
+        and += bpl;
+        xor += bpl;
+    }
+}
diff --git a/src/spice-util.h b/src/spice-util.h
new file mode 100644
index 0000000..3f429a0
--- /dev/null
+++ b/src/spice-util.h
@@ -0,0 +1,63 @@
+/* -*- 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_UTIL_H
+#define SPICE_UTIL_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+void spice_util_set_debug(gboolean enabled);
+gboolean spice_util_get_debug(void);
+const gchar *spice_util_get_version_string(void);
+gulong spice_g_signal_connect_object(gpointer instance,
+                                     const gchar *detailed_signal,
+                                     GCallback c_handler,
+                                     gpointer gobject,
+                                     GConnectFlags connect_flags);
+gchar* spice_uuid_to_string(const guint8 uuid[16]);
+
+#define SPICE_DEBUG(fmt, ...)                                   \
+    do {                                                        \
+        if (G_UNLIKELY(spice_util_get_debug()))                 \
+            g_debug(G_STRLOC " " fmt, ## __VA_ARGS__);          \
+    } while (0)
+
+#define SPICE_RESERVED_PADDING (10 * sizeof(void*))
+
+/* need to be in a public header, glib-compat.h is private */
+#ifndef SPICE_GNUC_DEPRECATED_FOR
+#if    __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)
+#define SPICE_GNUC_DEPRECATED_FOR(f)                        \
+  __attribute__((deprecated("Use " #f " instead")))
+#else
+#define SPICE_GNUC_DEPRECATED_FOR(f)        G_GNUC_DEPRECATED
+#endif /* __GNUC__ */
+#endif
+
+#ifndef SPICE_NO_DEPRECATED
+#define SPICE_DEPRECATED_FOR(f)  SPICE_GNUC_DEPRECATED_FOR(f)
+#define SPICE_DEPRECATED  G_GNUC_DEPRECATED
+#else
+#define SPICE_DEPRECATED_FOR(f)
+#define SPICE_DEPRECATED
+#endif
+
+G_END_DECLS
+
+#endif /* SPICE_UTIL_H */
diff --git a/src/spice-version.h.in b/src/spice-version.h.in
new file mode 100644
index 0000000..4276a23
--- /dev/null
+++ b/src/spice-version.h.in
@@ -0,0 +1,70 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+  Copyright (C) 2014 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_VERSION_H__
+#define __SPICE_VERSION_H__
+
+/**
+ * SECTION:spice-version
+ * @short_description: Spice-Gtk version checking
+ *
+ * Spice-Gtk provides macros to check the version of the library
+ * at compile-time
+ */
+
+/**
+ * SPICE_GTK_MAJOR_VERSION:
+ *
+ * Spice-Gtk major version component (e.g. 1 if version is 1.2.3)
+ * Since: 0.24
+ */
+#define SPICE_GTK_MAJOR_VERSION              (@SPICE_GTK_MAJOR_VERSION@)
+
+/**
+ * SPICE_GTK_MINOR_VERSION:
+ *
+ * Spice-Gtk minor version component (e.g. 2 if version is 1.2.3)
+ * Since: 0.24
+ */
+#define SPICE_GTK_MINOR_VERSION              (@SPICE_GTK_MINOR_VERSION@)
+
+/**
+ * SPICE_GTK_MICRO_VERSION:
+ *
+ * Spice-Gtk micro version component (e.g. 3 if version is 1.2.3)
+ * Since: 0.24
+ */
+#define SPICE_GTK_MICRO_VERSION              (@SPICE_GTK_MICRO_VERSION@)
+
+/**
+ * SPICE_GTK_CHECK_VERSION:
+ * @major: required major version
+ * @minor: required minor version
+ * @micro: required micro version
+ *
+ * Compile-time version checking. Evaluates to %TRUE if the version
+ * of Spice-Gtk is greater than the required one.
+ * Since: 0.24
+ */
+#define SPICE_GTK_CHECK_VERSION(major, minor, micro)                    \
+        (SPICE_GTK_MAJOR_VERSION > (major) ||                           \
+         (SPICE_GTK_MAJOR_VERSION == (major) && SPICE_GTK_MINOR_VERSION > (minor)) || \
+         (SPICE_GTK_MAJOR_VERSION == (major) && SPICE_GTK_MINOR_VERSION == (minor) && \
+          SPICE_GTK_MICRO_VERSION >= (micro)))
+
+
+#endif /* __SPICE_VERSION_H__ */
diff --git a/src/spice-widget-cairo.c b/src/spice-widget-cairo.c
new file mode 100644
index 0000000..96af076
--- /dev/null
+++ b/src/spice-widget-cairo.c
@@ -0,0 +1,160 @@
+/* -*- 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 "gtk-compat.h"
+#include "spice-widget.h"
+#include "spice-widget-priv.h"
+#include "spice-gtk-session-priv.h"
+
+
+G_GNUC_INTERNAL
+int spicex_image_create(SpiceDisplay *display)
+{
+    SpiceDisplayPrivate *d = display->priv;
+
+    if (d->ximage != NULL)
+        return 0;
+
+    if (d->format == SPICE_SURFACE_FMT_16_555 ||
+        d->format == SPICE_SURFACE_FMT_16_565) {
+        d->convert = TRUE;
+        d->data = g_malloc0(d->area.width * d->area.height * 4);
+
+        d->ximage = cairo_image_surface_create_for_data
+            (d->data, CAIRO_FORMAT_RGB24, d->area.width, d->area.height, d->area.width * 4);
+
+    } else {
+        d->convert = FALSE;
+
+        d->ximage = cairo_image_surface_create_for_data
+            (d->data, CAIRO_FORMAT_RGB24, d->width, d->height, d->stride);
+    }
+
+    return 0;
+}
+
+G_GNUC_INTERNAL
+void spicex_image_destroy(SpiceDisplay *display)
+{
+    SpiceDisplayPrivate *d = display->priv;
+
+    if (d->ximage) {
+        cairo_surface_destroy(d->ximage);
+        d->ximage = NULL;
+    }
+    if (d->convert && d->data) {
+        g_free(d->data);
+        d->data = NULL;
+    }
+    d->convert = FALSE;
+}
+
+G_GNUC_INTERNAL
+void spicex_draw_event(SpiceDisplay *display, cairo_t *cr)
+{
+    SpiceDisplayPrivate *d = display->priv;
+    cairo_rectangle_int_t rect;
+    cairo_region_t *region;
+    double s;
+    int x, y;
+    int ww, wh;
+    int w, h;
+
+    spice_display_get_scaling(display, &s, &x, &y, &w, &h);
+
+    gdk_drawable_get_size(gtk_widget_get_window(GTK_WIDGET(display)), &ww, &wh);
+
+    /* We need to paint the bg color around the image */
+    rect.x = 0;
+    rect.y = 0;
+    rect.width = ww;
+    rect.height = wh;
+    region = cairo_region_create_rectangle(&rect);
+
+    /* Optionally cut out the inner area where the pixmap
+       will be drawn. This avoids 'flashing' since we're
+       not double-buffering. */
+    if (d->ximage) {
+        rect.x = x;
+        rect.y = y;
+        rect.width = w;
+        rect.height = h;
+        cairo_region_subtract_rectangle(region, &rect);
+    }
+
+    gdk_cairo_region (cr, region);
+    cairo_region_destroy (region);
+
+    /* Need to set a real solid color, because the default is usually
+       transparent these days, and non-double buffered windows can't
+       render transparently */
+    cairo_set_source_rgb (cr, 0, 0, 0);
+    cairo_fill(cr);
+
+    /* Draw the display */
+    if (d->ximage) {
+        cairo_translate(cr, x, y);
+        cairo_rectangle(cr, 0, 0, w, h);
+        cairo_scale(cr, s, s);
+        if (!d->convert)
+            cairo_translate(cr, -d->area.x, -d->area.y);
+        cairo_set_source_surface(cr, d->ximage, 0, 0);
+        cairo_fill(cr);
+
+        if (d->mouse_mode == SPICE_MOUSE_MODE_SERVER &&
+            d->mouse_guest_x != -1 && d->mouse_guest_y != -1 &&
+            !d->show_cursor &&
+            spice_gtk_session_get_pointer_grabbed(d->gtk_session)) {
+            GdkPixbuf *image = d->mouse_pixbuf;
+            if (image != NULL) {
+                gdk_cairo_set_source_pixbuf(cr, image,
+                                            d->mouse_guest_x - d->mouse_hotspot.x,
+                                            d->mouse_guest_y - d->mouse_hotspot.y);
+                cairo_paint(cr);
+            }
+        }
+    }
+}
+
+#if ! GTK_CHECK_VERSION (2, 91, 0)
+G_GNUC_INTERNAL
+void spicex_expose_event(SpiceDisplay *display, GdkEventExpose *expose)
+{
+    cairo_t *cr;
+
+    cr = gdk_cairo_create(gtk_widget_get_window(GTK_WIDGET(display)));
+    cairo_rectangle(cr,
+                    expose->area.x,
+                    expose->area.y,
+                    expose->area.width,
+                    expose->area.height);
+    cairo_clip(cr);
+
+    spicex_draw_event(display, cr);
+
+    cairo_destroy(cr);
+}
+#endif
+
+G_GNUC_INTERNAL
+gboolean spicex_is_scaled(SpiceDisplay *display)
+{
+    SpiceDisplayPrivate *d = display->priv;
+    return d->allow_scaling;
+}
diff --git a/src/spice-widget-priv.h b/src/spice-widget-priv.h
new file mode 100644
index 0000000..0e1f661
--- /dev/null
+++ b/src/spice-widget-priv.h
@@ -0,0 +1,141 @@
+/* -*- 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_WIDGET_PRIV_H__
+#define __SPICE_WIDGET_PRIV_H__
+
+G_BEGIN_DECLS
+
+#include "config.h"
+
+#ifdef WITH_X11
+#include <X11/Xlib.h>
+#include <X11/extensions/XShm.h>
+#include <gdk/gdkx.h>
+#endif
+
+#ifdef WIN32
+#include <windows.h>
+#endif
+
+#include "spice-widget.h"
+#include "spice-common.h"
+#include "spice-gtk-session.h"
+
+#define SPICE_DISPLAY_GET_PRIVATE(obj)                                  \
+    (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_DISPLAY, SpiceDisplayPrivate))
+
+struct _SpiceDisplayPrivate {
+    gint                    channel_id;
+    gint                    monitor_id;
+
+    /* options */
+    bool                    keyboard_grab_enable;
+    gboolean                keyboard_grab_inhibit;
+    bool                    mouse_grab_enable;
+    bool                    resize_guest_enable;
+
+    /* state */
+    gboolean                ready;
+    gboolean                monitor_ready;
+    enum SpiceSurfaceFmt    format;
+    gint                    width, height, stride;
+    gint                    shmid;
+    gpointer                data_origin; /* the original display image data */
+    gpointer                data; /* converted if necessary to 32 bits */
+
+    GdkRectangle            area;
+    /* window border */
+    gint                    ww, wh, mx, my;
+
+    bool                    convert;
+    bool                    have_mitshm;
+    gboolean                allow_scaling;
+    gboolean                only_downscale;
+    gboolean                disable_inputs;
+
+    /* TODO: make a display object instead? */
+#ifdef WITH_X11
+    Display                 *dpy;
+    XVisualInfo             *vi;
+    XImage                  *ximage;
+    XShmSegmentInfo         *shminfo;
+    GC                      gc;
+#else
+    cairo_surface_t         *ximage;
+#endif
+
+    SpiceSession            *session;
+    SpiceGtkSession         *gtk_session;
+    SpiceMainChannel        *main;
+    SpiceChannel            *display;
+    SpiceCursorChannel      *cursor;
+    SpiceInputsChannel      *inputs;
+    SpiceSmartcardChannel   *smartcard;
+
+    enum SpiceMouseMode     mouse_mode;
+    int                     mouse_grab_active;
+    bool                    mouse_have_pointer;
+    GdkCursor               *mouse_cursor;
+    GdkPixbuf               *mouse_pixbuf;
+    GdkPoint                mouse_hotspot;
+    GdkCursor               *show_cursor;
+    int                     mouse_last_x;
+    int                     mouse_last_y;
+    int                     mouse_guest_x;
+    int                     mouse_guest_y;
+
+    bool                    keyboard_grab_active;
+    bool                    keyboard_have_focus;
+
+    const guint16          *keycode_map;
+    size_t                  keycode_maplen;
+    uint32_t                key_state[512 / 32];
+    int                     key_delayed_scancode;
+    guint                   key_delayed_id;
+    SpiceGrabSequence         *grabseq; /* the configured key sequence */
+    gboolean                *activeseq; /* the currently pressed keys */
+    gboolean                seq_pressed;
+    gboolean                keyboard_grab_released;
+    gint                    mark;
+#ifdef WIN32
+    HHOOK                   keyboard_hook;
+    int                     win_mouse[3];
+    int                     win_mouse_speed;
+#endif
+    guint                   keypress_delay;
+    gint                    zoom_level;
+#ifdef GDK_WINDOWING_X11
+    int                     x11_accel_numerator;
+    int                     x11_accel_denominator;
+    int                     x11_threshold;
+#endif
+};
+
+int      spicex_image_create                 (SpiceDisplay *display);
+void     spicex_image_destroy                (SpiceDisplay *display);
+#if GTK_CHECK_VERSION (2, 91, 0)
+void     spicex_draw_event                   (SpiceDisplay *display, cairo_t *cr);
+#else
+void     spicex_expose_event                 (SpiceDisplay *display, GdkEventExpose *ev);
+#endif
+gboolean spicex_is_scaled                    (SpiceDisplay *display);
+void     spice_display_get_scaling           (SpiceDisplay *display, double *s, int *x, int *y, int *w, int *h);
+
+G_END_DECLS
+
+#endif
diff --git a/src/spice-widget-x11.c b/src/spice-widget-x11.c
new file mode 100644
index 0000000..3f2ce94
--- /dev/null
+++ b/src/spice-widget-x11.c
@@ -0,0 +1,280 @@
+/* -*- 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-widget.h"
+#include "spice-widget-priv.h"
+
+#ifdef HAVE_SYS_SHM_H
+#include <sys/shm.h>
+#endif
+
+#ifdef HAVE_SYS_IPC_H
+#include <sys/ipc.h>
+#endif
+
+static bool no_mitshm;
+
+static struct format_table {
+    enum SpiceSurfaceFmt  spice;
+    XVisualInfo           xvisual;
+} format_table[] = {
+    {
+        .spice = SPICE_SURFACE_FMT_32_ARGB, /* FIXME: is that correct xvisual? */
+        .xvisual = {
+            .depth      = 24,
+            .red_mask   = 0xff0000,
+            .green_mask = 0x00ff00,
+            .blue_mask  = 0x0000ff,
+        },
+    },{
+        .spice = SPICE_SURFACE_FMT_32_xRGB,
+        .xvisual = {
+            .depth      = 24,
+            .red_mask   = 0xff0000,
+            .green_mask = 0x00ff00,
+            .blue_mask  = 0x0000ff,
+        },
+    },{
+        .spice = SPICE_SURFACE_FMT_16_555,
+        .xvisual = {
+            .depth      = 16,
+            .red_mask   = 0x7c00,
+            .green_mask = 0x03e0,
+            .blue_mask  = 0x001f,
+        },
+    },{
+        .spice = SPICE_SURFACE_FMT_16_565,
+        .xvisual = {
+            .depth      = 16,
+            .red_mask   = 0xf800,
+            .green_mask = 0x07e0,
+            .blue_mask  = 0x001f,
+        },
+    }
+};
+
+static XVisualInfo *get_visual_for_format(GtkWidget *widget, enum SpiceSurfaceFmt format)
+{
+    GdkDrawable  *drawable = gtk_widget_get_window(widget);
+    GdkDisplay   *display = gdk_drawable_get_display(drawable);
+    GdkScreen    *screen = gdk_drawable_get_screen(drawable);
+    XVisualInfo  template;
+    int          found, i;
+    XVisualInfo *vi;
+
+    for (i = 0; i < SPICE_N_ELEMENTS(format_table); i++) {
+        if (format == format_table[i].spice)
+            break;
+    }
+    if (i == SPICE_N_ELEMENTS(format_table)) {
+        g_warn_if_reached();
+        return NULL;
+    }
+
+    template = format_table[i].xvisual;
+    template.screen = gdk_x11_screen_get_screen_number(screen);
+    vi = XGetVisualInfo(gdk_x11_display_get_xdisplay(display),
+                        VisualScreenMask | VisualDepthMask |
+                        VisualRedMaskMask | VisualGreenMaskMask | VisualBlueMaskMask,
+                        &template, &found);
+    return vi;
+}
+
+static XVisualInfo *get_visual_default(GtkWidget *widget)
+{
+    GdkDrawable  *drawable = gtk_widget_get_window(widget);
+    GdkDisplay   *display = gdk_drawable_get_display(drawable);
+    GdkScreen    *screen = gdk_drawable_get_screen(drawable);
+    XVisualInfo  template;
+    int          found;
+
+    template.screen = gdk_x11_screen_get_screen_number(screen);
+    return XGetVisualInfo(gdk_x11_display_get_xdisplay(display),
+                          VisualScreenMask,
+                          &template, &found);
+}
+
+static int catch_no_mitshm(Display * dpy, XErrorEvent * event)
+{
+    no_mitshm = true;
+    return 0;
+}
+
+G_GNUC_INTERNAL
+int spicex_image_create(SpiceDisplay *display)
+{
+    SpiceDisplayPrivate   *d = display->priv;
+
+    if (d->ximage != NULL)
+        return 0;
+
+    GdkDrawable     *window = gtk_widget_get_window(GTK_WIDGET(display));
+    GdkDisplay      *gtkdpy = gdk_drawable_get_display(window);
+    void            *old_handler = NULL;
+    XGCValues       gcval = {
+        .foreground = 0,
+        .background = 0,
+    };
+
+    d->dpy = gdk_x11_display_get_xdisplay(gtkdpy);
+    d->convert = false;
+    d->vi = get_visual_for_format(GTK_WIDGET(display), d->format);
+    if (d->vi == NULL) {
+        d->convert = true;
+        d->vi = get_visual_default(GTK_WIDGET(display));
+        d->vi = get_visual_for_format(GTK_WIDGET(display), SPICE_SURFACE_FMT_32_xRGB);
+        g_return_val_if_fail(d->vi != NULL, 1);
+    }
+    if (d->convert) {
+        d->data = g_malloc0(d->height * d->stride); /* pixels are 32 bits */
+    }
+
+    d->gc = XCreateGC(d->dpy, gdk_x11_drawable_get_xid(window),
+                      GCForeground | GCBackground, &gcval);
+
+    if (d->convert) /* do not use shm when doing color format conversion */
+        goto xcreate;
+
+    if (d->have_mitshm && d->shmid != -1) {
+        if (!XShmQueryExtension(d->dpy)) {
+            goto shm_fail;
+        }
+        no_mitshm = false;
+        old_handler = XSetErrorHandler(catch_no_mitshm);
+        d->shminfo = g_new0(XShmSegmentInfo, 1);
+        d->ximage = XShmCreateImage(d->dpy, d->vi->visual, d->vi->depth,
+                                    ZPixmap, d->data, d->shminfo, d->width, d->height);
+        if (d->ximage == NULL)
+            goto shm_fail;
+        d->shminfo->shmaddr = d->data;
+        d->shminfo->shmid = d->shmid;
+        d->shminfo->readOnly = false;
+        XShmAttach(d->dpy, d->shminfo);
+        XSync(d->dpy, False);
+        shmctl(d->shmid, IPC_RMID, 0);
+        if (no_mitshm)
+            goto shm_fail;
+        XSetErrorHandler(old_handler);
+        return 0;
+    }
+
+ shm_fail:
+    d->have_mitshm = false;
+    g_free(d->shminfo);
+    d->shminfo = NULL;
+    if (old_handler)
+        XSetErrorHandler(old_handler);
+ xcreate:
+    d->ximage = XCreateImage(d->dpy, d->vi->visual, d->vi->depth, ZPixmap, 0,
+                             d->data, d->width, d->height, 32, d->stride);
+    return 0;
+}
+
+G_GNUC_INTERNAL
+void spicex_image_destroy(SpiceDisplay *display)
+{
+    SpiceDisplayPrivate *d = display->priv;
+
+    if (d->ximage) {
+        /* avoid XDestroy to free shared memory, owned and freed by
+           channel-display itself */
+        if (d->ximage->data == d->data_origin)
+            d->ximage->data = NULL;
+        XDestroyImage(d->ximage);
+        d->ximage = NULL;
+        if (d->convert)
+            d->data = 0;
+    }
+    if (d->shminfo) {
+        XShmDetach(d->dpy, d->shminfo);
+        free(d->shminfo);
+        d->shminfo = NULL;
+    }
+    if (d->gc) {
+        XFreeGC(d->dpy, d->gc);
+        d->gc = NULL;
+    }
+    if (d->convert && d->data) {
+        g_free(d->data);
+        d->data = NULL;
+    }
+}
+
+G_GNUC_INTERNAL
+void spicex_expose_event(SpiceDisplay *display, GdkEventExpose *expose)
+{
+    GdkDrawable *window = gtk_widget_get_window(GTK_WIDGET(display));
+    SpiceDisplayPrivate *d = display->priv;
+    int x, y, w, h;
+
+    spice_display_get_scaling(display, NULL, &x, &y, &w, &h);
+
+    if (expose->area.x >= x &&
+        expose->area.y >= y &&
+        expose->area.x + expose->area.width  <= x + w &&
+        expose->area.y + expose->area.height <= y + h) {
+        /* area is completely inside the guest screen -- blit it */
+        if (d->have_mitshm && d->shminfo) {
+            XShmPutImage(d->dpy, gdk_x11_drawable_get_xid(window),
+                         d->gc, d->ximage,
+                         d->area.x + expose->area.x - x, d->area.y + expose->area.y - y,
+                         expose->area.x, expose->area.y,
+                         expose->area.width, expose->area.height,
+                         true);
+        } else {
+            XPutImage(d->dpy, gdk_x11_drawable_get_xid(window),
+                      d->gc, d->ximage,
+                      d->area.x + expose->area.x - x, d->area.y + expose->area.y - y,
+                      expose->area.x, expose->area.y,
+                      expose->area.width, expose->area.height);
+        }
+    } else {
+        /* complete window update */
+        if (d->ww > d->area.width || d->wh > d->area.height) {
+            int x1 = x;
+            int x2 = x + w;
+            int y1 = y;
+            int y2 = y + h;
+            XFillRectangle(d->dpy, gdk_x11_drawable_get_xid(window),
+                           d->gc, 0, 0, x1, d->wh);
+            XFillRectangle(d->dpy, gdk_x11_drawable_get_xid(window),
+                           d->gc, x2, 0, d->ww - x2, d->wh);
+            XFillRectangle(d->dpy, gdk_x11_drawable_get_xid(window),
+                           d->gc, 0, 0, d->ww, y1);
+            XFillRectangle(d->dpy, gdk_x11_drawable_get_xid(window),
+                           d->gc, 0, y2, d->ww, d->wh - y2);
+        }
+        if (d->have_mitshm && d->shminfo) {
+            XShmPutImage(d->dpy, gdk_x11_drawable_get_xid(window),
+                         d->gc, d->ximage,
+                         d->area.x, d->area.y, x, y, w, h,
+                         true);
+        } else {
+            XPutImage(d->dpy, gdk_x11_drawable_get_xid(window),
+                      d->gc, d->ximage,
+                      d->area.x, d->area.y, x, y, w, h);
+        }
+    }
+}
+
+G_GNUC_INTERNAL
+gboolean spicex_is_scaled(SpiceDisplay *display)
+{
+    return FALSE; /* backend doesn't support scaling yet */
+}
diff --git a/src/spice-widget.c b/src/spice-widget.c
new file mode 100644
index 0000000..b9c4972
--- /dev/null
+++ b/src/spice-widget.c
@@ -0,0 +1,2642 @@
+/* -*- 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 <math.h>
+#include <glib.h>
+
+#if HAVE_X11_XKBLIB_H
+#include <X11/XKBlib.h>
+#include <gdk/gdkx.h>
+#endif
+#ifdef GDK_WINDOWING_X11
+#include <X11/Xlib.h>
+#include <gdk/gdkx.h>
+#endif
+#ifdef G_OS_WIN32
+#include <windows.h>
+#include <gdk/gdkwin32.h>
+#ifndef MAPVK_VK_TO_VSC /* may be undefined in older mingw-headers */
+#define MAPVK_VK_TO_VSC 0
+#endif
+#endif
+
+#include "spice-widget.h"
+#include "spice-widget-priv.h"
+#include "spice-gtk-session-priv.h"
+#include "vncdisplaykeymap.h"
+
+#include "glib-compat.h"
+#include "gtk-compat.h"
+
+/* Some compatibility defines to let us build on both Gtk2 and Gtk3 */
+
+/**
+ * SECTION:spice-widget
+ * @short_description: a GTK display widget
+ * @title: Spice Display
+ * @section_id:
+ * @stability: Stable
+ * @include: spice-widget.h
+ *
+ * A GTK widget that displays a SPICE server. It sends keyboard/mouse
+ * events and can also share clipboard...
+ *
+ * Arbitrary key events can be sent thanks to spice_display_send_keys().
+ *
+ * The widget will optionally grab the keyboard and the mouse when
+ * focused if the properties #SpiceDisplay:grab-keyboard and
+ * #SpiceDisplay:grab-mouse are #TRUE respectively.  It can be
+ * ungrabbed with spice_display_mouse_ungrab(), and by setting a key
+ * combination with spice_display_set_grab_keys().
+ *
+ * Finally, spice_display_get_pixbuf() will take a screenshot of the
+ * current display and return an #GdkPixbuf (that you can then easily
+ * save to disk).
+ */
+
+G_DEFINE_TYPE(SpiceDisplay, spice_display, GTK_TYPE_DRAWING_AREA)
+
+/* Properties */
+enum {
+    PROP_0,
+    PROP_SESSION,
+    PROP_CHANNEL_ID,
+    PROP_KEYBOARD_GRAB,
+    PROP_MOUSE_GRAB,
+    PROP_RESIZE_GUEST,
+    PROP_AUTO_CLIPBOARD,
+    PROP_SCALING,
+    PROP_ONLY_DOWNSCALE,
+    PROP_DISABLE_INPUTS,
+    PROP_ZOOM_LEVEL,
+    PROP_MONITOR_ID,
+    PROP_KEYPRESS_DELAY,
+    PROP_READY
+};
+
+/* Signals */
+enum {
+    SPICE_DISPLAY_MOUSE_GRAB,
+    SPICE_DISPLAY_KEYBOARD_GRAB,
+    SPICE_DISPLAY_GRAB_KEY_PRESSED,
+    SPICE_DISPLAY_LAST_SIGNAL,
+};
+
+static guint signals[SPICE_DISPLAY_LAST_SIGNAL];
+
+#ifdef G_OS_WIN32
+static HWND win32_window = NULL;
+#endif
+
+static void update_keyboard_grab(SpiceDisplay *display);
+static void try_keyboard_grab(SpiceDisplay *display);
+static void try_keyboard_ungrab(SpiceDisplay *display);
+static void update_mouse_grab(SpiceDisplay *display);
+static void try_mouse_grab(SpiceDisplay *display);
+static void try_mouse_ungrab(SpiceDisplay *display);
+static void recalc_geometry(GtkWidget *widget);
+static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data);
+static void channel_destroy(SpiceSession *s, SpiceChannel *channel, gpointer data);
+static void cursor_invalidate(SpiceDisplay *display);
+static void update_area(SpiceDisplay *display, gint x, gint y, gint width, gint height);
+static void release_keys(SpiceDisplay *display);
+
+/* ---------------------------------------------------------------- */
+
+static void spice_display_get_property(GObject    *object,
+                                       guint       prop_id,
+                                       GValue     *value,
+                                       GParamSpec *pspec)
+{
+    SpiceDisplay *display = SPICE_DISPLAY(object);
+    SpiceDisplayPrivate *d = display->priv;
+    gboolean boolean;
+
+    switch (prop_id) {
+    case PROP_SESSION:
+        g_value_set_object(value, d->session);
+        break;
+    case PROP_CHANNEL_ID:
+        g_value_set_int(value, d->channel_id);
+        break;
+    case PROP_MONITOR_ID:
+        g_value_set_int(value, d->monitor_id);
+        break;
+    case PROP_KEYBOARD_GRAB:
+        g_value_set_boolean(value, d->keyboard_grab_enable);
+        break;
+    case PROP_MOUSE_GRAB:
+        g_value_set_boolean(value, d->mouse_grab_enable);
+        break;
+    case PROP_RESIZE_GUEST:
+        g_value_set_boolean(value, d->resize_guest_enable);
+        break;
+    case PROP_AUTO_CLIPBOARD:
+        g_object_get(d->gtk_session, "auto-clipboard", &boolean, NULL);
+        g_value_set_boolean(value, boolean);
+        break;
+    case PROP_SCALING:
+        g_value_set_boolean(value, d->allow_scaling);
+        break;
+    case PROP_ONLY_DOWNSCALE:
+        g_value_set_boolean(value, d->only_downscale);
+        break;
+    case PROP_DISABLE_INPUTS:
+        g_value_set_boolean(value, d->disable_inputs);
+        break;
+    case PROP_ZOOM_LEVEL:
+        g_value_set_int(value, d->zoom_level);
+        break;
+    case PROP_READY:
+        g_value_set_boolean(value, d->ready);
+        break;
+    case PROP_KEYPRESS_DELAY:
+        g_value_set_uint(value, d->keypress_delay);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void scaling_updated(SpiceDisplay *display)
+{
+    SpiceDisplayPrivate *d = display->priv;
+    GdkWindow *window = gtk_widget_get_window(GTK_WIDGET(display));
+
+    recalc_geometry(GTK_WIDGET(display));
+    if (d->ximage && window) { /* if not yet shown */
+        gtk_widget_queue_draw(GTK_WIDGET(display));
+    }
+}
+
+static void update_size_request(SpiceDisplay *display)
+{
+    SpiceDisplayPrivate *d = display->priv;
+    gint reqwidth, reqheight;
+
+    if (d->resize_guest_enable) {
+        reqwidth = 640;
+        reqheight = 480;
+    } else {
+        reqwidth = d->area.width;
+        reqheight = d->area.height;
+    }
+
+    gtk_widget_set_size_request(GTK_WIDGET(display), reqwidth, reqheight);
+    recalc_geometry(GTK_WIDGET(display));
+}
+
+static void update_keyboard_focus(SpiceDisplay *display, gboolean state)
+{
+    SpiceDisplayPrivate *d = display->priv;
+
+    d->keyboard_have_focus = state;
+
+    /* keyboard grab gets inhibited by usb-device-manager when it is
+       in the process of redirecting a usb-device (as this may show a
+       policykit dialog). Making autoredir/automount setting changes while
+       this is happening is not a good idea! */
+    if (d->keyboard_grab_inhibit)
+        return;
+
+    spice_gtk_session_request_auto_usbredir(d->gtk_session, state);
+}
+
+static void update_ready(SpiceDisplay *display)
+{
+    SpiceDisplayPrivate *d = display->priv;
+    gboolean ready;
+
+    ready = d->mark != 0 && d->monitor_ready;
+
+    if (d->ready == ready)
+        return;
+
+    if (ready && gtk_widget_get_window(GTK_WIDGET(display)))
+        gtk_widget_queue_draw(GTK_WIDGET(display));
+
+    d->ready = ready;
+    g_object_notify(G_OBJECT(display), "ready");
+}
+
+static void set_monitor_ready(SpiceDisplay *self, gboolean ready)
+{
+    SpiceDisplayPrivate *d = self->priv;
+
+    d->monitor_ready = ready;
+    update_ready(self);
+}
+
+static gint get_display_id(SpiceDisplay *display)
+{
+    SpiceDisplayPrivate *d = display->priv;
+
+    /* supported monitor_id only with display channel #0 */
+    if (d->channel_id == 0 && d->monitor_id >= 0)
+        return d->monitor_id;
+
+    g_return_val_if_fail(d->monitor_id <= 0, -1);
+
+    return d->channel_id;
+}
+
+static void update_monitor_area(SpiceDisplay *display)
+{
+    SpiceDisplayPrivate *d = display->priv;
+    SpiceDisplayMonitorConfig *cfg, *c = NULL;
+    GArray *monitors = NULL;
+    int i;
+
+    SPICE_DEBUG("update monitor area %d:%d", d->channel_id, d->monitor_id);
+    if (d->monitor_id < 0)
+        goto whole;
+
+    g_object_get(d->display, "monitors", &monitors, NULL);
+    for (i = 0; monitors != NULL && i < monitors->len; i++) {
+        cfg = &g_array_index(monitors, SpiceDisplayMonitorConfig, i);
+        if (cfg->id == d->monitor_id) {
+           c = cfg;
+           break;
+        }
+    }
+    if (c == NULL) {
+        SPICE_DEBUG("update monitor: no monitor %d", d->monitor_id);
+        set_monitor_ready(display, false);
+        if (spice_channel_test_capability(d->display, SPICE_DISPLAY_CAP_MONITORS_CONFIG)) {
+            SPICE_DEBUG("waiting until MonitorsConfig is received");
+            g_clear_pointer(&monitors, g_array_unref);
+            return;
+        }
+        goto whole;
+    }
+
+    if (c->surface_id != 0) {
+        g_warning("FIXME: only support monitor config with primary surface 0, "
+                  "but given config surface %d", c->surface_id);
+        goto whole;
+    }
+
+    if (!d->resize_guest_enable)
+        spice_main_update_display(d->main, get_display_id(display),
+                                  c->x, c->y, c->width, c->height, FALSE);
+
+    update_area(display, c->x, c->y, c->width, c->height);
+    g_clear_pointer(&monitors, g_array_unref);
+    return;
+
+whole:
+    g_clear_pointer(&monitors, g_array_unref);
+    /* by display whole surface */
+    update_area(display, 0, 0, d->width, d->height);
+    set_monitor_ready(display, true);
+}
+
+static void spice_display_set_property(GObject      *object,
+                                       guint         prop_id,
+                                       const GValue *value,
+                                       GParamSpec   *pspec)
+{
+    SpiceDisplay *display = SPICE_DISPLAY(object);
+    SpiceDisplayPrivate *d = display->priv;
+
+    switch (prop_id) {
+    case PROP_SESSION:
+        g_warn_if_fail(d->session == NULL);
+        d->session = g_value_dup_object(value);
+        d->gtk_session = spice_gtk_session_get(d->session);
+        spice_g_signal_connect_object(d->gtk_session, "notify::pointer-grabbed",
+                                      G_CALLBACK(cursor_invalidate), object,
+                                      G_CONNECT_SWAPPED);
+        break;
+    case PROP_CHANNEL_ID:
+        d->channel_id = g_value_get_int(value);
+        break;
+    case PROP_MONITOR_ID:
+        d->monitor_id = g_value_get_int(value);
+        if (d->display) /* if constructed */
+            update_monitor_area(display);
+        break;
+    case PROP_KEYBOARD_GRAB:
+        d->keyboard_grab_enable = g_value_get_boolean(value);
+        update_keyboard_grab(display);
+        break;
+    case PROP_MOUSE_GRAB:
+        d->mouse_grab_enable = g_value_get_boolean(value);
+        update_mouse_grab(display);
+        break;
+    case PROP_RESIZE_GUEST:
+        d->resize_guest_enable = g_value_get_boolean(value);
+        update_size_request(display);
+        break;
+    case PROP_SCALING:
+        d->allow_scaling = g_value_get_boolean(value);
+        scaling_updated(display);
+        break;
+    case PROP_ONLY_DOWNSCALE:
+        d->only_downscale = g_value_get_boolean(value);
+        scaling_updated(display);
+        break;
+    case PROP_AUTO_CLIPBOARD:
+        g_object_set(d->gtk_session, "auto-clipboard",
+                     g_value_get_boolean(value), NULL);
+        break;
+    case PROP_DISABLE_INPUTS:
+        d->disable_inputs = g_value_get_boolean(value);
+        gtk_widget_set_can_focus(GTK_WIDGET(display), !d->disable_inputs);
+        update_keyboard_grab(display);
+        update_mouse_grab(display);
+        break;
+    case PROP_ZOOM_LEVEL:
+        d->zoom_level = g_value_get_int(value);
+        scaling_updated(display);
+        break;
+    case PROP_KEYPRESS_DELAY:
+        d->keypress_delay = g_value_get_uint(value);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void gtk_session_property_changed(GObject    *gobject,
+                                         GParamSpec *pspec,
+                                         gpointer    user_data)
+{
+    SpiceDisplay *display = user_data;
+
+    g_object_notify(G_OBJECT(display), g_param_spec_get_name(pspec));
+}
+
+static void session_inhibit_keyboard_grab_changed(GObject    *gobject,
+                                                  GParamSpec *pspec,
+                                                  gpointer    user_data)
+{
+    SpiceDisplay *display = user_data;
+    SpiceDisplayPrivate *d = display->priv;
+
+    g_object_get(d->session, "inhibit-keyboard-grab",
+                 &d->keyboard_grab_inhibit, NULL);
+    update_keyboard_grab(display);
+    update_mouse_grab(display);
+}
+
+static void spice_display_dispose(GObject *obj)
+{
+    SpiceDisplay *display = SPICE_DISPLAY(obj);
+    SpiceDisplayPrivate *d = display->priv;
+
+    SPICE_DEBUG("spice display dispose");
+
+    spicex_image_destroy(display);
+    g_clear_object(&d->session);
+    d->gtk_session = NULL;
+
+    if (d->key_delayed_id) {
+        g_source_remove(d->key_delayed_id);
+        d->key_delayed_id = 0;
+    }
+
+    G_OBJECT_CLASS(spice_display_parent_class)->dispose(obj);
+}
+
+static void spice_display_finalize(GObject *obj)
+{
+    SpiceDisplay *display = SPICE_DISPLAY(obj);
+    SpiceDisplayPrivate *d = display->priv;
+
+    SPICE_DEBUG("Finalize spice display");
+
+    if (d->grabseq) {
+        spice_grab_sequence_free(d->grabseq);
+        d->grabseq = NULL;
+    }
+    g_free(d->activeseq);
+    d->activeseq = NULL;
+
+    if (d->show_cursor) {
+        gdk_cursor_unref(d->show_cursor);
+        d->show_cursor = NULL;
+    }
+
+    if (d->mouse_cursor) {
+        gdk_cursor_unref(d->mouse_cursor);
+        d->mouse_cursor = NULL;
+    }
+
+    if (d->mouse_pixbuf) {
+        g_object_unref(d->mouse_pixbuf);
+        d->mouse_pixbuf = NULL;
+    }
+
+    G_OBJECT_CLASS(spice_display_parent_class)->finalize(obj);
+}
+
+static GdkCursor* get_blank_cursor(void)
+{
+    if (g_getenv("SPICE_DEBUG_CURSOR"))
+        return gdk_cursor_new(GDK_DOT);
+
+    return gdk_cursor_new(GDK_BLANK_CURSOR);
+}
+
+static gboolean grab_broken(SpiceDisplay *self, GdkEventGrabBroken *event,
+                            gpointer user_data G_GNUC_UNUSED)
+{
+    SPICE_DEBUG("%s (implicit: %d, keyboard: %d)", __FUNCTION__,
+                event->implicit, event->keyboard);
+
+    if (event->keyboard) {
+        try_keyboard_ungrab(self);
+        release_keys(self);
+    }
+
+    /* always release mouse when grab broken, this could be more
+       generally placed in keyboard_ungrab(), but one might worry of
+       breaking someone else code. */
+    try_mouse_ungrab(self);
+
+    return false;
+}
+
+static void drag_data_received_callback(SpiceDisplay *self,
+                                        GdkDragContext *drag_context,
+                                        gint x,
+                                        gint y,
+                                        GtkSelectionData *data,
+                                        guint info,
+                                        guint time,
+                                        gpointer *user_data)
+{
+    const guchar *buf;
+    gchar **file_urls;
+    int n_files;
+    SpiceDisplayPrivate *d = self->priv;
+    int i = 0;
+    GFile **files;
+
+    /* We get a buf like:
+     * file:///root/a.txt\r\nfile:///root/b.txt\r\n
+     */
+    SPICE_DEBUG("%s: drag a file", __FUNCTION__);
+    buf = gtk_selection_data_get_data(data);
+    g_return_if_fail(buf != NULL);
+
+    file_urls = g_uri_list_extract_uris((const gchar*)buf);
+    n_files = g_strv_length(file_urls);
+    files = g_new0(GFile*, n_files + 1);
+    for (i = 0; i < n_files; i++) {
+        files[i] = g_file_new_for_uri(file_urls[i]);
+    }
+    g_strfreev(file_urls);
+
+    spice_main_file_copy_async(d->main, files, 0, NULL, NULL,
+                               NULL, NULL, NULL);
+    for (i = 0; i < n_files; i++) {
+        g_object_unref(files[i]);
+    }
+    g_free(files);
+
+    gtk_drag_finish(drag_context, TRUE, FALSE, time);
+}
+
+static void grab_notify(SpiceDisplay *display, gboolean was_grabbed)
+{
+    SPICE_DEBUG("grab notify %d", was_grabbed);
+
+    if (was_grabbed == FALSE)
+        release_keys(display);
+}
+
+static void spice_display_init(SpiceDisplay *display)
+{
+    GtkWidget *widget = GTK_WIDGET(display);
+    SpiceDisplayPrivate *d;
+    GtkTargetEntry targets = { "text/uri-list", 0, 0 };
+
+    d = display->priv = SPICE_DISPLAY_GET_PRIVATE(display);
+
+    g_signal_connect(display, "grab-broken-event", G_CALLBACK(grab_broken), NULL);
+    g_signal_connect(display, "grab-notify", G_CALLBACK(grab_notify), NULL);
+
+    gtk_drag_dest_set(widget, GTK_DEST_DEFAULT_ALL, &targets, 1, GDK_ACTION_COPY);
+    g_signal_connect(display, "drag-data-received",
+                     G_CALLBACK(drag_data_received_callback), NULL);
+
+    gtk_widget_add_events(widget,
+                          GDK_STRUCTURE_MASK |
+                          GDK_POINTER_MOTION_MASK |
+                          GDK_BUTTON_PRESS_MASK |
+                          GDK_BUTTON_RELEASE_MASK |
+                          GDK_BUTTON_MOTION_MASK |
+                          GDK_ENTER_NOTIFY_MASK |
+                          GDK_LEAVE_NOTIFY_MASK |
+                          GDK_KEY_PRESS_MASK |
+                          GDK_SCROLL_MASK);
+#ifdef WITH_X11
+    gtk_widget_set_double_buffered(widget, false);
+#else
+    gtk_widget_set_double_buffered(widget, true);
+#endif
+    gtk_widget_set_can_focus(widget, true);
+    gtk_widget_set_has_window(widget, true);
+    d->grabseq = spice_grab_sequence_new_from_string("Control_L+Alt_L");
+    d->activeseq = g_new0(gboolean, d->grabseq->nkeysyms);
+
+    d->mouse_cursor = get_blank_cursor();
+    d->have_mitshm = true;
+}
+
+static GObject *
+spice_display_constructor(GType                  gtype,
+                          guint                  n_properties,
+                          GObjectConstructParam *properties)
+{
+    GObject *obj;
+    SpiceDisplay *display;
+    SpiceDisplayPrivate *d;
+    GList *list;
+    GList *it;
+
+    {
+        /* Always chain up to the parent constructor */
+        GObjectClass *parent_class;
+        parent_class = G_OBJECT_CLASS(spice_display_parent_class);
+        obj = parent_class->constructor(gtype, n_properties, properties);
+    }
+
+    display = SPICE_DISPLAY(obj);
+    d = display->priv;
+
+    if (!d->session)
+        g_error("SpiceDisplay constructed without a session");
+
+    spice_g_signal_connect_object(d->session, "channel-new",
+                                  G_CALLBACK(channel_new), display, 0);
+    spice_g_signal_connect_object(d->session, "channel-destroy",
+                                  G_CALLBACK(channel_destroy), display, 0);
+    list = spice_session_get_channels(d->session);
+    for (it = g_list_first(list); it != NULL; it = g_list_next(it)) {
+        if (SPICE_IS_MAIN_CHANNEL(it->data)) {
+            channel_new(d->session, it->data, (gpointer*)display);
+            break;
+        }
+    }
+    for (it = g_list_first(list); it != NULL; it = g_list_next(it)) {
+        if (!SPICE_IS_MAIN_CHANNEL(it->data))
+            channel_new(d->session, it->data, (gpointer*)display);
+    }
+    g_list_free(list);
+
+    spice_g_signal_connect_object(d->gtk_session, "notify::auto-clipboard",
+                                  G_CALLBACK(gtk_session_property_changed), display, 0);
+
+    spice_g_signal_connect_object(d->session, "notify::inhibit-keyboard-grab",
+                                  G_CALLBACK(session_inhibit_keyboard_grab_changed),
+                                  display, 0);
+
+    return obj;
+}
+
+/**
+ * spice_display_set_grab_keys:
+ * @display: the display widget
+ * @seq: (transfer none): key sequence
+ *
+ * Set the key combination to grab/ungrab the keyboard. The default is
+ * "Control L + Alt L".
+ **/
+void spice_display_set_grab_keys(SpiceDisplay *display, SpiceGrabSequence *seq)
+{
+    SpiceDisplayPrivate *d;
+
+    g_return_if_fail(SPICE_IS_DISPLAY(display));
+
+    d = display->priv;
+    g_return_if_fail(d != NULL);
+
+    if (d->grabseq) {
+        spice_grab_sequence_free(d->grabseq);
+    }
+    if (seq)
+        d->grabseq = spice_grab_sequence_copy(seq);
+    else
+        d->grabseq = spice_grab_sequence_new_from_string("Control_L+Alt_L");
+    g_free(d->activeseq);
+    d->activeseq = g_new0(gboolean, d->grabseq->nkeysyms);
+}
+
+#ifdef G_OS_WIN32
+static LRESULT CALLBACK keyboard_hook_cb(int code, WPARAM wparam, LPARAM lparam)
+{
+    if  (win32_window && code == HC_ACTION && wparam != WM_KEYUP) {
+        KBDLLHOOKSTRUCT *hooked = (KBDLLHOOKSTRUCT*)lparam;
+        DWORD dwmsg = (hooked->flags << 24) | (hooked->scanCode << 16) | 1;
+
+        if (hooked->vkCode == VK_NUMLOCK || hooked->vkCode == VK_RSHIFT) {
+            dwmsg &= ~(1 << 24);
+            SendMessage(win32_window, wparam, hooked->vkCode, dwmsg);
+        }
+        switch (hooked->vkCode) {
+        case VK_CAPITAL:
+        case VK_SCROLL:
+        case VK_NUMLOCK:
+        case VK_LSHIFT:
+        case VK_RSHIFT:
+        case VK_RCONTROL:
+        case VK_LMENU:
+        case VK_RMENU:
+            break;
+        case VK_LCONTROL:
+            /* When pressing AltGr, an extra VK_LCONTROL with a special
+             * scancode with bit 9 set is sent. Let's ignore the extra
+             * VK_LCONTROL, as that will make AltGr misbehave. */
+            if (hooked->scanCode & 0x200)
+                return 1;
+            break;
+        default:
+            SendMessage(win32_window, wparam, hooked->vkCode, dwmsg);
+            return 1;
+        }
+    }
+    return CallNextHookEx(NULL, code, wparam, lparam);
+}
+#endif
+
+/**
+ * spice_display_get_grab_keys:
+ * @display: the display widget
+ *
+ * Returns: (transfer none): the current grab key combination.
+ **/
+SpiceGrabSequence *spice_display_get_grab_keys(SpiceDisplay *display)
+{
+    SpiceDisplayPrivate *d;
+
+    g_return_val_if_fail(SPICE_IS_DISPLAY(display), NULL);
+
+    d = display->priv;
+    g_return_val_if_fail(d != NULL, NULL);
+
+    return d->grabseq;
+}
+
+static void try_keyboard_grab(SpiceDisplay *display)
+{
+    GtkWidget *widget = GTK_WIDGET(display);
+    SpiceDisplayPrivate *d = display->priv;
+    GdkGrabStatus status;
+
+    if (g_getenv("SPICE_NOGRAB"))
+        return;
+    if (d->disable_inputs)
+        return;
+
+    if (d->keyboard_grab_inhibit)
+        return;
+    if (!d->keyboard_grab_enable)
+        return;
+    if (d->keyboard_grab_active)
+        return;
+    if (!d->keyboard_have_focus)
+        return;
+    if (!d->mouse_have_pointer)
+        return;
+    if (d->keyboard_grab_released)
+        return;
+
+    g_return_if_fail(gtk_widget_is_focus(widget));
+
+    SPICE_DEBUG("grab keyboard");
+    gtk_widget_grab_focus(widget);
+
+#ifdef G_OS_WIN32
+    if (d->keyboard_hook == NULL)
+        d->keyboard_hook = SetWindowsHookEx(WH_KEYBOARD_LL, keyboard_hook_cb,
+                                            GetModuleHandle(NULL), 0);
+    g_warn_if_fail(d->keyboard_hook != NULL);
+#endif
+    status = gdk_keyboard_grab(gtk_widget_get_window(widget), FALSE,
+                               GDK_CURRENT_TIME);
+    if (status != GDK_GRAB_SUCCESS) {
+        g_warning("keyboard grab failed %d", status);
+        d->keyboard_grab_active = false;
+    } else {
+        d->keyboard_grab_active = true;
+        g_signal_emit(widget, signals[SPICE_DISPLAY_KEYBOARD_GRAB], 0, true);
+    }
+}
+
+static void try_keyboard_ungrab(SpiceDisplay *display)
+{
+    SpiceDisplayPrivate *d = display->priv;
+    GtkWidget *widget = GTK_WIDGET(display);
+
+    if (!d->keyboard_grab_active)
+        return;
+
+    SPICE_DEBUG("ungrab keyboard");
+    gdk_keyboard_ungrab(GDK_CURRENT_TIME);
+#ifdef G_OS_WIN32
+    if (d->keyboard_hook != NULL) {
+        UnhookWindowsHookEx(d->keyboard_hook);
+        d->keyboard_hook = NULL;
+    }
+#endif
+    d->keyboard_grab_active = false;
+    g_signal_emit(widget, signals[SPICE_DISPLAY_KEYBOARD_GRAB], 0, false);
+}
+
+static void update_keyboard_grab(SpiceDisplay *display)
+{
+    SpiceDisplayPrivate *d = display->priv;
+
+    if (d->keyboard_grab_enable &&
+        !d->keyboard_grab_inhibit &&
+        !d->disable_inputs)
+        try_keyboard_grab(display);
+    else
+        try_keyboard_ungrab(display);
+}
+
+static void set_mouse_accel(SpiceDisplay *display, gboolean enabled)
+{
+    SpiceDisplayPrivate *d = display->priv;
+
+#if defined GDK_WINDOWING_X11
+    GdkWindow *w = GDK_WINDOW(gtk_widget_get_window(GTK_WIDGET(display)));
+
+    if (!GDK_IS_X11_DISPLAY(gdk_window_get_display(w))) {
+        SPICE_DEBUG("FIXME: gtk backend is not X11");
+        return;
+    }
+
+    Display *x_display = GDK_WINDOW_XDISPLAY(w);
+    if (enabled) {
+        /* restore mouse acceleration */
+        XChangePointerControl(x_display, True, True,
+                              d->x11_accel_numerator, d->x11_accel_denominator, d->x11_threshold);
+    } else {
+        XGetPointerControl(x_display,
+                           &d->x11_accel_numerator, &d->x11_accel_denominator, &d->x11_threshold);
+        /* set mouse acceleration to default */
+        XChangePointerControl(x_display, True, True, -1, -1, -1);
+        SPICE_DEBUG("disabled X11 mouse motion %d %d %d",
+                    d->x11_accel_numerator, d->x11_accel_denominator, d->x11_threshold);
+    }
+#elif defined GDK_WINDOWING_WIN32
+    if (enabled) {
+        g_return_if_fail(SystemParametersInfo(SPI_SETMOUSE, 0, &d->win_mouse, 0));
+        g_return_if_fail(SystemParametersInfo(SPI_SETMOUSESPEED, 0, (PVOID)(INT_PTR)d->win_mouse_speed, 0));
+    } else {
+        int accel[3] = { 0, 0, 0 }; // disabled
+        g_return_if_fail(SystemParametersInfo(SPI_GETMOUSE, 0, &d->win_mouse, 0));
+        g_return_if_fail(SystemParametersInfo(SPI_GETMOUSESPEED, 0, &d->win_mouse_speed, 0));
+        g_return_if_fail(SystemParametersInfo(SPI_SETMOUSE, 0, &accel, SPIF_SENDCHANGE));
+        g_return_if_fail(SystemParametersInfo(SPI_SETMOUSESPEED, 0, (PVOID)10, SPIF_SENDCHANGE)); // default
+    }
+#else
+    g_warning("Mouse acceleration code missing for your platform");
+#endif
+}
+
+#ifdef G_OS_WIN32
+static gboolean win32_clip_cursor(void)
+{
+    RECT window, workarea, rect;
+    HMONITOR monitor;
+    MONITORINFO mi = { 0, };
+
+    g_return_val_if_fail(win32_window != NULL, FALSE);
+
+    if (!GetWindowRect(win32_window, &window))
+        goto error;
+
+    monitor = MonitorFromRect(&window, MONITOR_DEFAULTTONEAREST);
+    g_return_val_if_fail(monitor != NULL, false);
+
+    mi.cbSize = sizeof(mi);
+    if (!GetMonitorInfo(monitor, &mi))
+        goto error;
+    workarea = mi.rcWork;
+
+    if (!IntersectRect(&rect, &window, &workarea)) {
+        g_critical("error clipping cursor");
+        return false;
+    }
+
+    SPICE_DEBUG("clip rect %ld %ld %ld %ld\n",
+                rect.left, rect.right, rect.top, rect.bottom);
+
+    if (!ClipCursor(&rect))
+        goto error;
+
+    return true;
+
+error:
+    {
+        DWORD errval  = GetLastError();
+        gchar *errstr = g_win32_error_message(errval);
+        g_warning("failed to clip cursor (%ld) %s", errval, errstr);
+    }
+
+    return false;
+}
+#endif
+
+static GdkGrabStatus do_pointer_grab(SpiceDisplay *display)
+{
+    SpiceDisplayPrivate *d = display->priv;
+    GdkWindow *window = GDK_WINDOW(gtk_widget_get_window(GTK_WIDGET(display)));
+    GdkGrabStatus status = GDK_GRAB_BROKEN;
+    GdkCursor *blank = get_blank_cursor();
+
+    if (!gtk_widget_get_realized(GTK_WIDGET(display)))
+        goto end;
+
+#ifdef G_OS_WIN32
+    if (!win32_clip_cursor())
+        goto end;
+#endif
+
+    try_keyboard_grab(display);
+    /*
+     * from gtk-vnc:
+     * For relative mouse to work correctly when grabbed we need to
+     * allow the pointer to move anywhere on the local desktop, so
+     * use NULL for the 'confine_to' argument. Furthermore we need
+     * the coords to be reported to our VNC window, regardless of
+     * what window the pointer is actally over, so use 'FALSE' for
+     * 'owner_events' parameter
+     */
+    status = gdk_pointer_grab(window, FALSE,
+                     GDK_POINTER_MOTION_MASK |
+                     GDK_BUTTON_PRESS_MASK |
+                     GDK_BUTTON_RELEASE_MASK |
+                     GDK_BUTTON_MOTION_MASK |
+                     GDK_SCROLL_MASK,
+                     NULL,
+                     blank,
+                     GDK_CURRENT_TIME);
+    if (status != GDK_GRAB_SUCCESS) {
+        d->mouse_grab_active = false;
+        g_warning("pointer grab failed %d", status);
+    } else {
+        d->mouse_grab_active = true;
+        g_signal_emit(display, signals[SPICE_DISPLAY_MOUSE_GRAB], 0, true);
+        spice_gtk_session_set_pointer_grabbed(d->gtk_session, true);
+        set_mouse_accel(display, FALSE);
+    }
+
+end:
+    gdk_cursor_unref(blank);
+    return status;
+}
+
+static void update_mouse_pointer(SpiceDisplay *display)
+{
+    SpiceDisplayPrivate *d = display->priv;
+    GdkWindow *window = GDK_WINDOW(gtk_widget_get_window(GTK_WIDGET(display)));
+
+    if (!window)
+        return;
+
+    switch (d->mouse_mode) {
+    case SPICE_MOUSE_MODE_CLIENT:
+        if (gdk_window_get_cursor(window) != d->mouse_cursor)
+            gdk_window_set_cursor(window, d->mouse_cursor);
+        break;
+    case SPICE_MOUSE_MODE_SERVER:
+        if (gdk_window_get_cursor(window) != NULL)
+            gdk_window_set_cursor(window, NULL);
+        break;
+    default:
+        g_warn_if_reached();
+        break;
+    }
+}
+
+static void try_mouse_grab(SpiceDisplay *display)
+{
+    SpiceDisplayPrivate *d = display->priv;
+
+    if (g_getenv("SPICE_NOGRAB"))
+        return;
+    if (d->disable_inputs)
+        return;
+
+    if (!d->mouse_have_pointer)
+        return;
+    if (!d->keyboard_have_focus)
+        return;
+
+    if (!d->mouse_grab_enable)
+        return;
+    if (d->mouse_mode != SPICE_MOUSE_MODE_SERVER)
+        return;
+    if (d->mouse_grab_active)
+        return;
+
+    if (do_pointer_grab(display) != GDK_GRAB_SUCCESS)
+        return;
+
+    d->mouse_last_x = -1;
+    d->mouse_last_y = -1;
+}
+
+static void mouse_wrap(SpiceDisplay *display, GdkEventMotion *motion)
+{
+    SpiceDisplayPrivate *d = display->priv;
+    gint xr, yr;
+
+#ifdef G_OS_WIN32
+    RECT clip;
+    g_return_if_fail(GetClipCursor(&clip));
+    xr = clip.left + (clip.right - clip.left) / 2;
+    yr = clip.top + (clip.bottom - clip.top) / 2;
+    /* the clip rectangle has no offset, so we can't use gdk_wrap_pointer */
+    SetCursorPos(xr, yr);
+    d->mouse_last_x = -1;
+    d->mouse_last_y = -1;
+#else
+    GdkScreen *screen = gtk_widget_get_screen(GTK_WIDGET(display));
+    xr = gdk_screen_get_width(screen) / 2;
+    yr = gdk_screen_get_height(screen) / 2;
+
+    if (xr != (gint)motion->x_root || yr != (gint)motion->y_root) {
+        /* FIXME: we try our best to ignore that next pointer move event.. */
+        gdk_display_sync(gdk_screen_get_display(screen));
+
+        gdk_display_warp_pointer(gtk_widget_get_display(GTK_WIDGET(display)),
+                                 screen, xr, yr);
+        d->mouse_last_x = -1;
+        d->mouse_last_y = -1;
+    }
+#endif
+
+}
+
+static void try_mouse_ungrab(SpiceDisplay *display)
+{
+    SpiceDisplayPrivate *d = display->priv;
+    double s;
+    int x, y;
+
+    if (!d->mouse_grab_active)
+        return;
+
+    gdk_pointer_ungrab(GDK_CURRENT_TIME);
+    gtk_grab_remove(GTK_WIDGET(display));
+#ifdef G_OS_WIN32
+    ClipCursor(NULL);
+#endif
+    set_mouse_accel(display, TRUE);
+
+    d->mouse_grab_active = false;
+
+    spice_display_get_scaling(display, &s, &x, &y, NULL, NULL);
+
+    gdk_window_get_root_coords(gtk_widget_get_window(GTK_WIDGET(display)),
+                               x + d->mouse_guest_x * s,
+                               y + d->mouse_guest_y * s,
+                               &x, &y);
+
+    gdk_display_warp_pointer(gtk_widget_get_display(GTK_WIDGET(display)),
+                             gtk_widget_get_screen(GTK_WIDGET(display)),
+                             x, y);
+
+    g_signal_emit(display, signals[SPICE_DISPLAY_MOUSE_GRAB], 0, false);
+    spice_gtk_session_set_pointer_grabbed(d->gtk_session, false);
+}
+
+static void update_mouse_grab(SpiceDisplay *display)
+{
+    SpiceDisplayPrivate *d = display->priv;
+
+    if (d->mouse_grab_enable &&
+        !d->keyboard_grab_inhibit &&
+        !d->disable_inputs)
+        try_mouse_grab(display);
+    else
+        try_mouse_ungrab(display);
+}
+
+static void recalc_geometry(GtkWidget *widget)
+{
+    SpiceDisplay *display = SPICE_DISPLAY(widget);
+    SpiceDisplayPrivate *d = display->priv;
+    gdouble zoom = 1.0;
+
+    if (spicex_is_scaled(display))
+        zoom = (gdouble)d->zoom_level / 100;
+
+    SPICE_DEBUG("recalc geom monitor: %d:%d, guest +%d+%d:%dx%d, window %dx%d, zoom %g",
+                d->channel_id, d->monitor_id, d->area.x, d->area.y, d->area.width, d->area.height,
+                d->ww, d->wh, zoom);
+
+    if (d->resize_guest_enable)
+        spice_main_set_display(d->main, get_display_id(display),
+                               d->area.x, d->area.y, d->ww / zoom, d->wh / zoom);
+}
+
+/* ---------------------------------------------------------------- */
+
+#define CONVERT_0565_TO_0888(s)                                         \
+    (((((s) << 3) & 0xf8) | (((s) >> 2) & 0x7)) |                       \
+     ((((s) << 5) & 0xfc00) | (((s) >> 1) & 0x300)) |                   \
+     ((((s) << 8) & 0xf80000) | (((s) << 3) & 0x70000)))
+
+#define CONVERT_0565_TO_8888(s) (CONVERT_0565_TO_0888(s) | 0xff000000)
+
+#define CONVERT_0555_TO_0888(s)                                         \
+    (((((s) & 0x001f) << 3) | (((s) & 0x001c) >> 2)) |                  \
+     ((((s) & 0x03e0) << 6) | (((s) & 0x0380) << 1)) |                  \
+     ((((s) & 0x7c00) << 9) | ((((s) & 0x7000)) << 4)))
+
+#define CONVERT_0555_TO_8888(s) (CONVERT_0555_TO_0888(s) | 0xff000000)
+
+static gboolean do_color_convert(SpiceDisplay *display, GdkRectangle *r)
+{
+    SpiceDisplayPrivate *d = display->priv;
+    guint32 *dest = d->data;
+    guint16 *src = d->data_origin;
+    gint x, y;
+
+    g_return_val_if_fail(r != NULL, false);
+    g_return_val_if_fail(d->format == SPICE_SURFACE_FMT_16_555 ||
+                         d->format == SPICE_SURFACE_FMT_16_565, false);
+
+    src += (d->stride / 2) * r->y + r->x;
+    dest += d->area.width * (r->y - d->area.y) + (r->x - d->area.x);
+
+    if (d->format == SPICE_SURFACE_FMT_16_555) {
+        for (y = 0; y < r->height; y++) {
+            for (x = 0; x < r->width; x++) {
+                dest[x] = CONVERT_0555_TO_0888(src[x]);
+            }
+
+            dest += d->area.width;
+            src += d->stride / 2;
+        }
+    } else if (d->format == SPICE_SURFACE_FMT_16_565) {
+        for (y = 0; y < r->height; y++) {
+            for (x = 0; x < r->width; x++) {
+                dest[x] = CONVERT_0565_TO_0888(src[x]);
+            }
+
+            dest += d->area.width;
+            src += d->stride / 2;
+        }
+    }
+
+    return true;
+}
+
+
+#if GTK_CHECK_VERSION (2, 91, 0)
+static gboolean draw_event(GtkWidget *widget, cairo_t *cr)
+{
+    SpiceDisplay *display = SPICE_DISPLAY(widget);
+    SpiceDisplayPrivate *d = display->priv;
+    g_return_val_if_fail(d != NULL, false);
+
+    if (d->mark == 0 || d->data == NULL ||
+        d->area.width == 0 || d->area.height == 0)
+        return false;
+    g_return_val_if_fail(d->ximage != NULL, false);
+
+    spicex_draw_event(display, cr);
+    update_mouse_pointer(display);
+
+    return true;
+}
+#else
+static gboolean expose_event(GtkWidget *widget, GdkEventExpose *expose)
+{
+    SpiceDisplay *display = SPICE_DISPLAY(widget);
+    SpiceDisplayPrivate *d = display->priv;
+    g_return_val_if_fail(d != NULL, false);
+
+    if (d->mark == 0 || d->data == NULL ||
+        d->area.width == 0 || d->area.height == 0)
+        return false;
+    g_return_val_if_fail(d->ximage != NULL, false);
+
+    spicex_expose_event(display, expose);
+    update_mouse_pointer(display);
+
+    return true;
+}
+#endif
+
+/* ---------------------------------------------------------------- */
+typedef enum {
+    SEND_KEY_PRESS,
+    SEND_KEY_RELEASE,
+} SendKeyType;
+
+static void key_press_and_release(SpiceDisplay *display)
+{
+    SpiceDisplayPrivate *d = display->priv;
+
+    if (d->key_delayed_scancode == 0)
+        return;
+
+    spice_inputs_key_press_and_release(d->inputs, d->key_delayed_scancode);
+    d->key_delayed_scancode = 0;
+
+    if (d->key_delayed_id) {
+        g_source_remove(d->key_delayed_id);
+        d->key_delayed_id = 0;
+    }
+}
+
+static gboolean key_press_delayed(gpointer data)
+{
+    SpiceDisplay *display = data;
+    SpiceDisplayPrivate *d = display->priv;
+
+    if (d->key_delayed_scancode == 0)
+        return FALSE;
+
+    spice_inputs_key_press(d->inputs, d->key_delayed_scancode);
+    d->key_delayed_scancode = 0;
+
+    if (d->key_delayed_id) {
+        g_source_remove(d->key_delayed_id);
+        d->key_delayed_id = 0;
+    }
+
+    return FALSE;
+}
+
+static void send_key(SpiceDisplay *display, int scancode, SendKeyType type, gboolean press_delayed)
+{
+    SpiceDisplayPrivate *d = display->priv;
+    uint32_t i, b, m;
+
+    g_return_if_fail(scancode != 0);
+
+    if (!d->inputs)
+        return;
+
+    if (d->disable_inputs)
+        return;
+
+    i = scancode / 32;
+    b = scancode % 32;
+    m = (1 << b);
+    g_return_if_fail(i < SPICE_N_ELEMENTS(d->key_state));
+
+    switch (type) {
+    case SEND_KEY_PRESS:
+        /* ensure delayed key is pressed before any new input event */
+        key_press_delayed(display);
+
+        if (press_delayed &&
+            d->keypress_delay != 0 &&
+            !(d->key_state[i] & m)) {
+            g_warn_if_fail(d->key_delayed_id == 0);
+            d->key_delayed_id = g_timeout_add(d->keypress_delay, key_press_delayed, display);
+            d->key_delayed_scancode = scancode;
+        } else
+            spice_inputs_key_press(d->inputs, scancode);
+
+        d->key_state[i] |= m;
+        break;
+
+    case SEND_KEY_RELEASE:
+        if (!(d->key_state[i] & m))
+            break;
+
+        if (d->key_delayed_scancode == scancode)
+            key_press_and_release(display);
+        else {
+            /* ensure delayed key is pressed before other key are released */
+            key_press_delayed(display);
+            spice_inputs_key_release(d->inputs, scancode);
+        }
+
+        d->key_state[i] &= ~m;
+        break;
+
+    default:
+        g_warn_if_reached();
+    }
+}
+
+static void release_keys(SpiceDisplay *display)
+{
+    SpiceDisplayPrivate *d = display->priv;
+    uint32_t i, b;
+
+    SPICE_DEBUG("%s", __FUNCTION__);
+    for (i = 0; i < SPICE_N_ELEMENTS(d->key_state); i++) {
+        if (!d->key_state[i]) {
+            continue;
+        }
+        for (b = 0; b < 32; b++) {
+            unsigned int scancode = i * 32 + b;
+            if (scancode != 0) {
+                send_key(display, scancode, SEND_KEY_RELEASE, FALSE);
+            }
+        }
+    }
+}
+
+static gboolean check_for_grab_key(SpiceDisplay *display, int type, int keyval,
+                                   int check_type, int reset_type)
+{
+    SpiceDisplayPrivate *d = display->priv;
+    int i;
+
+    if (!d->grabseq->nkeysyms)
+        return FALSE;
+
+    if (type == check_type) {
+        /* Record the new key */
+        for (i = 0 ; i < d->grabseq->nkeysyms ; i++)
+            if (d->grabseq->keysyms[i] == keyval)
+                d->activeseq[i] = TRUE;
+
+        /* Return if any key is missing */
+        for (i = 0 ; i < d->grabseq->nkeysyms ; i++)
+            if (d->activeseq[i] == FALSE)
+                return FALSE;
+
+        /* resets the whole grab sequence on success */
+        memset(d->activeseq, 0, sizeof(gboolean) * d->grabseq->nkeysyms);
+        return TRUE;
+    } else if (type == reset_type) {
+        /* reset key event type resets the whole grab sequence */
+        memset(d->activeseq, 0, sizeof(gboolean) * d->grabseq->nkeysyms);
+        d->seq_pressed = FALSE;
+        return FALSE;
+    } else
+        g_warn_if_reached();
+
+    return FALSE;
+}
+
+static gboolean check_for_grab_key_pressed(SpiceDisplay *display, int type, int keyval)
+{
+    return check_for_grab_key(display, type, keyval, GDK_KEY_PRESS, GDK_KEY_RELEASE);
+}
+
+static gboolean check_for_grab_key_released(SpiceDisplay *display, int type, int keyval)
+{
+    return check_for_grab_key(display, type, keyval, GDK_KEY_RELEASE, GDK_KEY_PRESS);
+}
+
+static void update_display(SpiceDisplay *display)
+{
+#ifdef G_OS_WIN32
+    win32_window = display ? GDK_WINDOW_HWND(gtk_widget_get_window(GTK_WIDGET(display))) : NULL;
+#endif
+}
+
+static gboolean key_event(GtkWidget *widget, GdkEventKey *key)
+{
+    SpiceDisplay *display = SPICE_DISPLAY(widget);
+    SpiceDisplayPrivate *d = display->priv;
+    int scancode;
+
+#ifdef G_OS_WIN32
+    /* on windows, we ought to ignore the reserved key event? */
+    if (key->hardware_keycode == 0xff)
+        return false;
+
+    if (!d->keyboard_grab_active) {
+        if (key->hardware_keycode == VK_LWIN ||
+            key->hardware_keycode == VK_RWIN ||
+            key->hardware_keycode == VK_APPS)
+            return false;
+    }
+
+#endif
+    SPICE_DEBUG("%s %s: keycode: %d  state: %d  group %d modifier %d",
+            __FUNCTION__, key->type == GDK_KEY_PRESS ? "press" : "release",
+            key->hardware_keycode, key->state, key->group, key->is_modifier);
+
+    if (!d->seq_pressed && check_for_grab_key_pressed(display, key->type, key->keyval)) {
+        g_signal_emit(widget, signals[SPICE_DISPLAY_GRAB_KEY_PRESSED], 0);
+
+        if (d->mouse_mode == SPICE_MOUSE_MODE_SERVER) {
+            if (d->mouse_grab_active)
+                try_mouse_ungrab(display);
+            else
+                try_mouse_grab(display);
+        }
+        d->seq_pressed = TRUE;
+    } else if (d->seq_pressed && check_for_grab_key_released(display, key->type, key->keyval)) {
+        release_keys(display);
+        if (!d->keyboard_grab_released) {
+            d->keyboard_grab_released = TRUE;
+            try_keyboard_ungrab(display);
+        } else {
+            d->keyboard_grab_released = FALSE;
+            try_keyboard_grab(display);
+        }
+        d->seq_pressed = FALSE;
+    }
+
+    if (!d->inputs)
+        return true;
+
+    scancode = vnc_display_keymap_gdk2xtkbd(d->keycode_map, d->keycode_maplen,
+                                            key->hardware_keycode);
+#ifdef G_OS_WIN32
+    /* MapVirtualKey doesn't return scancode with needed higher byte */
+    scancode = MapVirtualKey(key->hardware_keycode, MAPVK_VK_TO_VSC) |
+        (scancode & 0xff00);
+#endif
+
+    switch (key->type) {
+    case GDK_KEY_PRESS:
+        send_key(display, scancode, SEND_KEY_PRESS, !key->is_modifier);
+        break;
+    case GDK_KEY_RELEASE:
+        send_key(display, scancode, SEND_KEY_RELEASE, !key->is_modifier);
+        break;
+    default:
+        g_warn_if_reached();
+        break;
+    }
+
+    return true;
+}
+
+static guint get_scancode_from_keyval(SpiceDisplay *display, guint keyval)
+{
+    SpiceDisplayPrivate *d = display->priv;
+    guint keycode = 0;
+    GdkKeymapKey *keys = NULL;
+    gint n_keys = 0;
+
+    if (gdk_keymap_get_entries_for_keyval(gdk_keymap_get_default(),
+                                          keyval, &keys, &n_keys)) {
+        /* FIXME what about levels? */
+        keycode = keys[0].keycode;
+        g_free(keys);
+    } else {
+        g_warning("could not lookup keyval %u, please report a bug", keyval);
+        return 0;
+    }
+
+    return vnc_display_keymap_gdk2xtkbd(d->keycode_map, d->keycode_maplen, keycode);
+}
+
+
+/**
+ * spice_display_send_keys:
+ * @display: The #SpiceDisplay
+ * @keyvals: (array length=nkeyvals): Keyval array
+ * @nkeyvals: Length of keyvals
+ * @kind: #SpiceDisplayKeyEvent action
+ *
+ * Send keyval press/release events to the display.
+ *
+ **/
+void spice_display_send_keys(SpiceDisplay *display, const guint *keyvals,
+                             int nkeyvals, SpiceDisplayKeyEvent kind)
+{
+    int i;
+
+    g_return_if_fail(SPICE_IS_DISPLAY(display));
+    g_return_if_fail(keyvals != NULL);
+
+    SPICE_DEBUG("%s", __FUNCTION__);
+
+    if (kind & SPICE_DISPLAY_KEY_EVENT_PRESS) {
+        for (i = 0 ; i < nkeyvals ; i++)
+            send_key(display, get_scancode_from_keyval(display, keyvals[i]), SEND_KEY_PRESS, FALSE);
+    }
+
+    if (kind & SPICE_DISPLAY_KEY_EVENT_RELEASE) {
+        for (i = (nkeyvals-1) ; i >= 0 ; i--)
+            send_key(display, get_scancode_from_keyval(display, keyvals[i]), SEND_KEY_RELEASE, FALSE);
+    }
+}
+
+static gboolean enter_event(GtkWidget *widget, GdkEventCrossing *crossing G_GNUC_UNUSED)
+{
+    SpiceDisplay *display = SPICE_DISPLAY(widget);
+    SpiceDisplayPrivate *d = display->priv;
+
+    SPICE_DEBUG("%s", __FUNCTION__);
+
+    d->mouse_have_pointer = true;
+    try_keyboard_grab(display);
+    update_display(display);
+
+    return true;
+}
+
+static gboolean leave_event(GtkWidget *widget, GdkEventCrossing *crossing G_GNUC_UNUSED)
+{
+    SpiceDisplay *display = SPICE_DISPLAY(widget);
+    SpiceDisplayPrivate *d = display->priv;
+
+    SPICE_DEBUG("%s", __FUNCTION__);
+
+    if (d->mouse_grab_active)
+        return true;
+
+    d->mouse_have_pointer = false;
+    try_keyboard_ungrab(display);
+
+    return true;
+}
+
+static gboolean focus_in_event(GtkWidget *widget, GdkEventFocus *focus G_GNUC_UNUSED)
+{
+    SpiceDisplay *display = SPICE_DISPLAY(widget);
+    SpiceDisplayPrivate *d = display->priv;
+
+    SPICE_DEBUG("%s", __FUNCTION__);
+
+    /*
+     * Ignore focus in when we already have the focus
+     * (this happens when doing an ungrab from the leave_event callback).
+     */
+    if (d->keyboard_have_focus)
+        return true;
+
+    release_keys(display);
+    if (!d->disable_inputs)
+        spice_gtk_session_sync_keyboard_modifiers(d->gtk_session);
+    if (d->keyboard_grab_released)
+        memset(d->activeseq, 0, sizeof(gboolean) * d->grabseq->nkeysyms);
+    update_keyboard_focus(display, true);
+    try_keyboard_grab(display);
+
+    if (gtk_widget_get_realized(widget))
+        update_display(display);
+
+    return true;
+}
+
+static gboolean focus_out_event(GtkWidget *widget, GdkEventFocus *focus G_GNUC_UNUSED)
+{
+    SpiceDisplay *display = SPICE_DISPLAY(widget);
+    SpiceDisplayPrivate *d = display->priv;
+
+    SPICE_DEBUG("%s", __FUNCTION__);
+    update_display(NULL);
+
+    /*
+     * Ignore focus out after a keyboard grab
+     * (this happens when doing the grab from the enter_event callback).
+     */
+    if (d->keyboard_grab_active)
+        return true;
+
+    release_keys(display);
+    update_keyboard_focus(display, false);
+
+    return true;
+}
+
+static int button_gdk_to_spice(int gdk)
+{
+    static const int map[] = {
+        [ 1 ] = SPICE_MOUSE_BUTTON_LEFT,
+        [ 2 ] = SPICE_MOUSE_BUTTON_MIDDLE,
+        [ 3 ] = SPICE_MOUSE_BUTTON_RIGHT,
+        [ 4 ] = SPICE_MOUSE_BUTTON_UP,
+        [ 5 ] = SPICE_MOUSE_BUTTON_DOWN,
+    };
+
+    if (gdk < SPICE_N_ELEMENTS(map)) {
+        return map [ gdk ];
+    }
+    return 0;
+}
+
+static int button_mask_gdk_to_spice(int gdk)
+{
+    int spice = 0;
+
+    if (gdk & GDK_BUTTON1_MASK)
+        spice |= SPICE_MOUSE_BUTTON_MASK_LEFT;
+    if (gdk & GDK_BUTTON2_MASK)
+        spice |= SPICE_MOUSE_BUTTON_MASK_MIDDLE;
+    if (gdk & GDK_BUTTON3_MASK)
+        spice |= SPICE_MOUSE_BUTTON_MASK_RIGHT;
+    return spice;
+}
+
+G_GNUC_INTERNAL
+void spicex_transform_input (SpiceDisplay *display,
+                             double window_x, double window_y,
+                             int *input_x, int *input_y)
+{
+    SpiceDisplayPrivate *d = display->priv;
+    int display_x, display_y, display_w, display_h;
+    double is;
+
+    spice_display_get_scaling(display, NULL,
+                              &display_x, &display_y,
+                              &display_w, &display_h);
+
+    /* For input we need a different scaling factor in order to
+       be able to reach the full width of a display. For instance, consider
+       a display of 100 pixels showing in a window 10 pixels wide. The normal
+       scaling factor here would be 100/10==10, but if you then take the largest
+       possible window coordinate, i.e. 9 and multiply by 10 you get 90, not 99,
+       which is the max display coord.
+
+       If you want to be able to reach the last pixel in the window you need
+       max_window_x * input_scale == max_display_x, which is
+       (window_width - 1) * input_scale == (display_width - 1)
+
+       Note, this is the inverse of s (i.e. s ~= 1/is) as we're converting the
+       coordinates in the inverse direction (window -> display) as the fb size
+       (display -> window).
+    */
+    is = (double)(d->area.width-1) / (double)(display_w-1);
+
+    window_x -= display_x;
+    window_y -= display_y;
+
+    *input_x = floor (window_x * is);
+    *input_y = floor (window_y * is);
+}
+
+static gboolean motion_event(GtkWidget *widget, GdkEventMotion *motion)
+{
+    SpiceDisplay *display = SPICE_DISPLAY(widget);
+    SpiceDisplayPrivate *d = display->priv;
+    int x, y;
+
+    if (!d->inputs)
+        return true;
+    if (d->disable_inputs)
+        return true;
+
+    d->seq_pressed = FALSE;
+
+    if (d->keyboard_grab_released && d->keyboard_have_focus) {
+        d->keyboard_grab_released = FALSE;
+        release_keys(display);
+        try_keyboard_grab(display);
+    }
+
+    spicex_transform_input (display, motion->x, motion->y, &x, &y);
+
+    switch (d->mouse_mode) {
+    case SPICE_MOUSE_MODE_CLIENT:
+        if (x >= 0 && x < d->area.width &&
+            y >= 0 && y < d->area.height) {
+            spice_inputs_position(d->inputs, x, y, get_display_id(display),
+                                  button_mask_gdk_to_spice(motion->state));
+        }
+        break;
+    case SPICE_MOUSE_MODE_SERVER:
+        if (d->mouse_grab_active) {
+            gint dx = d->mouse_last_x != -1 ? x - d->mouse_last_x : 0;
+            gint dy = d->mouse_last_y != -1 ? y - d->mouse_last_y : 0;
+
+            spice_inputs_motion(d->inputs, dx, dy,
+                                button_mask_gdk_to_spice(motion->state));
+
+            d->mouse_last_x = x;
+            d->mouse_last_y = y;
+            if (dx != 0 || dy != 0)
+                mouse_wrap(display, motion);
+        }
+        break;
+    default:
+        g_warn_if_reached();
+        break;
+    }
+    return true;
+}
+
+static gboolean scroll_event(GtkWidget *widget, GdkEventScroll *scroll)
+{
+    int button;
+    SpiceDisplay *display = SPICE_DISPLAY(widget);
+    SpiceDisplayPrivate *d = display->priv;
+
+    SPICE_DEBUG("%s", __FUNCTION__);
+
+    if (!d->inputs)
+        return true;
+    if (d->disable_inputs)
+        return true;
+
+    if (scroll->direction == GDK_SCROLL_UP)
+        button = SPICE_MOUSE_BUTTON_UP;
+    else if (scroll->direction == GDK_SCROLL_DOWN)
+        button = SPICE_MOUSE_BUTTON_DOWN;
+    else {
+        SPICE_DEBUG("unsupported scroll direction");
+        return true;
+    }
+
+    spice_inputs_button_press(d->inputs, button,
+                              button_mask_gdk_to_spice(scroll->state));
+    spice_inputs_button_release(d->inputs, button,
+                                button_mask_gdk_to_spice(scroll->state));
+    return true;
+}
+
+static gboolean button_event(GtkWidget *widget, GdkEventButton *button)
+{
+    SpiceDisplay *display = SPICE_DISPLAY(widget);
+    SpiceDisplayPrivate *d = display->priv;
+    int x, y;
+
+    SPICE_DEBUG("%s %s: button %d, state 0x%x", __FUNCTION__,
+            button->type == GDK_BUTTON_PRESS ? "press" : "release",
+            button->button, button->state);
+
+    if (d->disable_inputs)
+        return true;
+
+    spicex_transform_input (display, button->x, button->y, &x, &y);
+    if ((x < 0 || x >= d->area.width ||
+         y < 0 || y >= d->area.height) &&
+        d->mouse_mode == SPICE_MOUSE_MODE_CLIENT) {
+        /* rule out clicks in outside region */
+        return true;
+    }
+
+    gtk_widget_grab_focus(widget);
+    if (d->mouse_mode == SPICE_MOUSE_MODE_SERVER) {
+        if (!d->mouse_grab_active) {
+            try_mouse_grab(display);
+            return true;
+        }
+    } else
+        /* allow to drag and drop between windows/displays:
+
+           By default, X (and other window system) do a pointer grab
+           when you press a button, so that the release event is
+           received by the same window regardless of where the pointer
+           is. Here, we change that behaviour, so that you can press
+           and release in two differents displays. This is only
+           supported in client mouse mode.
+
+           FIXME: should be multiple widget grab, but how?
+           or should know the position of the other widgets?
+        */
+        gdk_pointer_ungrab(GDK_CURRENT_TIME);
+
+    if (!d->inputs)
+        return true;
+
+    switch (button->type) {
+    case GDK_BUTTON_PRESS:
+        spice_inputs_button_press(d->inputs,
+                                  button_gdk_to_spice(button->button),
+                                  button_mask_gdk_to_spice(button->state));
+        break;
+    case GDK_BUTTON_RELEASE:
+        spice_inputs_button_release(d->inputs,
+                                    button_gdk_to_spice(button->button),
+                                    button_mask_gdk_to_spice(button->state));
+        break;
+    default:
+        break;
+    }
+    return true;
+}
+
+static gboolean configure_event(GtkWidget *widget, GdkEventConfigure *conf)
+{
+    SpiceDisplay *display = SPICE_DISPLAY(widget);
+    SpiceDisplayPrivate *d = display->priv;
+
+    if (conf->width == d->ww && conf->height == d->wh &&
+            conf->x == d->mx && conf->y == d->my) {
+        return true;
+    }
+
+    if (conf->width != d->ww  || conf->height != d->wh) {
+        d->ww = conf->width;
+        d->wh = conf->height;
+        recalc_geometry(widget);
+    }
+
+    d->mx = conf->x;
+    d->my = conf->y;
+
+#ifdef G_OS_WIN32
+    if (d->mouse_grab_active) {
+        try_mouse_ungrab(display);
+        try_mouse_grab(display);
+    }
+#endif
+
+    return true;
+}
+
+static void update_image(SpiceDisplay *display)
+{
+    SpiceDisplayPrivate *d = display->priv;
+
+    spicex_image_create(display);
+    if (d->convert)
+        do_color_convert(display, &d->area);
+}
+
+static void realize(GtkWidget *widget)
+{
+    SpiceDisplay *display = SPICE_DISPLAY(widget);
+    SpiceDisplayPrivate *d = display->priv;
+
+    GTK_WIDGET_CLASS(spice_display_parent_class)->realize(widget);
+
+    d->keycode_map =
+        vnc_display_keymap_gdk2xtkbd_table(gtk_widget_get_window(widget),
+                                           &d->keycode_maplen);
+    update_image(display);
+}
+
+static void unrealize(GtkWidget *widget)
+{
+    spicex_image_destroy(SPICE_DISPLAY(widget));
+
+    GTK_WIDGET_CLASS(spice_display_parent_class)->unrealize(widget);
+}
+
+
+/* ---------------------------------------------------------------- */
+
+static void spice_display_class_init(SpiceDisplayClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+    GtkWidgetClass *gtkwidget_class = GTK_WIDGET_CLASS(klass);
+
+#if GTK_CHECK_VERSION (2, 91, 0)
+    gtkwidget_class->draw = draw_event;
+#else
+    gtkwidget_class->expose_event = expose_event;
+#endif
+    gtkwidget_class->key_press_event = key_event;
+    gtkwidget_class->key_release_event = key_event;
+    gtkwidget_class->enter_notify_event = enter_event;
+    gtkwidget_class->leave_notify_event = leave_event;
+    gtkwidget_class->focus_in_event = focus_in_event;
+    gtkwidget_class->focus_out_event = focus_out_event;
+    gtkwidget_class->motion_notify_event = motion_event;
+    gtkwidget_class->button_press_event = button_event;
+    gtkwidget_class->button_release_event = button_event;
+    gtkwidget_class->configure_event = configure_event;
+    gtkwidget_class->scroll_event = scroll_event;
+    gtkwidget_class->realize = realize;
+    gtkwidget_class->unrealize = unrealize;
+
+    gobject_class->constructor = spice_display_constructor;
+    gobject_class->dispose = spice_display_dispose;
+    gobject_class->finalize = spice_display_finalize;
+    gobject_class->get_property = spice_display_get_property;
+    gobject_class->set_property = spice_display_set_property;
+
+    /**
+     * SpiceDisplay:session:
+     *
+     * #SpiceSession for this #SpiceDisplay
+     *
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_SESSION,
+         g_param_spec_object("session",
+                             "Session",
+                             "SpiceSession",
+                             SPICE_TYPE_SESSION,
+                             G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+                             G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceDisplay:channel-id:
+     *
+     * channel-id for this #SpiceDisplay
+     *
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_CHANNEL_ID,
+         g_param_spec_int("channel-id",
+                          "Channel ID",
+                          "Channel ID for this display",
+                          0, 255, 0,
+                          G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+                          G_PARAM_STATIC_STRINGS));
+
+    g_object_class_install_property
+        (gobject_class, PROP_KEYBOARD_GRAB,
+         g_param_spec_boolean("grab-keyboard",
+                              "Grab Keyboard",
+                              "Whether we should grab the keyboard.",
+                              TRUE,
+                              G_PARAM_READWRITE |
+                              G_PARAM_CONSTRUCT |
+                              G_PARAM_STATIC_STRINGS));
+
+    g_object_class_install_property
+        (gobject_class, PROP_MOUSE_GRAB,
+         g_param_spec_boolean("grab-mouse",
+                              "Grab Mouse",
+                              "Whether we should grab the mouse.",
+                              TRUE,
+                              G_PARAM_READWRITE |
+                              G_PARAM_CONSTRUCT |
+                              G_PARAM_STATIC_STRINGS));
+
+    g_object_class_install_property
+        (gobject_class, PROP_RESIZE_GUEST,
+         g_param_spec_boolean("resize-guest",
+                              "Resize guest",
+                              "Try to adapt guest display on window resize. "
+                              "Requires guest cooperation.",
+                              FALSE,
+                              G_PARAM_READWRITE |
+                              G_PARAM_CONSTRUCT |
+                              G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceDisplay:ready:
+     *
+     * Indicate whether the display is ready to be shown. It takes
+     * into account several conditions, such as the channel display
+     * "mark" state, whether the monitor area is visible..
+     *
+     * Since: 0.13
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_READY,
+         g_param_spec_boolean("ready",
+                              "Ready",
+                              "Ready to display",
+                              FALSE,
+                              G_PARAM_READABLE |
+                              G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceDisplay:auto-clipboard:
+     *
+     * When this is true the clipboard gets automatically shared between host
+     * and guest.
+     *
+     * Deprecated: 0.8: Use SpiceGtkSession:auto-clipboard property instead
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_AUTO_CLIPBOARD,
+         g_param_spec_boolean("auto-clipboard",
+                              "Auto clipboard",
+                              "Automatically relay clipboard changes between "
+                              "host and guest.",
+                              TRUE,
+                              G_PARAM_READWRITE |
+                              G_PARAM_STATIC_STRINGS |
+                              G_PARAM_DEPRECATED));
+
+    g_object_class_install_property
+        (gobject_class, PROP_SCALING,
+         g_param_spec_boolean("scaling", "Scaling",
+                              "Whether we should use scaling",
+                              TRUE,
+                              G_PARAM_READWRITE |
+                              G_PARAM_CONSTRUCT |
+                              G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceDisplay:only-downscale:
+     *
+     * If scaling, only scale down, never up.
+     *
+     * Since: 0.14
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_ONLY_DOWNSCALE,
+         g_param_spec_boolean("only-downscale", "Only Downscale",
+                              "If scaling, only scale down, never up",
+                              FALSE,
+                              G_PARAM_READWRITE |
+                              G_PARAM_CONSTRUCT |
+                              G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceDisplay:keypress-delay:
+     *
+     * Delay in ms of non-modifiers key press events. If the key is
+     * released before this delay, a single press & release event is
+     * sent to the server. If the key is pressed longer than the
+     * keypress-delay, the server will receive the delayed press
+     * event, and a following release event when the key is released.
+     *
+     * Since: 0.13
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_KEYPRESS_DELAY,
+         g_param_spec_uint("keypress-delay", "Keypress delay",
+                           "Keypress delay",
+                           0, G_MAXUINT, 100,
+                           G_PARAM_READWRITE |
+                           G_PARAM_CONSTRUCT |
+                           G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceDisplay:disable-inputs:
+     *
+     * Disable all keyboard & mouse inputs.
+     *
+     * Since: 0.8
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_DISABLE_INPUTS,
+         g_param_spec_boolean("disable-inputs", "Disable inputs",
+                              "Whether inputs should be disabled",
+                              FALSE,
+                              G_PARAM_READWRITE |
+                              G_PARAM_CONSTRUCT |
+                              G_PARAM_STATIC_STRINGS));
+
+
+    /**
+     * SpiceDisplay:zoom-level:
+     *
+     * Zoom level in percentage, from 10 to 400. Default to 100.
+     * (this option is only supported with cairo backend when scaling
+     * is enabled)
+     *
+     * Since: 0.10
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_ZOOM_LEVEL,
+         g_param_spec_int("zoom-level", "Zoom Level",
+                          "Zoom Level",
+                          10, 400, 100,
+                          G_PARAM_READWRITE |
+                          G_PARAM_CONSTRUCT |
+                          G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceDisplay:monitor-id:
+     *
+     * Select monitor from #SpiceDisplay to show.
+     * The value -1 means the whole display is shown.
+     * By default, the monitor 0 is selected.
+     *
+     * Since: 0.13
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_MONITOR_ID,
+         g_param_spec_int("monitor-id",
+                          "Monitor ID",
+                          "Select monitor ID",
+                          -1, G_MAXINT, 0,
+                          G_PARAM_READWRITE |
+                          G_PARAM_CONSTRUCT |
+                          G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceDisplay::mouse-grab:
+     * @display: the #SpiceDisplay that emitted the signal
+     * @status: 1 if grabbed, 0 otherwise.
+     *
+     * Notify when the mouse grab is active or not.
+     **/
+    signals[SPICE_DISPLAY_MOUSE_GRAB] =
+        g_signal_new("mouse-grab",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_FIRST,
+                     G_STRUCT_OFFSET(SpiceDisplayClass, mouse_grab),
+                     NULL, NULL,
+                     g_cclosure_marshal_VOID__INT,
+                     G_TYPE_NONE,
+                     1,
+                     G_TYPE_INT);
+
+    /**
+     * SpiceDisplay::keyboard-grab:
+     * @display: the #SpiceDisplay that emitted the signal
+     * @status: 1 if grabbed, 0 otherwise.
+     *
+     * Notify when the keyboard grab is active or not.
+     **/
+    signals[SPICE_DISPLAY_KEYBOARD_GRAB] =
+        g_signal_new("keyboard-grab",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_FIRST,
+                     G_STRUCT_OFFSET(SpiceDisplayClass, keyboard_grab),
+                     NULL, NULL,
+                     g_cclosure_marshal_VOID__INT,
+                     G_TYPE_NONE,
+                     1,
+                     G_TYPE_INT);
+
+    /**
+     * SpiceDisplay::grab-keys-pressed:
+     * @display: the #SpiceDisplay that emitted the signal
+     *
+     * Notify when the grab keys have been pressed
+     **/
+    signals[SPICE_DISPLAY_GRAB_KEY_PRESSED] =
+        g_signal_new("grab-keys-pressed",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_FIRST,
+                     G_STRUCT_OFFSET(SpiceDisplayClass, keyboard_grab),
+                     NULL, NULL,
+                     g_cclosure_marshal_VOID__VOID,
+                     G_TYPE_NONE,
+                     0);
+
+    g_type_class_add_private(klass, sizeof(SpiceDisplayPrivate));
+}
+
+/* ---------------------------------------------------------------- */
+
+#define SPICE_GDK_BUTTONS_MASK \
+    (GDK_BUTTON1_MASK|GDK_BUTTON2_MASK|GDK_BUTTON3_MASK|GDK_BUTTON4_MASK|GDK_BUTTON5_MASK)
+
+static void update_mouse_mode(SpiceChannel *channel, gpointer data)
+{
+    SpiceDisplay *display = data;
+    SpiceDisplayPrivate *d = display->priv;
+    GdkWindow *window = gtk_widget_get_window(GTK_WIDGET(display));
+
+    g_object_get(channel, "mouse-mode", &d->mouse_mode, NULL);
+    SPICE_DEBUG("mouse mode %d", d->mouse_mode);
+
+    switch (d->mouse_mode) {
+    case SPICE_MOUSE_MODE_CLIENT:
+        try_mouse_ungrab(display);
+        break;
+    case SPICE_MOUSE_MODE_SERVER:
+        d->mouse_guest_x = -1;
+        d->mouse_guest_y = -1;
+
+        if (window != NULL) {
+            GdkModifierType modifiers;
+            gdk_window_get_pointer(window, NULL, NULL, &modifiers);
+
+            if (modifiers & SPICE_GDK_BUTTONS_MASK)
+                try_mouse_grab(display);
+        }
+        break;
+    default:
+        g_warn_if_reached();
+    }
+
+    update_mouse_pointer(display);
+}
+
+static void update_area(SpiceDisplay *display,
+                        gint x, gint y, gint width, gint height)
+{
+    SpiceDisplayPrivate *d = display->priv;
+    GdkRectangle primary = {
+        .x = 0,
+        .y = 0,
+        .width = d->width,
+        .height = d->height
+    };
+    GdkRectangle area = {
+        .x = x,
+        .y = y,
+        .width = width,
+        .height = height
+    };
+
+    SPICE_DEBUG("update area, primary: %dx%d, area: +%d+%d %dx%d", d->width, d->height, area.x, area.y, area.width, area.height);
+
+    if (!gdk_rectangle_intersect(&primary, &area, &area)) {
+        SPICE_DEBUG("The monitor area is not intersecting primary surface");
+        memset(&d->area, '\0', sizeof(d->area));
+        set_monitor_ready(display, false);
+        return;
+    }
+
+    spicex_image_destroy(display);
+    d->area = area;
+    if (gtk_widget_get_realized(GTK_WIDGET(display)))
+        update_image(display);
+
+    update_size_request(display);
+
+    set_monitor_ready(display, true);
+}
+
+static void primary_create(SpiceChannel *channel, gint format,
+                           gint width, gint height, gint stride,
+                           gint shmid, gpointer imgdata, gpointer data)
+{
+    SpiceDisplay *display = data;
+    SpiceDisplayPrivate *d = display->priv;
+
+    d->format = format;
+    d->stride = stride;
+    d->shmid = shmid;
+    d->width = width;
+    d->height = height;
+    d->data_origin = d->data = imgdata;
+
+    update_monitor_area(display);
+}
+
+static void primary_destroy(SpiceChannel *channel, gpointer data)
+{
+    SpiceDisplay *display = SPICE_DISPLAY(data);
+    SpiceDisplayPrivate *d = display->priv;
+
+    spicex_image_destroy(display);
+    d->width  = 0;
+    d->height = 0;
+    d->stride = 0;
+    d->shmid  = 0;
+    d->data = NULL;
+    d->data_origin = NULL;
+    set_monitor_ready(display, false);
+}
+
+static void invalidate(SpiceChannel *channel,
+                       gint x, gint y, gint w, gint h, gpointer data)
+{
+    SpiceDisplay *display = data;
+    SpiceDisplayPrivate *d = display->priv;
+    int display_x, display_y;
+    int x1, y1, x2, y2;
+    double s;
+    GdkRectangle rect = {
+        .x = x,
+        .y = y,
+        .width = w,
+        .height = h
+    };
+
+    if (!gtk_widget_get_window(GTK_WIDGET(display)))
+        return;
+
+    if (!gdk_rectangle_intersect(&rect, &d->area, &rect))
+        return;
+
+    if (d->convert)
+        do_color_convert(display, &rect);
+
+    spice_display_get_scaling(display, &s,
+                              &display_x, &display_y,
+                              NULL, NULL);
+
+    x1 = floor ((rect.x - d->area.x) * s);
+    y1 = floor ((rect.y - d->area.y) * s);
+    x2 = ceil ((rect.x - d->area.x + rect.width) * s);
+    y2 = ceil ((rect.y - d->area.y + rect.height) * s);
+
+    gtk_widget_queue_draw_area(GTK_WIDGET(display),
+                               display_x + x1, display_y + y1,
+                               x2 - x1, y2-y1);
+}
+
+static void mark(SpiceDisplay *display, gint mark)
+{
+    SpiceDisplayPrivate *d = display->priv;
+    g_return_if_fail(d != NULL);
+
+    SPICE_DEBUG("widget mark: %d, %d:%d %p", mark, d->channel_id, d->monitor_id, display);
+    d->mark = mark;
+    update_ready(display);
+}
+
+static void cursor_set(SpiceCursorChannel *channel,
+                       gint width, gint height, gint hot_x, gint hot_y,
+                       gpointer rgba, gpointer data)
+{
+    SpiceDisplay *display = data;
+    SpiceDisplayPrivate *d = display->priv;
+    GdkCursor *cursor = NULL;
+
+    cursor_invalidate(display);
+
+    if (d->mouse_pixbuf) {
+        g_object_unref(d->mouse_pixbuf);
+        d->mouse_pixbuf = NULL;
+    }
+
+    if (rgba != NULL) {
+        d->mouse_pixbuf = gdk_pixbuf_new_from_data(g_memdup(rgba, width * height * 4),
+                                                   GDK_COLORSPACE_RGB,
+                                                   TRUE, 8,
+                                                   width,
+                                                   height,
+                                                   width * 4,
+                                                   (GdkPixbufDestroyNotify)g_free, NULL);
+        d->mouse_hotspot.x = hot_x;
+        d->mouse_hotspot.y = hot_y;
+        cursor = gdk_cursor_new_from_pixbuf(gtk_widget_get_display(GTK_WIDGET(display)),
+                                            d->mouse_pixbuf, hot_x, hot_y);
+    } else
+        g_warn_if_reached();
+
+    if (d->show_cursor) {
+        /* unhide */
+        gdk_cursor_unref(d->show_cursor);
+        d->show_cursor = NULL;
+        if (d->mouse_mode == SPICE_MOUSE_MODE_SERVER) {
+            /* keep a hidden cursor, will be shown in cursor_move() */
+            d->show_cursor = cursor;
+            return;
+        }
+    }
+
+    gdk_cursor_unref(d->mouse_cursor);
+    d->mouse_cursor = cursor;
+
+    update_mouse_pointer(display);
+    cursor_invalidate(display);
+}
+
+static void cursor_hide(SpiceCursorChannel *channel, gpointer data)
+{
+    SpiceDisplay *display = data;
+    SpiceDisplayPrivate *d = display->priv;
+
+    if (d->show_cursor != NULL) /* then we are already hidden */
+        return;
+
+    cursor_invalidate(display);
+    d->show_cursor = d->mouse_cursor;
+    d->mouse_cursor = get_blank_cursor();
+    update_mouse_pointer(display);
+}
+
+G_GNUC_INTERNAL
+void spice_display_get_scaling(SpiceDisplay *display,
+                               double *s_out,
+                               int *x_out, int *y_out,
+                               int *w_out, int *h_out)
+{
+    SpiceDisplayPrivate *d = display->priv;
+    int fbw = d->area.width, fbh = d->area.height;
+    int ww, wh;
+    int x, y, w, h;
+    double s;
+
+    if (gtk_widget_get_realized (GTK_WIDGET(display)))
+        gdk_drawable_get_size(gtk_widget_get_window(GTK_WIDGET(display)), &ww, &wh);
+    else {
+        ww = fbw;
+        wh = fbh;
+    }
+
+    if (!spicex_is_scaled(display)) {
+        s = 1.0;
+        x = 0;
+        y = 0;
+        if (ww > d->area.width)
+            x = (ww - d->area.width) / 2;
+        if (wh > d->area.height)
+            y = (wh - d->area.height) / 2;
+        w = fbw;
+        h = fbh;
+    } else {
+        s = MIN ((double)ww / (double)fbw, (double)wh / (double)fbh);
+
+        if (d->only_downscale && s >= 1.0)
+            s = 1.0;
+
+        /* Round to int size */
+        w = floor (fbw * s + 0.5);
+        h = floor (fbh * s + 0.5);
+
+        /* Center the display */
+        x = (ww - w) / 2;
+        y = (wh - h) / 2;
+    }
+
+    if (s_out)
+        *s_out = s;
+    if (w_out)
+        *w_out = w;
+    if (h_out)
+        *h_out = h;
+    if (x_out)
+        *x_out = x;
+    if (y_out)
+        *y_out = y;
+}
+
+static void cursor_invalidate(SpiceDisplay *display)
+{
+    SpiceDisplayPrivate *d = display->priv;
+    double s;
+    int x, y;
+
+    if (!gtk_widget_get_realized (GTK_WIDGET(display)))
+        return;
+
+    if (d->mouse_pixbuf == NULL)
+        return;
+
+    if (!d->ready || !d->monitor_ready)
+        return;
+
+    spice_display_get_scaling(display, &s, &x, &y, NULL, NULL);
+
+    gtk_widget_queue_draw_area(GTK_WIDGET(display),
+                               floor ((d->mouse_guest_x - d->mouse_hotspot.x - d->area.x) * s) + x,
+                               floor ((d->mouse_guest_y - d->mouse_hotspot.y - d->area.y) * s) + y,
+                               ceil (gdk_pixbuf_get_width(d->mouse_pixbuf) * s),
+                               ceil (gdk_pixbuf_get_height(d->mouse_pixbuf) * s));
+}
+
+static void cursor_move(SpiceCursorChannel *channel, gint x, gint y, gpointer data)
+{
+    SpiceDisplay *display = data;
+    SpiceDisplayPrivate *d = display->priv;
+
+    cursor_invalidate(display);
+
+    d->mouse_guest_x = x;
+    d->mouse_guest_y = y;
+
+    cursor_invalidate(display);
+
+    /* apparently we have to restore cursor when "cursor_move" */
+    if (d->show_cursor != NULL) {
+        gdk_cursor_unref(d->mouse_cursor);
+        d->mouse_cursor = d->show_cursor;
+        d->show_cursor = NULL;
+        update_mouse_pointer(display);
+    }
+}
+
+static void cursor_reset(SpiceCursorChannel *channel, gpointer data)
+{
+    SpiceDisplay *display = data;
+    GdkWindow *window = gtk_widget_get_window(GTK_WIDGET(display));
+
+    if (!window) {
+        SPICE_DEBUG("%s: no window, returning",  __FUNCTION__);
+        return;
+    }
+
+    SPICE_DEBUG("%s",  __FUNCTION__);
+    gdk_window_set_cursor(window, NULL);
+}
+
+static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data)
+{
+    SpiceDisplay *display = data;
+    SpiceDisplayPrivate *d = display->priv;
+    int id;
+
+    g_object_get(channel, "channel-id", &id, NULL);
+    if (SPICE_IS_MAIN_CHANNEL(channel)) {
+        d->main = SPICE_MAIN_CHANNEL(channel);
+        spice_g_signal_connect_object(channel, "main-mouse-update",
+                                      G_CALLBACK(update_mouse_mode), display, 0);
+        update_mouse_mode(channel, display);
+        return;
+    }
+
+    if (SPICE_IS_DISPLAY_CHANNEL(channel)) {
+        SpiceDisplayPrimary primary;
+        if (id != d->channel_id)
+            return;
+        d->display = channel;
+        spice_g_signal_connect_object(channel, "display-primary-create",
+                                      G_CALLBACK(primary_create), display, 0);
+        spice_g_signal_connect_object(channel, "display-primary-destroy",
+                                      G_CALLBACK(primary_destroy), display, 0);
+        spice_g_signal_connect_object(channel, "display-invalidate",
+                                      G_CALLBACK(invalidate), display, 0);
+        spice_g_signal_connect_object(channel, "display-mark",
+                                      G_CALLBACK(mark), display, G_CONNECT_AFTER | G_CONNECT_SWAPPED);
+        spice_g_signal_connect_object(channel, "notify::monitors",
+                                      G_CALLBACK(update_monitor_area), display, G_CONNECT_AFTER | G_CONNECT_SWAPPED);
+        if (spice_display_get_primary(channel, 0, &primary)) {
+            primary_create(channel, primary.format, primary.width, primary.height,
+                           primary.stride, primary.shmid, primary.data, display);
+            mark(display, primary.marked);
+        }
+        spice_channel_connect(channel);
+        spice_main_set_display_enabled(d->main, get_display_id(display), TRUE);
+        return;
+    }
+
+    if (SPICE_IS_CURSOR_CHANNEL(channel)) {
+        if (id != d->channel_id)
+            return;
+        d->cursor = SPICE_CURSOR_CHANNEL(channel);
+        spice_g_signal_connect_object(channel, "cursor-set",
+                                      G_CALLBACK(cursor_set), display, 0);
+        spice_g_signal_connect_object(channel, "cursor-move",
+                                      G_CALLBACK(cursor_move), display, 0);
+        spice_g_signal_connect_object(channel, "cursor-hide",
+                                      G_CALLBACK(cursor_hide), display, 0);
+        spice_g_signal_connect_object(channel, "cursor-reset",
+                                      G_CALLBACK(cursor_reset), display, 0);
+        spice_channel_connect(channel);
+        return;
+    }
+
+    if (SPICE_IS_INPUTS_CHANNEL(channel)) {
+        d->inputs = SPICE_INPUTS_CHANNEL(channel);
+        spice_channel_connect(channel);
+        return;
+    }
+
+#ifdef USE_SMARTCARD
+    if (SPICE_IS_SMARTCARD_CHANNEL(channel)) {
+        d->smartcard = SPICE_SMARTCARD_CHANNEL(channel);
+        spice_channel_connect(channel);
+        return;
+    }
+#endif
+
+    return;
+}
+
+static void channel_destroy(SpiceSession *s, SpiceChannel *channel, gpointer data)
+{
+    SpiceDisplay *display = data;
+    SpiceDisplayPrivate *d = display->priv;
+    int id;
+
+    g_object_get(channel, "channel-id", &id, NULL);
+    SPICE_DEBUG("channel_destroy %d", id);
+
+    if (SPICE_IS_MAIN_CHANNEL(channel)) {
+        d->main = NULL;
+        return;
+    }
+
+    if (SPICE_IS_DISPLAY_CHANNEL(channel)) {
+        if (id != d->channel_id)
+            return;
+        primary_destroy(d->display, display);
+        d->display = NULL;
+        return;
+    }
+
+    if (SPICE_IS_CURSOR_CHANNEL(channel)) {
+        if (id != d->channel_id)
+            return;
+        d->cursor = NULL;
+        return;
+    }
+
+    if (SPICE_IS_INPUTS_CHANNEL(channel)) {
+        d->inputs = NULL;
+        return;
+    }
+
+#ifdef USE_SMARTCARD
+    if (SPICE_IS_SMARTCARD_CHANNEL(channel)) {
+        d->smartcard = NULL;
+        return;
+    }
+#endif
+
+    return;
+}
+
+/**
+ * spice_display_new:
+ * @session: a #SpiceSession
+ * @channel_id: the display channel ID to associate with #SpiceDisplay
+ *
+ * Returns: a new #SpiceDisplay widget.
+ **/
+SpiceDisplay *spice_display_new(SpiceSession *session, int id)
+{
+    return g_object_new(SPICE_TYPE_DISPLAY, "session", session,
+                        "channel-id", id, NULL);
+}
+
+/**
+ * spice_display_new_with_monitor:
+ * @session: a #SpiceSession
+ * @channel_id: the display channel ID to associate with #SpiceDisplay
+ * @monitor_id: the monitor id within the display channel
+ *
+ * Since: 0.13
+ * Returns: a new #SpiceDisplay widget.
+ **/
+SpiceDisplay* spice_display_new_with_monitor(SpiceSession *session, gint channel_id, gint monitor_id)
+{
+    return g_object_new(SPICE_TYPE_DISPLAY,
+                        "session", session,
+                        "channel-id", channel_id,
+                        "monitor-id", monitor_id,
+                        NULL);
+}
+
+/**
+ * spice_display_mouse_ungrab:
+ * @display:
+ *
+ * Ungrab the mouse.
+ **/
+void spice_display_mouse_ungrab(SpiceDisplay *display)
+{
+    g_return_if_fail(SPICE_IS_DISPLAY(display));
+
+    try_mouse_ungrab(display);
+}
+
+/**
+ * spice_display_copy_to_guest:
+ * @display:
+ *
+ * Copy client-side clipboard to guest clipboard.
+ *
+ * Deprecated: 0.8: Use spice_gtk_session_copy_to_guest() instead
+ **/
+void spice_display_copy_to_guest(SpiceDisplay *display)
+{
+    SpiceDisplayPrivate *d;
+
+    g_return_if_fail(SPICE_IS_DISPLAY(display));
+
+    d = display->priv;
+
+    g_return_if_fail(d->gtk_session != NULL);
+
+    spice_gtk_session_copy_to_guest(d->gtk_session);
+}
+
+/**
+ * spice_display_paste_from_guest:
+ * @display:
+ *
+ * Copy guest clipboard to client-side clipboard.
+ *
+ * Deprecated: 0.8: Use spice_gtk_session_paste_from_guest() instead
+ **/
+void spice_display_paste_from_guest(SpiceDisplay *display)
+{
+    SpiceDisplayPrivate *d;
+
+    g_return_if_fail(SPICE_IS_DISPLAY(display));
+
+    d = display->priv;
+
+    g_return_if_fail(d->gtk_session != NULL);
+
+    spice_gtk_session_paste_from_guest(d->gtk_session);
+}
+
+/**
+ * spice_display_get_pixbuf:
+ * @display:
+ *
+ * Take a screenshot of the display.
+ *
+ * Returns: (transfer full): a #GdkPixbuf with the screenshot image buffer
+ **/
+GdkPixbuf *spice_display_get_pixbuf(SpiceDisplay *display)
+{
+    SpiceDisplayPrivate *d;
+    GdkPixbuf *pixbuf;
+    int x, y;
+    guchar *src, *data, *dest;
+
+    g_return_val_if_fail(SPICE_IS_DISPLAY(display), NULL);
+
+    d = display->priv;
+
+    g_return_val_if_fail(d != NULL, NULL);
+    /* TODO: ensure d->data has been exposed? */
+    g_return_val_if_fail(d->data != NULL, NULL);
+
+    data = g_malloc0(d->area.width * d->area.height * 3);
+    src = d->data;
+    dest = data;
+
+    src += d->area.y * d->stride + d->area.x * 4;
+    for (y = 0; y < d->area.height; ++y) {
+        for (x = 0; x < d->area.width; ++x) {
+          dest[0] = src[x * 4 + 2];
+          dest[1] = src[x * 4 + 1];
+          dest[2] = src[x * 4 + 0];
+          dest += 3;
+        }
+        src += d->stride;
+    }
+
+    pixbuf = gdk_pixbuf_new_from_data(data, GDK_COLORSPACE_RGB, false,
+                                      8, d->area.width, d->area.height, d->area.width * 3,
+                                      (GdkPixbufDestroyNotify)g_free, NULL);
+    return pixbuf;
+}
diff --git a/src/spice-widget.h b/src/spice-widget.h
new file mode 100644
index 0000000..d239ed2
--- /dev/null
+++ b/src/spice-widget.h
@@ -0,0 +1,92 @@
+/* -*- 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_CLIENT_WIDGET_H__
+#define __SPICE_CLIENT_WIDGET_H__
+
+#include "spice-client.h"
+
+#include <gtk/gtk.h>
+#include "spice-grabsequence.h"
+#include "spice-widget-enums.h"
+#include "spice-util.h"
+#include "spice-gtk-session.h"
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_DISPLAY            (spice_display_get_type())
+#define SPICE_DISPLAY(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), SPICE_TYPE_DISPLAY, SpiceDisplay))
+#define SPICE_DISPLAY_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), SPICE_TYPE_DISPLAY, SpiceDisplayClass))
+#define SPICE_IS_DISPLAY(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), SPICE_TYPE_DISPLAY))
+#define SPICE_IS_DISPLAY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SPICE_TYPE_DISPLAY))
+#define SPICE_DISPLAY_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), SPICE_TYPE_DISPLAY, SpiceDisplayClass))
+
+
+typedef struct _SpiceDisplay SpiceDisplay;
+typedef struct _SpiceDisplayClass SpiceDisplayClass;
+typedef struct _SpiceDisplayPrivate SpiceDisplayPrivate;
+
+struct _SpiceDisplay {
+    GtkDrawingArea parent;
+    SpiceDisplayPrivate *priv;
+    /* Do not add fields to this struct */
+};
+
+struct _SpiceDisplayClass {
+    GtkDrawingAreaClass parent_class;
+
+    /* signals */
+    void (*mouse_grab)(SpiceChannel *channel, gint grabbed);
+    void (*keyboard_grab)(SpiceChannel *channel, gint grabbed);
+
+    /*< private >*/
+    /*
+     * If adding fields to this struct, remove corresponding
+     * amount of padding to avoid changing overall struct size
+     */
+    gchar _spice_reserved[SPICE_RESERVED_PADDING];
+};
+
+typedef enum
+{
+	SPICE_DISPLAY_KEY_EVENT_PRESS = 1,
+	SPICE_DISPLAY_KEY_EVENT_RELEASE = 2,
+	SPICE_DISPLAY_KEY_EVENT_CLICK = 3,
+} SpiceDisplayKeyEvent;
+
+GType	        spice_display_get_type(void);
+
+SpiceDisplay* spice_display_new(SpiceSession *session, int channel_id);
+SpiceDisplay* spice_display_new_with_monitor(SpiceSession *session, gint channel_id, gint monitor_id);
+
+void spice_display_mouse_ungrab(SpiceDisplay *display);
+void spice_display_set_grab_keys(SpiceDisplay *display, SpiceGrabSequence *seq);
+SpiceGrabSequence *spice_display_get_grab_keys(SpiceDisplay *display);
+void spice_display_send_keys(SpiceDisplay *display, const guint *keyvals,
+                             int nkeyvals, SpiceDisplayKeyEvent kind);
+GdkPixbuf *spice_display_get_pixbuf(SpiceDisplay *display);
+
+#ifndef SPICE_DISABLE_DEPRECATED
+SPICE_DEPRECATED_FOR(spice_gtk_session_copy_to_guest)
+void spice_display_copy_to_guest(SpiceDisplay *display);
+SPICE_DEPRECATED_FOR(spice_gtk_session_paste_from_guest)
+void spice_display_paste_from_guest(SpiceDisplay *display);
+#endif
+
+G_END_DECLS
+
+#endif /* __SPICE_CLIENT_WIDGET_H__ */
diff --git a/src/spicy-screenshot.c b/src/spicy-screenshot.c
new file mode 100644
index 0000000..e7835bf
--- /dev/null
+++ b/src/spicy-screenshot.c
@@ -0,0 +1,196 @@
+/* -*- 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.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) {
+        fputc(p[2], fp);
+        fputc(p[1], fp);
+        fputc(p[0], fp);
+        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 %d\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: %d", 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      = N_("Output file name (default spicy-screenshot.ppm)"),
+        .arg_description  = N_("<filename>"),
+    },
+    {
+        .long_name        = "version",
+        .arg              = G_OPTION_ARG_NONE,
+        .arg_data         = &version,
+        .description      = N_("Display version and quit"),
+    },
+    {
+        /* end of list */
+    }
+};
+
+int main(int argc, char *argv[])
+{
+    GError *error = NULL;
+    GOptionContext *context;
+
+    bindtextdomain(GETTEXT_PACKAGE, SPICE_GTK_LOCALEDIR);
+    bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
+    textdomain(GETTEXT_PACKAGE);
+
+    /* 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);
+    }
+
+#if !GLIB_CHECK_VERSION(2,36,0)
+    g_type_init();
+#endif
+    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
new file mode 100644
index 0000000..c98148d
--- /dev/null
+++ b/src/spicy-stats.c
@@ -0,0 +1,144 @@
+/* -*- 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.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: %d", 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      = N_("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);
+
+    bindtextdomain(GETTEXT_PACKAGE, SPICE_GTK_LOCALEDIR);
+    bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
+    textdomain(GETTEXT_PACKAGE);
+
+    /* 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);
+    }
+
+#if !GLIB_CHECK_VERSION(2,36,0)
+    g_type_init();
+#endif
+    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
new file mode 100644
index 0000000..9cd6ee5
--- /dev/null
+++ b/src/spicy.c
@@ -0,0 +1,1855 @@
+/* -*- 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/gi18n.h>
+
+#include <sys/stat.h>
+#ifdef HAVE_TERMIOS_H
+#include <termios.h>
+#endif
+
+#ifdef USE_SMARTCARD
+#include <vreader.h>
+#include "smartcard-manager.h"
+#endif
+
+#include "glib-compat.h"
+#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"
+
+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;
+};
+
+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;
+};
+
+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,
+                                         GTK_STOCK_OK,
+                                         GTK_RESPONSE_ACCEPT,
+                                         GTK_STOCK_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 struct {
+    const char *text;
+    const char *prop;
+    GtkWidget *entry;
+} connect_entries[] = {
+    { .text = N_("Hostname"),   .prop = "host"      },
+    { .text = N_("Port"),       .prop = "port"      },
+    { .text = N_("TLS Port"),   .prop = "tls-port"  },
+};
+
+#ifndef G_OS_WIN32
+static void recent_selection_changed_dialog_cb(GtkRecentChooser *chooser, gpointer data)
+{
+    GtkRecentInfo *info;
+    gchar *txt = NULL;
+    const gchar *uri;
+    SpiceSession *session = data;
+
+    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);
+
+    g_object_get(session, "host", &txt, NULL);
+    gtk_entry_set_text(GTK_ENTRY(connect_entries[0].entry), txt ? txt : "");
+    g_free(txt);
+
+    g_object_get(session, "port", &txt, NULL);
+    gtk_entry_set_text(GTK_ENTRY(connect_entries[1].entry), txt ? txt : "");
+    g_free(txt);
+
+    g_object_get(session, "tls-port", &txt, NULL);
+    gtk_entry_set_text(GTK_ENTRY(connect_entries[2].entry), txt ? txt : "");
+    g_free(txt);
+
+    gtk_recent_info_unref(info);
+}
+
+static void recent_item_activated_dialog_cb(GtkRecentChooser *chooser, gpointer data)
+{
+   gtk_dialog_response (GTK_DIALOG (data), GTK_RESPONSE_ACCEPT);
+}
+#endif
+
+static int connect_dialog(SpiceSession *session)
+{
+    GtkWidget *dialog, *area, *label;
+    GtkTable *table;
+    int i, retval;
+
+    /* Create the widgets */
+    dialog = gtk_dialog_new_with_buttons(_("Connect to SPICE"),
+                                         NULL,
+                                         GTK_DIALOG_DESTROY_WITH_PARENT,
+                                         GTK_STOCK_CANCEL,
+                                         GTK_RESPONSE_REJECT,
+                                         GTK_STOCK_CONNECT,
+                                         GTK_RESPONSE_ACCEPT,
+                                         NULL);
+    gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
+    area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
+    table = GTK_TABLE(gtk_table_new(3, 2, 0));
+    gtk_box_pack_start(GTK_BOX(area), GTK_WIDGET(table), TRUE, TRUE, 0);
+    gtk_table_set_row_spacings(table, 5);
+    gtk_table_set_col_spacings(table, 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_table_attach_defaults(table, label, 0, 1, i, i+1);
+        connect_entries[i].entry = GTK_WIDGET(gtk_entry_new());
+        gtk_table_attach_defaults(table, connect_entries[i].entry, 1, 2, i, i+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);
+        }
+    }
+
+    label = gtk_label_new("Recent connections:");
+    gtk_box_pack_start(GTK_BOX(area), label, TRUE, TRUE, 0);
+    gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
+#ifndef G_OS_WIN32
+    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(GTK_BOX(area), 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(recent, "item-activated",
+                     G_CALLBACK(recent_item_activated_dialog_cb), dialog);
+#endif
+    /* show and wait for response */
+    gtk_widget_show_all(dialog);
+    if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
+        for (i = 0; i < SPICE_N_ELEMENTS(connect_entries); i++) {
+            const gchar *txt;
+            txt = gtk_entry_get_text(GTK_ENTRY(connect_entries[i].entry));
+            g_object_set(session, connect_entries[i].prop, txt, NULL);
+        }
+        retval = 0;
+    } else
+        retval = -1;
+    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
+
+#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,
+                    GTK_STOCK_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",  GTK_STOCK_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        = "HelpMenu",
+        .label       = "_Help",
+    },{
+
+        /* File menu */
+        .name        = "Connect",
+        .stock_id    = GTK_STOCK_CONNECT,
+        .label       = N_("_Connect ..."),
+        .callback    = G_CALLBACK(menu_cb_connect),
+    },{
+        .name        = "Close",
+        .stock_id    = GTK_STOCK_CLOSE,
+        .label       = N_("_Close"),
+        .callback    = G_CALLBACK(menu_cb_close),
+        .accelerator = "", /* none (disable default "<control>W") */
+    },{
+
+        /* Edit menu */
+        .name        = "CopyToGuest",
+        .stock_id    = GTK_STOCK_COPY,
+        .label       = N_("_Copy to guest"),
+        .callback    = G_CALLBACK(menu_cb_copy),
+        .accelerator = "", /* none (disable default "<control>C") */
+    },{
+        .name        = "PasteFromGuest",
+        .stock_id    = GTK_STOCK_PASTE,
+        .label       = N_("_Paste from guest"),
+        .callback    = G_CALLBACK(menu_cb_paste),
+        .accelerator = "", /* none (disable default "<control>V") */
+    },{
+
+        /* View menu */
+        .name        = "Fullscreen",
+        .stock_id    = GTK_STOCK_FULLSCREEN,
+        .label       = N_("_Fullscreen"),
+        .callback    = G_CALLBACK(menu_cb_fullscreen),
+        .accelerator = "<shift>F11",
+    },{
+#ifdef USE_SMARTCARD
+	.name        = "InsertSmartcard",
+	.label       = N_("_Insert Smartcard"),
+	.callback    = G_CALLBACK(menu_cb_insert_smartcard),
+        .accelerator = "<shift>F8",
+    },{
+	.name        = "RemoveSmartcard",
+	.label       = N_("_Remove Smartcard"),
+	.callback    = G_CALLBACK(menu_cb_remove_smartcard),
+        .accelerator = "<shift>F9",
+    },{
+#endif
+
+#ifdef USE_USBREDIR
+        .name        = "SelectUsbDevices",
+        .label       = N_("_Select USB Devices for redirection"),
+        .callback    = G_CALLBACK(menu_cb_select_usb_devices),
+        .accelerator = "<shift>F10",
+    },{
+#endif
+
+        /* Help menu */
+        .name        = "About",
+        .stock_id    = GTK_STOCK_ABOUT,
+        .label       = N_("_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",
+};
+
+static const GtkToggleActionEntry tentries[] = {
+    {
+        .name        = "grab-keyboard",
+        .label       = N_("Grab keyboard when active and focused"),
+        .callback    = G_CALLBACK(menu_cb_bool_prop),
+    },{
+        .name        = "grab-mouse",
+        .label       = N_("Grab mouse in server mode (no tabled/vdagent)"),
+        .callback    = G_CALLBACK(menu_cb_bool_prop),
+    },{
+        .name        = "resize-guest",
+        .label       = N_("Resize guest to match window size"),
+        .callback    = G_CALLBACK(menu_cb_bool_prop),
+    },{
+        .name        = "scaling",
+        .label       = N_("Scale display"),
+        .callback    = G_CALLBACK(menu_cb_bool_prop),
+    },{
+        .name        = "disable-inputs",
+        .label       = N_("Disable inputs"),
+        .callback    = G_CALLBACK(menu_cb_bool_prop),
+    },{
+        .name        = "auto-clipboard",
+        .label       = N_("Automagic clipboard sharing between host and guest"),
+        .callback    = G_CALLBACK(menu_cb_bool_prop),
+    },{
+        .name        = "auto-usbredir",
+        .label       = N_("Auto redirect newly plugged in USB devices"),
+        .callback    = G_CALLBACK(menu_cb_bool_prop),
+    },{
+        .name        = "Statusbar",
+        .label       = N_("Statusbar"),
+        .callback    = G_CALLBACK(menu_cb_statusbar),
+    },{
+        .name        = "Toolbar",
+        .label       = N_("Toolbar"),
+        .callback    = G_CALLBACK(menu_cb_toolbar),
+    }
+};
+
+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='resize-guest'/>\n"
+"      <menuitem action='scaling'/>\n"
+"      <menuitem action='disable-inputs'/>\n"
+"      <menuitem action='auto-clipboard'/>\n"
+"      <menuitem action='auto-usbredir'/>\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;
+}
+
+#ifndef G_OS_WIN32
+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);
+}
+#endif
+
+static gboolean configure_event_cb(GtkWidget         *widget,
+                                   GdkEventConfigure *event,
+                                   gpointer           data)
+{
+    gboolean resize_guest;
+    SpiceWindow *win = data;
+
+    g_return_val_if_fail(win != NULL, FALSE);
+    g_return_val_if_fail(win->conn != NULL, FALSE);
+
+    g_object_get(win->spice, "resize-guest", &resize_guest, NULL);
+    if (resize_guest && win->conn->agent_connected)
+        return FALSE;
+
+    return FALSE;
+}
+
+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_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");
+
+#ifndef G_OS_WIN32
+    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);
+#endif
+
+    /* spice display */
+    win->spice = GTK_WIDGET(spice_display_new_with_monitor(conn->session, id, monitor_id));
+    g_signal_connect(win->spice, "configure-event", G_CALLBACK(configure_event_cb), win);
+    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 */
+#if GTK_CHECK_VERSION(3,0,0)
+    win->statusbar = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 1);
+#else
+    win->statusbar = gtk_hbox_new(FALSE, 1);
+#endif
+
+    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 */
+#if GTK_CHECK_VERSION(3,0,0)
+    vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 1);
+#else
+    vbox = gtk_vbox_new(FALSE, 1);
+#endif
+    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);
+        }
+
+        rc = connect_dialog(conn->session);
+        if (rc == 0) {
+            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: %d", 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 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) {
+            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);
+    }
+}
+
+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);
+        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);
+    }
+
+    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);
+    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      = N_("Open in full screen mode"),
+    },{
+        .long_name        = "version",
+        .arg              = G_OPTION_ARG_NONE,
+        .arg_data         = &version,
+        .description      = N_("Display version and quit"),
+    },{
+        .long_name        = "title",
+        .arg              = G_OPTION_ARG_STRING,
+        .arg_data         = &spicy_title,
+        .description      = N_("Set the window title"),
+        .arg_description  = N_("<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
+    static struct termios saved_tios;
+    struct termios tios;
+
+    if (reset)
+        tios = saved_tios;
+    else {
+        tcgetattr(stdinfd, &tios);
+        saved_tios = tios;
+        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;
+
+#if !GLIB_CHECK_VERSION(2,31,18)
+    g_thread_init(NULL);
+#endif
+    bindtextdomain(GETTEXT_PACKAGE, SPICE_GTK_LOCALEDIR);
+    bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
+    textdomain(GETTEXT_PACKAGE);
+
+    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);
+    }
+
+#if !GLIB_CHECK_VERSION(2,36,0)
+    g_type_init();
+#endif
+    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) {
+        int ret = connect_dialog(conn->session);
+        if (ret != 0) {
+            exit(0);
+        }
+    }
+    g_free(host);
+    g_free(port);
+    g_free(tls_port);
+    g_free(unix_path);
+
+    watch_stdin();
+
+    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/src/usb-acl-helper.c b/src/usb-acl-helper.c
new file mode 100644
index 0000000..6a49627
--- /dev/null
+++ b/src/usb-acl-helper.c
@@ -0,0 +1,299 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2011 Red Hat, Inc.
+
+   Red Hat Authors:
+   Hans de Goede <hdegoede at redhat.com>
+
+   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 <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "usb-acl-helper.h"
+#include "glib-compat.h"
+
+/* ------------------------------------------------------------------ */
+/* gobject glue                                                       */
+
+#define SPICE_USB_ACL_HELPER_GET_PRIVATE(obj)                                  \
+    (G_TYPE_INSTANCE_GET_PRIVATE ((obj), SPICE_TYPE_USB_ACL_HELPER, SpiceUsbAclHelperPrivate))
+
+struct _SpiceUsbAclHelperPrivate {
+    GSimpleAsyncResult *result;
+    GIOChannel *in_ch;
+    GIOChannel *out_ch;
+    GCancellable *cancellable;
+    gulong cancellable_id;
+};
+
+G_DEFINE_TYPE(SpiceUsbAclHelper, spice_usb_acl_helper, G_TYPE_OBJECT);
+
+static void spice_usb_acl_helper_init(SpiceUsbAclHelper *self)
+{
+    self->priv = SPICE_USB_ACL_HELPER_GET_PRIVATE(self);
+}
+
+static void spice_usb_acl_helper_cleanup(SpiceUsbAclHelper *self)
+{
+    SpiceUsbAclHelperPrivate *priv = self->priv;
+
+    g_cancellable_disconnect(priv->cancellable, priv->cancellable_id);
+    priv->cancellable = NULL;
+    priv->cancellable_id = 0;
+
+    g_clear_object(&priv->result);
+
+    if (priv->in_ch) {
+        g_io_channel_unref(priv->in_ch);
+        priv->in_ch = NULL;
+    }
+
+    if (priv->out_ch) {
+        g_io_channel_unref(priv->out_ch);
+        priv->out_ch = NULL;
+    }
+}
+
+static void spice_usb_acl_helper_finalize(GObject *gobject)
+{
+    spice_usb_acl_helper_cleanup(SPICE_USB_ACL_HELPER(gobject));
+
+    if (G_OBJECT_CLASS(spice_usb_acl_helper_parent_class)->finalize)
+        G_OBJECT_CLASS(spice_usb_acl_helper_parent_class)->finalize(gobject);
+}
+
+static void spice_usb_acl_helper_class_init(SpiceUsbAclHelperClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+    gobject_class->finalize     = spice_usb_acl_helper_finalize;
+
+    g_type_class_add_private(klass, sizeof(SpiceUsbAclHelperPrivate));
+}
+
+/* ------------------------------------------------------------------ */
+/* callbacks                                                          */
+
+static void async_result_set_cancelled(GSimpleAsyncResult *result)
+{
+    g_simple_async_result_set_error(result,
+                G_IO_ERROR, G_IO_ERROR_CANCELLED,
+                "Setting USB device node ACL cancelled");
+}
+
+static gboolean cb_out_watch(GIOChannel    *channel,
+                             GIOCondition   cond,
+                             gpointer      *user_data)
+{
+    SpiceUsbAclHelper *self = SPICE_USB_ACL_HELPER(user_data);
+    SpiceUsbAclHelperPrivate *priv = self->priv;
+    gboolean success = FALSE;
+    GError *err = NULL;
+    GIOStatus status;
+    gchar *string;
+    gsize size;
+
+    /* Check that we've not been cancelled */
+    if (priv->result == NULL)
+        goto done;
+
+    g_return_val_if_fail(channel == priv->out_ch, FALSE);
+
+    status = g_io_channel_read_line(priv->out_ch, &string, &size, NULL, &err);
+    switch (status) {
+        case G_IO_STATUS_NORMAL:
+            string[strlen(string) - 1] = 0;
+            if (!strcmp(string, "SUCCESS")) {
+                success = TRUE;
+            } else if (!strcmp(string, "CANCELED")) {
+                async_result_set_cancelled(priv->result);
+            } else {
+                g_simple_async_result_set_error(priv->result,
+                            SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                            "Error setting USB device node ACL: '%s'",
+                            string);
+            }
+            g_free(string);
+            break;
+        case G_IO_STATUS_ERROR:
+            g_simple_async_result_take_error(priv->result, err);
+            break;
+        case G_IO_STATUS_EOF:
+            g_simple_async_result_set_error(priv->result,
+                        SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                        "Unexpected EOF reading from acl helper stdout");
+            break;
+        case G_IO_STATUS_AGAIN:
+            return TRUE; /* Wait for more input */
+    }
+
+    g_cancellable_disconnect(priv->cancellable, priv->cancellable_id);
+    priv->cancellable = NULL;
+    priv->cancellable_id = 0;
+
+    g_simple_async_result_complete_in_idle(priv->result);
+    g_clear_object(&priv->result);
+
+    if (!success)
+        spice_usb_acl_helper_cleanup(self);
+
+done:
+    g_object_unref(self);
+    return FALSE;
+}
+
+static void cancelled_cb(GCancellable *cancellable, gpointer user_data)
+{
+    SpiceUsbAclHelper *self = SPICE_USB_ACL_HELPER(user_data);
+
+    spice_usb_acl_helper_close_acl(self);
+}
+
+static void helper_child_watch_cb(GPid pid, gint status, gpointer user_data)
+{
+    /* Nothing to do, but we need the child watch to avoid zombies */
+}
+
+/* ------------------------------------------------------------------ */
+/* private api                                                        */
+
+G_GNUC_INTERNAL
+SpiceUsbAclHelper *spice_usb_acl_helper_new(void)
+{
+    GObject *obj;
+
+    obj = g_object_new(SPICE_TYPE_USB_ACL_HELPER, NULL);
+
+    return SPICE_USB_ACL_HELPER(obj);
+}
+
+G_GNUC_INTERNAL
+void spice_usb_acl_helper_open_acl(SpiceUsbAclHelper *self,
+                                   gint busnum, gint devnum,
+                                   GCancellable *cancellable,
+                                   GAsyncReadyCallback callback,
+                                   gpointer user_data)
+{
+    g_return_if_fail(SPICE_IS_USB_ACL_HELPER(self));
+
+    SpiceUsbAclHelperPrivate *priv = self->priv;
+    GSimpleAsyncResult *result;
+    GError *err = NULL;
+    GIOStatus status;
+    GPid helper_pid;
+    gsize bytes_written;
+    gchar *argv[] = { (char*) ACL_HELPER_PATH"/spice-client-glib-usb-acl-helper", NULL };
+    gint in, out;
+    gchar buf[128];
+
+    result = g_simple_async_result_new(G_OBJECT(self), callback, user_data,
+                                       spice_usb_acl_helper_open_acl);
+
+    if (priv->out_ch) {
+        g_simple_async_result_set_error(result,
+                            SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                            "Error acl-helper already has an acl open");
+        goto done;
+    }
+
+    if (g_cancellable_set_error_if_cancelled(cancellable, &err)) {
+        g_simple_async_result_take_error(result, err);
+        goto done;
+    }
+
+    if (!g_spawn_async_with_pipes(NULL, argv, NULL,
+                           G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH,
+                           NULL, NULL, &helper_pid, &in, &out, NULL, &err)) {
+        g_simple_async_result_take_error(result, err);
+        goto done;
+    }
+    g_child_watch_add(helper_pid, helper_child_watch_cb, NULL);
+
+    priv->in_ch = g_io_channel_unix_new(in);
+    g_io_channel_set_close_on_unref(priv->in_ch, TRUE);
+
+    priv->out_ch = g_io_channel_unix_new(out);
+    g_io_channel_set_close_on_unref(priv->out_ch, TRUE);
+    status = g_io_channel_set_flags(priv->out_ch, G_IO_FLAG_NONBLOCK, &err);
+    if (status != G_IO_STATUS_NORMAL) {
+        g_simple_async_result_take_error(result, err);
+        goto done;
+    }
+
+    snprintf(buf, sizeof(buf), "%d %d\n", busnum, devnum);
+    status = g_io_channel_write_chars(priv->in_ch, buf, -1,
+                                      &bytes_written, &err);
+    if (status != G_IO_STATUS_NORMAL) {
+        g_simple_async_result_take_error(result, err);
+        goto done;
+    }
+    status = g_io_channel_flush(priv->in_ch, &err);
+    if (status != G_IO_STATUS_NORMAL) {
+        g_simple_async_result_take_error(result, err);
+        goto done;
+    }
+
+    priv->result = result;
+    if (cancellable) {
+        priv->cancellable = cancellable;
+        priv->cancellable_id = g_cancellable_connect(cancellable,
+                                                     G_CALLBACK(cancelled_cb),
+                                                     self, NULL);
+    }
+    g_io_add_watch(priv->out_ch, G_IO_IN|G_IO_HUP,
+                   (GIOFunc)cb_out_watch, g_object_ref(self));
+    return;
+
+done:
+    spice_usb_acl_helper_cleanup(self);
+    g_simple_async_result_complete_in_idle(result);
+    g_object_unref(result);
+}
+
+G_GNUC_INTERNAL
+gboolean spice_usb_acl_helper_open_acl_finish(
+    SpiceUsbAclHelper *self, GAsyncResult *res, GError **err)
+{
+    GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT(res);
+
+    g_return_val_if_fail(g_simple_async_result_is_valid(res, G_OBJECT(self),
+                                               spice_usb_acl_helper_open_acl),
+                         FALSE);
+
+    if (g_simple_async_result_propagate_error(result, err))
+        return FALSE;
+
+    return TRUE;
+}
+
+G_GNUC_INTERNAL
+void spice_usb_acl_helper_close_acl(SpiceUsbAclHelper *self)
+{
+    g_return_if_fail(SPICE_IS_USB_ACL_HELPER(self));
+
+    SpiceUsbAclHelperPrivate *priv = self->priv;
+
+    /* If the acl open has not completed yet report it as cancelled */
+    if (priv->result) {
+        async_result_set_cancelled(priv->result);
+        g_simple_async_result_complete_in_idle(priv->result);
+    }
+
+    spice_usb_acl_helper_cleanup(self);
+}
diff --git a/src/usb-acl-helper.h b/src/usb-acl-helper.h
new file mode 100644
index 0000000..2d41b68
--- /dev/null
+++ b/src/usb-acl-helper.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2011 Red Hat, Inc.
+
+   Red Hat Authors:
+   Hans de Goede <hdegoede at redhat.com>
+
+   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_USB_ACL_HELPER_H__
+#define __SPICE_USB_ACL_HELPER_H__
+
+#include "spice-client.h"
+#include <gio/gio.h>
+
+/* Note the entire usb-acl-helper class is private to spice-client-glib !! */
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_USB_ACL_HELPER            (spice_usb_acl_helper_get_type ())
+#define SPICE_USB_ACL_HELPER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), SPICE_TYPE_USB_ACL_HELPER, SpiceUsbAclHelper))
+#define SPICE_USB_ACL_HELPER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SPICE_TYPE_USB_ACL_HELPER, SpiceUsbAclHelperClass))
+#define SPICE_IS_USB_ACL_HELPER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SPICE_TYPE_USB_ACL_HELPER))
+#define SPICE_IS_USB_ACL_HELPER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SPICE_TYPE_USB_ACL_HELPER))
+#define SPICE_USB_ACL_HELPER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SPICE_TYPE_USB_ACL_HELPER, SpiceUsbAclHelperClass))
+
+typedef struct _SpiceUsbAclHelper SpiceUsbAclHelper;
+typedef struct _SpiceUsbAclHelperClass SpiceUsbAclHelperClass;
+typedef struct _SpiceUsbAclHelperPrivate SpiceUsbAclHelperPrivate;
+
+struct _SpiceUsbAclHelper
+{
+    GObject parent;
+
+    /*< private >*/
+    SpiceUsbAclHelperPrivate *priv;
+    /* Do not add fields to this struct */
+};
+
+struct _SpiceUsbAclHelperClass
+{
+    GObjectClass parent_class;
+};
+
+GType spice_usb_acl_helper_get_type(void);
+
+SpiceUsbAclHelper *spice_usb_acl_helper_new(void);
+
+void spice_usb_acl_helper_open_acl(SpiceUsbAclHelper *self,
+                                   gint busnum, gint devnum,
+                                   GCancellable *cancellable,
+                                   GAsyncReadyCallback callback,
+                                   gpointer user_data);
+gboolean spice_usb_acl_helper_open_acl_finish(
+    SpiceUsbAclHelper *self, GAsyncResult *res, GError **err);
+
+void spice_usb_acl_helper_close_acl(SpiceUsbAclHelper *self);
+
+G_END_DECLS
+
+#endif /* __SPICE_USB_ACL_HELPER_H__ */
diff --git a/src/usb-device-manager-priv.h b/src/usb-device-manager-priv.h
new file mode 100644
index 0000000..b6fa9c9
--- /dev/null
+++ b/src/usb-device-manager-priv.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2011,2012 Red Hat, Inc.
+
+   Red Hat Authors:
+   Hans de Goede <hdegoede at redhat.com>
+
+   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_USB_DEVICE_MANAGER_PRIV_H__
+#define __SPICE_USB_DEVICE_MANAGER_PRIV_H__
+
+#include "usb-device-manager.h"
+
+G_BEGIN_DECLS
+
+gboolean spice_usb_device_manager_start_event_listening(
+    SpiceUsbDeviceManager *manager, GError **err);
+
+void spice_usb_device_manager_stop_event_listening(
+    SpiceUsbDeviceManager *manager);
+
+#ifdef USE_USBREDIR
+#include <libusb.h>
+void spice_usb_device_manager_device_error(
+    SpiceUsbDeviceManager *manager, SpiceUsbDevice *device, GError *err);
+
+guint8 spice_usb_device_get_busnum(const SpiceUsbDevice *device);
+guint8 spice_usb_device_get_devaddr(const SpiceUsbDevice *device);
+guint16 spice_usb_device_get_vid(const SpiceUsbDevice *device);
+guint16 spice_usb_device_get_pid(const SpiceUsbDevice *device);
+
+#endif
+
+G_END_DECLS
+
+#endif /* __SPICE_USB_DEVICE_MANAGER_PRIV_H__ */
diff --git a/src/usb-device-manager.c b/src/usb-device-manager.c
new file mode 100644
index 0000000..7aa60c4
--- /dev/null
+++ b/src/usb-device-manager.c
@@ -0,0 +1,1932 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2011, 2012 Red Hat, Inc.
+
+   Red Hat Authors:
+   Hans de Goede <hdegoede at redhat.com>
+
+   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-object.h>
+
+#include "glib-compat.h"
+
+#ifdef USE_USBREDIR
+#include <errno.h>
+#include <libusb.h>
+
+#if defined(USE_GUDEV)
+#include <gudev/gudev.h>
+#elif defined(G_OS_WIN32)
+#include "win-usb-dev.h"
+#include "win-usb-driver-install.h"
+#define USE_GUDEV /* win-usb-dev.h provides a fake gudev interface */
+#elif !defined USE_LIBUSB_HOTPLUG
+#error "Expecting one of USE_GUDEV or USE_LIBUSB_HOTPLUG to be defined"
+#endif
+
+#include "channel-usbredir-priv.h"
+#include "usbredirhost.h"
+#include "usbutil.h"
+#endif
+
+#include "spice-session-priv.h"
+#include "spice-client.h"
+#include "spice-marshal.h"
+#include "usb-device-manager-priv.h"
+
+#include <glib/gi18n.h>
+
+#ifndef G_OS_WIN32 /* Linux -- device id is bus.addr */
+#define DEV_ID_FMT "at %d.%d"
+#else /* Windows -- device id is vid:pid */
+#define DEV_ID_FMT "0x%04x:0x%04x"
+#endif
+
+/**
+ * SECTION:usb-device-manager
+ * @short_description: USB device management
+ * @title: Spice USB Manager
+ * @section_id:
+ * @see_also:
+ * @stability: Stable
+ * @include: usb-device-manager.h
+ *
+ * #SpiceUsbDeviceManager monitors USB redirection channels and USB
+ * devices plugging/unplugging. If #SpiceUsbDeviceManager:auto-connect
+ * is set to %TRUE, it will automatically connect newly plugged USB
+ * devices to available channels.
+ *
+ * There should always be a 1:1 relation between #SpiceUsbDeviceManager objects
+ * and #SpiceSession objects. Therefor there is no
+ * spice_usb_device_manager_new, instead there is
+ * spice_usb_device_manager_get() which ensures this 1:1 relation.
+ */
+
+/* ------------------------------------------------------------------ */
+/* gobject glue                                                       */
+
+#define SPICE_USB_DEVICE_MANAGER_GET_PRIVATE(obj)                                  \
+    (G_TYPE_INSTANCE_GET_PRIVATE ((obj), SPICE_TYPE_USB_DEVICE_MANAGER, SpiceUsbDeviceManagerPrivate))
+
+enum {
+    PROP_0,
+    PROP_SESSION,
+    PROP_AUTO_CONNECT,
+    PROP_AUTO_CONNECT_FILTER,
+    PROP_REDIRECT_ON_CONNECT,
+};
+
+enum
+{
+    DEVICE_ADDED,
+    DEVICE_REMOVED,
+    AUTO_CONNECT_FAILED,
+    DEVICE_ERROR,
+    LAST_SIGNAL,
+};
+
+struct _SpiceUsbDeviceManagerPrivate {
+    SpiceSession *session;
+    gboolean auto_connect;
+    gchar *auto_connect_filter;
+    gchar *redirect_on_connect;
+#ifdef USE_USBREDIR
+    libusb_context *context;
+    int event_listeners;
+    GThread *event_thread;
+    gboolean event_thread_run;
+    struct usbredirfilter_rule *auto_conn_filter_rules;
+    struct usbredirfilter_rule *redirect_on_connect_rules;
+    int auto_conn_filter_rules_count;
+    int redirect_on_connect_rules_count;
+#ifdef USE_GUDEV
+    GUdevClient *udev;
+    libusb_device **coldplug_list; /* Avoid needless reprobing during init */
+#else
+    libusb_hotplug_callback_handle hp_handle;
+#endif
+#ifdef G_OS_WIN32
+    SpiceWinUsbDriver     *installer;
+#endif
+#endif
+    GPtrArray *devices;
+    GPtrArray *channels;
+};
+
+enum {
+    SPICE_USB_DEVICE_STATE_NONE = 0, /* this is also DISCONNECTED */
+    SPICE_USB_DEVICE_STATE_CONNECTING,
+    SPICE_USB_DEVICE_STATE_CONNECTED,
+    SPICE_USB_DEVICE_STATE_DISCONNECTING,
+    SPICE_USB_DEVICE_STATE_INSTALLING,
+    SPICE_USB_DEVICE_STATE_UNINSTALLING,
+    SPICE_USB_DEVICE_STATE_INSTALLED,
+    SPICE_USB_DEVICE_STATE_MAX
+};
+
+#ifdef USE_USBREDIR
+
+typedef struct _SpiceUsbDeviceInfo {
+    guint8  busnum;
+    guint8  devaddr;
+    guint16 vid;
+    guint16 pid;
+#ifdef G_OS_WIN32
+    guint8  state;
+#else
+    libusb_device *libdev;
+#endif
+    gint    ref;
+} SpiceUsbDeviceInfo;
+
+
+static void channel_new(SpiceSession *session, SpiceChannel *channel,
+                        gpointer user_data);
+static void channel_destroy(SpiceSession *session, SpiceChannel *channel,
+                            gpointer user_data);
+#ifdef USE_GUDEV
+static void spice_usb_device_manager_uevent_cb(GUdevClient     *client,
+                                               const gchar     *action,
+                                               GUdevDevice     *udevice,
+                                               gpointer         user_data);
+static void spice_usb_device_manager_add_udev(SpiceUsbDeviceManager  *self,
+                                              GUdevDevice            *udev);
+#else
+static int spice_usb_device_manager_hotplug_cb(libusb_context       *ctx,
+                                               libusb_device        *device,
+                                               libusb_hotplug_event  event,
+                                               void                 *data);
+#endif
+static void spice_usb_device_manager_check_redir_on_connect(
+    SpiceUsbDeviceManager *self, SpiceChannel *channel);
+
+static SpiceUsbDeviceInfo *spice_usb_device_new(libusb_device *libdev);
+static SpiceUsbDevice *spice_usb_device_ref(SpiceUsbDevice *device);
+static void spice_usb_device_unref(SpiceUsbDevice *device);
+
+#ifdef G_OS_WIN32
+static guint8 spice_usb_device_get_state(SpiceUsbDevice *device);
+static void  spice_usb_device_set_state(SpiceUsbDevice *device, guint8 s);
+#endif
+
+static gboolean spice_usb_device_equal_libdev(SpiceUsbDevice *device,
+                                              libusb_device *libdev);
+static libusb_device *
+spice_usb_device_manager_device_to_libdev(SpiceUsbDeviceManager *self,
+                                          SpiceUsbDevice *device);
+
+static void
+_spice_usb_device_manager_connect_device_async(SpiceUsbDeviceManager *self,
+                                               SpiceUsbDevice *device,
+                                               GCancellable *cancellable,
+                                               GAsyncReadyCallback callback,
+                                               gpointer user_data);
+
+G_DEFINE_BOXED_TYPE(SpiceUsbDevice, spice_usb_device,
+                    (GBoxedCopyFunc)spice_usb_device_ref,
+                    (GBoxedFreeFunc)spice_usb_device_unref)
+
+#else
+G_DEFINE_BOXED_TYPE(SpiceUsbDevice, spice_usb_device, g_object_ref, g_object_unref)
+#endif
+
+static void spice_usb_device_manager_initable_iface_init(GInitableIface *iface);
+
+#ifdef USE_USBREDIR
+#ifdef G_OS_WIN32
+static void spice_usb_device_manager_drv_install_cb(GObject *gobject,
+                                                    GAsyncResult *res,
+                                                    gpointer user_data);
+#endif
+#endif
+
+static guint signals[LAST_SIGNAL] = { 0, };
+
+G_DEFINE_TYPE_WITH_CODE(SpiceUsbDeviceManager, spice_usb_device_manager, G_TYPE_OBJECT,
+     G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, spice_usb_device_manager_initable_iface_init));
+
+static void spice_usb_device_manager_init(SpiceUsbDeviceManager *self)
+{
+    SpiceUsbDeviceManagerPrivate *priv;
+
+    priv = SPICE_USB_DEVICE_MANAGER_GET_PRIVATE(self);
+    self->priv = priv;
+
+    priv->channels = g_ptr_array_new();
+#ifdef USE_USBREDIR
+    priv->devices  = g_ptr_array_new_with_free_func((GDestroyNotify)
+                                                    spice_usb_device_unref);
+#endif
+}
+
+static gboolean spice_usb_device_manager_initable_init(GInitable  *initable,
+                                                    GCancellable  *cancellable,
+                                                    GError        **err)
+{
+    SpiceUsbDeviceManager *self;
+    SpiceUsbDeviceManagerPrivate *priv;
+#ifdef USE_USBREDIR
+    GList *list;
+    GList *it;
+    int rc;
+#ifdef USE_GUDEV
+    const gchar *const subsystems[] = {"usb", NULL};
+#endif
+#endif
+
+    g_return_val_if_fail(SPICE_IS_USB_DEVICE_MANAGER(initable), FALSE);
+    g_return_val_if_fail(err == NULL || *err == NULL, FALSE);
+
+    if (cancellable != NULL) {
+        g_set_error_literal(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                            "Cancellable initialization not supported");
+        return FALSE;
+    }
+
+    self = SPICE_USB_DEVICE_MANAGER(initable);
+    priv = self->priv;
+
+    if (!priv->session) {
+        g_set_error_literal(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                "SpiceUsbDeviceManager constructed without a session");
+        return FALSE;
+    }
+
+#ifdef USE_USBREDIR
+    /* Initialize libusb */
+    rc = libusb_init(&priv->context);
+    if (rc < 0) {
+        const char *desc = spice_usbutil_libusb_strerror(rc);
+        g_warning("Error initializing USB support: %s [%i]", desc, rc);
+        g_set_error(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                    "Error initializing USB support: %s [%i]", desc, rc);
+        return FALSE;
+    }
+
+    /* Start listening for usb devices plug / unplug */
+#ifdef USE_GUDEV
+    priv->udev = g_udev_client_new(subsystems);
+    g_signal_connect(G_OBJECT(priv->udev), "uevent",
+                     G_CALLBACK(spice_usb_device_manager_uevent_cb), self);
+    /* Do coldplug (detection of already connected devices) */
+    libusb_get_device_list(priv->context, &priv->coldplug_list);
+    list = g_udev_client_query_by_subsystem(priv->udev, "usb");
+    for (it = g_list_first(list); it; it = g_list_next(it)) {
+        spice_usb_device_manager_add_udev(self, it->data);
+        g_object_unref(it->data);
+    }
+    g_list_free(list);
+    libusb_free_device_list(priv->coldplug_list, 1);
+    priv->coldplug_list = NULL;
+#else
+    rc = libusb_hotplug_register_callback(priv->context,
+        LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT,
+        LIBUSB_HOTPLUG_ENUMERATE, LIBUSB_HOTPLUG_MATCH_ANY,
+        LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_MATCH_ANY,
+        spice_usb_device_manager_hotplug_cb, self, &priv->hp_handle);
+    if (rc < 0) {
+        const char *desc = spice_usbutil_libusb_strerror(rc);
+        g_warning("Error initializing USB hotplug support: %s [%i]", desc, rc);
+        g_set_error(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                  "Error initializing USB hotplug support: %s [%i]", desc, rc);
+        return FALSE;
+    }
+    spice_usb_device_manager_start_event_listening(self, NULL);
+#endif
+
+    /* Start listening for usb channels connect/disconnect */
+    spice_g_signal_connect_object(priv->session, "channel-new", G_CALLBACK(channel_new), self, G_CONNECT_AFTER);
+    g_signal_connect(priv->session, "channel-destroy",
+                     G_CALLBACK(channel_destroy), self);
+    list = spice_session_get_channels(priv->session);
+    for (it = g_list_first(list); it != NULL; it = g_list_next(it)) {
+        channel_new(priv->session, it->data, (gpointer*)self);
+    }
+    g_list_free(list);
+
+    return TRUE;
+#else
+    g_set_error_literal(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                        _("USB redirection support not compiled in"));
+    return FALSE;
+#endif
+}
+
+static void spice_usb_device_manager_dispose(GObject *gobject)
+{
+#ifdef USE_USBREDIR
+    SpiceUsbDeviceManager *self = SPICE_USB_DEVICE_MANAGER(gobject);
+    SpiceUsbDeviceManagerPrivate *priv = self->priv;
+
+#ifdef USE_LIBUSB_HOTPLUG
+    if (priv->hp_handle) {
+        spice_usb_device_manager_stop_event_listening(self);
+        /* This also wakes up the libusb_handle_events() in the event_thread */
+        libusb_hotplug_deregister_callback(priv->context, priv->hp_handle);
+        priv->hp_handle = 0;
+    }
+#endif
+    if (priv->event_thread && !priv->event_thread_run) {
+        g_thread_join(priv->event_thread);
+        priv->event_thread = NULL;
+    }
+#endif
+
+    /* Chain up to the parent class */
+    if (G_OBJECT_CLASS(spice_usb_device_manager_parent_class)->dispose)
+        G_OBJECT_CLASS(spice_usb_device_manager_parent_class)->dispose(gobject);
+}
+
+static void spice_usb_device_manager_finalize(GObject *gobject)
+{
+    SpiceUsbDeviceManager *self = SPICE_USB_DEVICE_MANAGER(gobject);
+    SpiceUsbDeviceManagerPrivate *priv = self->priv;
+
+    g_ptr_array_unref(priv->channels);
+    if (priv->devices)
+        g_ptr_array_unref(priv->devices);
+
+#ifdef USE_USBREDIR
+#ifdef USE_GUDEV
+    g_clear_object(&priv->udev);
+#endif
+    g_return_if_fail(priv->event_thread == NULL);
+    if (priv->context)
+        libusb_exit(priv->context);
+    free(priv->auto_conn_filter_rules);
+    free(priv->redirect_on_connect_rules);
+#ifdef G_OS_WIN32
+    if (priv->installer)
+        g_object_unref(priv->installer);
+#endif
+#endif
+
+    g_free(priv->auto_connect_filter);
+    g_free(priv->redirect_on_connect);
+
+    /* Chain up to the parent class */
+    if (G_OBJECT_CLASS(spice_usb_device_manager_parent_class)->finalize)
+        G_OBJECT_CLASS(spice_usb_device_manager_parent_class)->finalize(gobject);
+}
+
+static void spice_usb_device_manager_initable_iface_init(GInitableIface *iface)
+{
+    iface->init = spice_usb_device_manager_initable_init;
+}
+
+static void spice_usb_device_manager_get_property(GObject     *gobject,
+                                                  guint        prop_id,
+                                                  GValue      *value,
+                                                  GParamSpec  *pspec)
+{
+    SpiceUsbDeviceManager *self = SPICE_USB_DEVICE_MANAGER(gobject);
+    SpiceUsbDeviceManagerPrivate *priv = self->priv;
+
+    switch (prop_id) {
+    case PROP_SESSION:
+        g_value_set_object(value, priv->session);
+        break;
+    case PROP_AUTO_CONNECT:
+        g_value_set_boolean(value, priv->auto_connect);
+        break;
+    case PROP_AUTO_CONNECT_FILTER:
+        g_value_set_string(value, priv->auto_connect_filter);
+        break;
+    case PROP_REDIRECT_ON_CONNECT:
+        g_value_set_string(value, priv->redirect_on_connect);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
+        break;
+    }
+}
+
+static void spice_usb_device_manager_set_property(GObject       *gobject,
+                                                  guint          prop_id,
+                                                  const GValue  *value,
+                                                  GParamSpec    *pspec)
+{
+    SpiceUsbDeviceManager *self = SPICE_USB_DEVICE_MANAGER(gobject);
+    SpiceUsbDeviceManagerPrivate *priv = self->priv;
+
+    switch (prop_id) {
+    case PROP_SESSION:
+        priv->session = g_value_get_object(value);
+        break;
+    case PROP_AUTO_CONNECT:
+        priv->auto_connect = g_value_get_boolean(value);
+        break;
+    case PROP_AUTO_CONNECT_FILTER: {
+        const gchar *filter = g_value_get_string(value);
+#ifdef USE_USBREDIR
+        struct usbredirfilter_rule *rules;
+        int r, count;
+
+        r = usbredirfilter_string_to_rules(filter, ",", "|", &rules, &count);
+        if (r) {
+            if (r == -ENOMEM)
+                g_error("Failed to allocate memory for auto-connect-filter");
+            g_warning("Error parsing auto-connect-filter string, keeping old filter");
+            break;
+        }
+
+        free(priv->auto_conn_filter_rules);
+        priv->auto_conn_filter_rules = rules;
+        priv->auto_conn_filter_rules_count = count;
+#endif
+        g_free(priv->auto_connect_filter);
+        priv->auto_connect_filter = g_strdup(filter);
+        break;
+    }
+    case PROP_REDIRECT_ON_CONNECT: {
+        const gchar *filter = g_value_get_string(value);
+#ifdef USE_USBREDIR
+        struct usbredirfilter_rule *rules = NULL;
+        int r = 0, count = 0;
+
+        if (filter)
+            r = usbredirfilter_string_to_rules(filter, ",", "|",
+                                               &rules, &count);
+        if (r) {
+            if (r == -ENOMEM)
+                g_error("Failed to allocate memory for redirect-on-connect");
+            g_warning("Error parsing redirect-on-connect string, keeping old filter");
+            break;
+        }
+
+        free(priv->redirect_on_connect_rules);
+        priv->redirect_on_connect_rules = rules;
+        priv->redirect_on_connect_rules_count = count;
+#endif
+        g_free(priv->redirect_on_connect);
+        priv->redirect_on_connect = g_strdup(filter);
+        break;
+    }
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
+        break;
+    }
+}
+
+static void spice_usb_device_manager_class_init(SpiceUsbDeviceManagerClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+    GParamSpec *pspec;
+
+    gobject_class->dispose      = spice_usb_device_manager_dispose;
+    gobject_class->finalize     = spice_usb_device_manager_finalize;
+    gobject_class->get_property = spice_usb_device_manager_get_property;
+    gobject_class->set_property = spice_usb_device_manager_set_property;
+
+    /**
+     * SpiceUsbDeviceManager:session:
+     *
+     * #SpiceSession this #SpiceUsbDeviceManager is associated with
+     *
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_SESSION,
+         g_param_spec_object("session",
+                             "Session",
+                             "SpiceSession",
+                             SPICE_TYPE_SESSION,
+                             G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+                             G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceUsbDeviceManager:auto-connect:
+     *
+     * Set this to TRUE to automatically redirect newly plugged in device.
+     *
+     * Note when #SpiceGtkSession's auto-usbredir property is TRUE, this
+     * property is controlled by #SpiceGtkSession.
+     */
+    pspec = g_param_spec_boolean("auto-connect", "Auto Connect",
+                                 "Auto connect plugged in USB devices",
+                                 FALSE,
+                                 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+    g_object_class_install_property(gobject_class, PROP_AUTO_CONNECT, pspec);
+
+    /**
+     * SpiceUsbDeviceManager:auto-connect-filter:
+     *
+     * Set a string specifying a filter to use to determine which USB devices
+     * to autoconnect when plugged in, a filter consists of one or more rules.
+     * Where each rule has the form of:
+     *
+     * @class, at vendor, at product, at version, at allow
+     *
+     * Use -1 for @class/@vendor/@product/@version to accept any value.
+     *
+     * And the rules themselves are concatenated like this:
+     *
+     * @rule1|@rule2|@rule3
+     *
+     * The default setting filters out HID (class 0x03) USB devices from auto
+     * connect and auto connects anything else. Note the explicit allow rule at
+     * the end, this is necessary since by default all devices without a
+     * matching filter rule will not auto-connect.
+     *
+     * Filter strings in this format can be easily created with the RHEV-M
+     * USB filter editor tool.
+     */
+    pspec = g_param_spec_string("auto-connect-filter", "Auto Connect Filter ",
+               "Filter determining which USB devices to auto connect",
+               "0x03,-1,-1,-1,0|-1,-1,-1,-1,1",
+               G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+    g_object_class_install_property(gobject_class, PROP_AUTO_CONNECT_FILTER,
+                                    pspec);
+
+    /**
+     * SpiceUsbDeviceManager:redirect-on-connect:
+     *
+     * Set a string specifying a filter selecting USB devices to automatically
+     * redirect after a Spice connection has been established.
+     *
+     * See #SpiceUsbDeviceManager:auto-connect-filter for the filter string
+     * format.
+     */
+    pspec = g_param_spec_string("redirect-on-connect", "Redirect on connect",
+               "Filter selecting USB devices to redirect on connect", NULL,
+               G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+    g_object_class_install_property(gobject_class, PROP_REDIRECT_ON_CONNECT,
+                                    pspec);
+
+    /**
+     * SpiceUsbDeviceManager::device-added:
+     * @manager: the #SpiceUsbDeviceManager that emitted the signal
+     * @device: #SpiceUsbDevice boxed object corresponding to the added device
+     *
+     * The #SpiceUsbDeviceManager::device-added signal is emitted whenever
+     * a new USB device has been plugged in.
+     **/
+    signals[DEVICE_ADDED] =
+        g_signal_new("device-added",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_FIRST,
+                     G_STRUCT_OFFSET(SpiceUsbDeviceManagerClass, device_added),
+                     NULL, NULL,
+                     g_cclosure_marshal_VOID__BOXED,
+                     G_TYPE_NONE,
+                     1,
+                     SPICE_TYPE_USB_DEVICE);
+
+    /**
+     * SpiceUsbDeviceManager::device-removed:
+     * @manager: the #SpiceUsbDeviceManager that emitted the signal
+     * @device: #SpiceUsbDevice boxed object corresponding to the removed device
+     *
+     * The #SpiceUsbDeviceManager::device-removed signal is emitted whenever
+     * an USB device has been removed.
+     **/
+    signals[DEVICE_REMOVED] =
+        g_signal_new("device-removed",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_FIRST,
+                     G_STRUCT_OFFSET(SpiceUsbDeviceManagerClass, device_removed),
+                     NULL, NULL,
+                     g_cclosure_marshal_VOID__BOXED,
+                     G_TYPE_NONE,
+                     1,
+                     SPICE_TYPE_USB_DEVICE);
+
+    /**
+     * SpiceUsbDeviceManager::auto-connect-failed:
+     * @manager: the #SpiceUsbDeviceManager that emitted the signal
+     * @device: #SpiceUsbDevice boxed object corresponding to the device which failed to auto connect
+     * @error: #GError describing the reason why the autoconnect failed
+     *
+     * The #SpiceUsbDeviceManager::auto-connect-failed signal is emitted
+     * whenever the auto-connect property is true, and a newly plugged in
+     * device could not be auto-connected.
+     **/
+    signals[AUTO_CONNECT_FAILED] =
+        g_signal_new("auto-connect-failed",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_FIRST,
+                     G_STRUCT_OFFSET(SpiceUsbDeviceManagerClass, auto_connect_failed),
+                     NULL, NULL,
+                     g_cclosure_user_marshal_VOID__BOXED_BOXED,
+                     G_TYPE_NONE,
+                     2,
+                     SPICE_TYPE_USB_DEVICE,
+                     G_TYPE_ERROR);
+
+    /**
+     * SpiceUsbDeviceManager::device-error:
+     * @manager: #SpiceUsbDeviceManager that emitted the signal
+     * @device:  #SpiceUsbDevice boxed object corresponding to the device which has an error
+     * @error:   #GError describing the error
+     *
+     * The #SpiceUsbDeviceManager::device-error signal is emitted whenever an
+     * error happens which causes a device to no longer be available to the
+     * guest.
+     **/
+    signals[DEVICE_ERROR] =
+        g_signal_new("device-error",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_FIRST,
+                     G_STRUCT_OFFSET(SpiceUsbDeviceManagerClass, device_error),
+                     NULL, NULL,
+                     g_cclosure_user_marshal_VOID__BOXED_BOXED,
+                     G_TYPE_NONE,
+                     2,
+                     SPICE_TYPE_USB_DEVICE,
+                     G_TYPE_ERROR);
+
+    g_type_class_add_private(klass, sizeof(SpiceUsbDeviceManagerPrivate));
+}
+
+#ifdef USE_USBREDIR
+
+/* ------------------------------------------------------------------ */
+/* gudev / libusb Helper functions                                    */
+
+#ifdef USE_GUDEV
+static gboolean spice_usb_device_manager_get_udev_bus_n_address(
+    GUdevDevice *udev, int *bus, int *address)
+{
+    const gchar *bus_str, *address_str;
+
+    *bus = *address = 0;
+
+#ifndef G_OS_WIN32
+    bus_str = g_udev_device_get_property(udev, "BUSNUM");
+    address_str = g_udev_device_get_property(udev, "DEVNUM");
+#else /* Windows -- request vid:pid instead */
+    bus_str = g_udev_device_get_property(udev, "VID");
+    address_str = g_udev_device_get_property(udev, "PID");
+#endif
+    if (bus_str)
+        *bus = atoi(bus_str);
+    if (address_str)
+        *address = atoi(address_str);
+
+    return *bus && *address;
+}
+#endif
+
+static gboolean spice_usb_device_manager_get_device_descriptor(
+    libusb_device *libdev,
+    struct libusb_device_descriptor *desc)
+{
+    int errcode;
+    const gchar *errstr;
+
+    g_return_val_if_fail(libdev != NULL, FALSE);
+    g_return_val_if_fail(desc   != NULL, FALSE);
+
+    errcode = libusb_get_device_descriptor(libdev, desc);
+    if (errcode < 0) {
+        int bus, addr;
+
+        bus = libusb_get_bus_number(libdev);
+        addr = libusb_get_device_address(libdev);
+        errstr = spice_usbutil_libusb_strerror(errcode);
+        g_warning("cannot get device descriptor for (%p) %d.%d -- %s(%d)",
+                  libdev, bus, addr, errstr, errcode);
+        return FALSE;
+    }
+    return TRUE;
+}
+
+
+/**
+ * spice_usb_device_get_libusb_device:
+ * @device: #SpiceUsbDevice to get the descriptor information of
+ *
+ * Returns: (transfer none): the %libusb_device associated to %SpiceUsbDevice.
+ *
+ * Since: 0.27
+ **/
+gconstpointer
+spice_usb_device_get_libusb_device(const SpiceUsbDevice *device G_GNUC_UNUSED)
+{
+#ifdef USE_USBREDIR
+#ifndef G_OS_WIN32
+    const SpiceUsbDeviceInfo *info = (const SpiceUsbDeviceInfo *)device;
+
+    g_return_val_if_fail(info != NULL, FALSE);
+
+    return info->libdev;
+#endif
+#endif
+    return NULL;
+}
+
+static gboolean spice_usb_device_manager_get_libdev_vid_pid(
+    libusb_device *libdev, int *vid, int *pid)
+{
+    struct libusb_device_descriptor desc;
+
+    g_return_val_if_fail(libdev != NULL, FALSE);
+    g_return_val_if_fail(vid != NULL, FALSE);
+    g_return_val_if_fail(pid != NULL, FALSE);
+
+    *vid = *pid = 0;
+
+    if (!spice_usb_device_manager_get_device_descriptor(libdev, &desc)) {
+        return FALSE;
+    }
+    *vid = desc.idVendor;
+    *pid = desc.idProduct;
+
+    return TRUE;
+}
+
+/* ------------------------------------------------------------------ */
+/* callbacks                                                          */
+
+static void channel_new(SpiceSession *session, SpiceChannel *channel,
+                        gpointer user_data)
+{
+    SpiceUsbDeviceManager *self = user_data;
+
+    if (!SPICE_IS_USBREDIR_CHANNEL(channel))
+        return;
+
+    spice_usbredir_channel_set_context(SPICE_USBREDIR_CHANNEL(channel),
+                                       self->priv->context);
+    spice_channel_connect(channel);
+    g_ptr_array_add(self->priv->channels, channel);
+
+    spice_usb_device_manager_check_redir_on_connect(self, channel);
+
+    /*
+     * add a reference to ourself, to make sure the libusb context is
+     * alive as long as the channel is.
+     * TODO: moving to gusb could help here too.
+     */
+    g_object_ref(self);
+    g_object_weak_ref(G_OBJECT(channel), (GWeakNotify)g_object_unref, self);
+}
+
+static void channel_destroy(SpiceSession *session, SpiceChannel *channel,
+                            gpointer user_data)
+{
+    SpiceUsbDeviceManager *self = user_data;
+
+    if (!SPICE_IS_USBREDIR_CHANNEL(channel))
+        return;
+
+    g_ptr_array_remove(self->priv->channels, channel);
+}
+
+static void spice_usb_device_manager_auto_connect_cb(GObject      *gobject,
+                                                     GAsyncResult *res,
+                                                     gpointer      user_data)
+{
+    SpiceUsbDeviceManager *self = SPICE_USB_DEVICE_MANAGER(gobject);
+    SpiceUsbDevice *device = user_data;
+    GError *err = NULL;
+
+    spice_usb_device_manager_connect_device_finish(self, res, &err);
+    if (err) {
+        gchar *desc = spice_usb_device_get_description(device, NULL);
+        g_prefix_error(&err, "Could not auto-redirect %s: ", desc);
+        g_free(desc);
+
+        SPICE_DEBUG("%s", err->message);
+        g_signal_emit(self, signals[AUTO_CONNECT_FAILED], 0, device, err);
+        g_error_free(err);
+    }
+    spice_usb_device_unref(device);
+}
+
+#ifndef G_OS_WIN32 /* match functions for Linux -- match by bus.addr */
+static gboolean
+spice_usb_device_manager_device_match(SpiceUsbDevice *device,
+                                      const int bus, const int address)
+{
+    return (spice_usb_device_get_busnum(device) == bus &&
+            spice_usb_device_get_devaddr(device) == address);
+}
+
+#ifdef USE_GUDEV
+static gboolean
+spice_usb_device_manager_libdev_match(libusb_device *libdev,
+                                      const int bus, const int address)
+{
+    return (libusb_get_bus_number(libdev) == bus &&
+            libusb_get_device_address(libdev) == address);
+}
+#endif
+
+#else /* Win32 -- match functions for Windows -- match by vid:pid */
+static gboolean
+spice_usb_device_manager_device_match(SpiceUsbDevice *device,
+                                      const int vid, const int pid)
+{
+    return (spice_usb_device_get_vid(device) == vid &&
+            spice_usb_device_get_pid(device) == pid);
+}
+
+static gboolean
+spice_usb_device_manager_libdev_match(libusb_device *libdev,
+                                      const int vid, const int pid)
+{
+    int vid2, pid2;
+
+    if (!spice_usb_device_manager_get_libdev_vid_pid(libdev, &vid2, &pid2)) {
+        return FALSE;
+    }
+    return (vid == vid2 && pid == pid2);
+}
+#endif /* of Win32 -- match functions */
+
+static SpiceUsbDevice*
+spice_usb_device_manager_find_device(SpiceUsbDeviceManager *self,
+                                     const int bus, const int address)
+{
+    SpiceUsbDeviceManagerPrivate *priv = self->priv;
+    SpiceUsbDevice *curr, *device = NULL;
+    guint i;
+
+    for (i = 0; i < priv->devices->len; i++) {
+        curr = g_ptr_array_index(priv->devices, i);
+        if (spice_usb_device_manager_device_match(curr, bus, address)) {
+            device = curr;
+            break;
+        }
+    }
+    return device;
+}
+
+static void spice_usb_device_manager_add_dev(SpiceUsbDeviceManager  *self,
+                                             libusb_device          *libdev)
+{
+    SpiceUsbDeviceManagerPrivate *priv = self->priv;
+    struct libusb_device_descriptor desc;
+    SpiceUsbDevice *device;
+
+    if (!spice_usb_device_manager_get_device_descriptor(libdev, &desc))
+        return;
+
+    /* Skip hubs */
+    if (desc.bDeviceClass == LIBUSB_CLASS_HUB)
+        return;
+
+    device = (SpiceUsbDevice*)spice_usb_device_new(libdev);
+    if (!device)
+        return;
+
+    g_ptr_array_add(priv->devices, device);
+
+    if (priv->auto_connect) {
+        gboolean can_redirect, auto_ok;
+
+        can_redirect = spice_usb_device_manager_can_redirect_device(
+                                        self, device, NULL);
+
+        auto_ok = usbredirhost_check_device_filter(
+                            priv->auto_conn_filter_rules,
+                            priv->auto_conn_filter_rules_count,
+                            libdev, 0) == 0;
+
+        if (can_redirect && auto_ok)
+            spice_usb_device_manager_connect_device_async(self,
+                                   device, NULL,
+                                   spice_usb_device_manager_auto_connect_cb,
+                                   spice_usb_device_ref(device));
+    }
+
+    SPICE_DEBUG("device added %p", device);
+    g_signal_emit(self, signals[DEVICE_ADDED], 0, device);
+}
+
+static void spice_usb_device_manager_remove_dev(SpiceUsbDeviceManager *self,
+                                                int bus, int address)
+{
+    SpiceUsbDeviceManagerPrivate *priv = self->priv;
+    SpiceUsbDevice *device;
+
+    device = spice_usb_device_manager_find_device(self, bus, address);
+    if (!device) {
+        g_warning("Could not find USB device to remove " DEV_ID_FMT,
+                  bus, address);
+        return;
+    }
+
+#ifdef G_OS_WIN32
+    const guint8 state = spice_usb_device_get_state(device);
+    if ((state == SPICE_USB_DEVICE_STATE_INSTALLING) ||
+        (state == SPICE_USB_DEVICE_STATE_UNINSTALLING)) {
+        SPICE_DEBUG("skipping " DEV_ID_FMT ". It is un/installing its driver",
+                    bus, address);
+        return;
+    }
+#endif
+
+    spice_usb_device_manager_disconnect_device(self, device);
+
+    SPICE_DEBUG("device removed %p", device);
+    spice_usb_device_ref(device);
+    g_ptr_array_remove(priv->devices, device);
+    g_signal_emit(self, signals[DEVICE_REMOVED], 0, device);
+    spice_usb_device_unref(device);
+}
+
+#ifdef USE_GUDEV
+static void spice_usb_device_manager_add_udev(SpiceUsbDeviceManager  *self,
+                                              GUdevDevice            *udev)
+{
+    SpiceUsbDeviceManagerPrivate *priv = self->priv;
+    libusb_device *libdev = NULL, **dev_list = NULL;
+    SpiceUsbDevice *device;
+    const gchar *devtype;
+    int i, bus, address;
+
+    devtype = g_udev_device_get_property(udev, "DEVTYPE");
+    /* Check if this is a usb device (and not an interface) */
+    if (!devtype || strcmp(devtype, "usb_device"))
+        return;
+
+    if (!spice_usb_device_manager_get_udev_bus_n_address(udev, &bus, &address)) {
+        g_warning("USB device without bus number or device address");
+        return;
+    }
+
+    device = spice_usb_device_manager_find_device(self, bus, address);
+    if (device) {
+        SPICE_DEBUG("USB device 0x%04x:0x%04x at %d.%d already exists, ignored",
+                    spice_usb_device_get_vid(device),
+                    spice_usb_device_get_pid(device),
+                    spice_usb_device_get_busnum(device),
+                    spice_usb_device_get_devaddr(device));
+        return;
+    }
+
+    if (priv->coldplug_list)
+        dev_list = priv->coldplug_list;
+    else
+        libusb_get_device_list(priv->context, &dev_list);
+
+    for (i = 0; dev_list && dev_list[i]; i++) {
+        if (spice_usb_device_manager_libdev_match(dev_list[i], bus, address)) {
+            libdev = dev_list[i];
+            break;
+        }
+    }
+
+    if (libdev)
+        spice_usb_device_manager_add_dev(self, libdev);
+    else
+        g_warning("Could not find USB device to add " DEV_ID_FMT,
+                  bus, address);
+
+    if (!priv->coldplug_list)
+        libusb_free_device_list(dev_list, 1);
+}
+
+static void spice_usb_device_manager_remove_udev(SpiceUsbDeviceManager  *self,
+                                                 GUdevDevice            *udev)
+{
+    int bus, address;
+
+    if (!spice_usb_device_manager_get_udev_bus_n_address(udev, &bus, &address))
+        return;
+
+    spice_usb_device_manager_remove_dev(self, bus, address);
+}
+
+static void spice_usb_device_manager_uevent_cb(GUdevClient     *client,
+                                               const gchar     *action,
+                                               GUdevDevice     *udevice,
+                                               gpointer         user_data)
+{
+    SpiceUsbDeviceManager *self = SPICE_USB_DEVICE_MANAGER(user_data);
+
+    if (g_str_equal(action, "add"))
+        spice_usb_device_manager_add_udev(self, udevice);
+    else if (g_str_equal (action, "remove"))
+        spice_usb_device_manager_remove_udev(self, udevice);
+}
+#else
+struct hotplug_idle_cb_args {
+    SpiceUsbDeviceManager *self;
+    libusb_device *device;
+    libusb_hotplug_event event;
+};
+
+static gboolean spice_usb_device_manager_hotplug_idle_cb(gpointer user_data)
+{
+    struct hotplug_idle_cb_args *args = user_data;
+    SpiceUsbDeviceManager *self = SPICE_USB_DEVICE_MANAGER(args->self);
+
+    switch (args->event) {
+    case LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED:
+        spice_usb_device_manager_add_dev(self, args->device);
+        break;
+    case LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT:
+        spice_usb_device_manager_remove_dev(self,
+                                    libusb_get_bus_number(args->device),
+                                    libusb_get_device_address(args->device));
+        break;
+    }
+    libusb_unref_device(args->device);
+    g_object_unref(self);
+    g_free(args);
+    return FALSE;
+}
+
+/* Can be called from both the main-thread as well as the event_thread */
+static int spice_usb_device_manager_hotplug_cb(libusb_context       *ctx,
+                                               libusb_device        *device,
+                                               libusb_hotplug_event  event,
+                                               void                 *user_data)
+{
+    SpiceUsbDeviceManager *self = SPICE_USB_DEVICE_MANAGER(user_data);
+    struct hotplug_idle_cb_args *args = g_malloc0(sizeof(*args));
+
+    args->self = g_object_ref(self);
+    args->device = libusb_ref_device(device);
+    args->event = event;
+    g_idle_add(spice_usb_device_manager_hotplug_idle_cb, args);
+    return 0;
+}
+#endif
+
+static void spice_usb_device_manager_channel_connect_cb(
+    GObject *gobject, GAsyncResult *channel_res, gpointer user_data)
+{
+    SpiceUsbredirChannel *channel = SPICE_USBREDIR_CHANNEL(gobject);
+    GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT(user_data);
+    GError *err = NULL;
+
+    spice_usbredir_channel_connect_device_finish(channel, channel_res, &err);
+    if (err) {
+        g_simple_async_result_take_error(result, err);
+    }
+    g_simple_async_result_complete(result);
+    g_object_unref(result);
+}
+
+#ifdef G_OS_WIN32
+
+typedef struct _UsbInstallCbInfo {
+    SpiceUsbDeviceManager *manager;
+    SpiceUsbDevice        *device;
+    SpiceWinUsbDriver     *installer;
+    GCancellable          *cancellable;
+    GAsyncReadyCallback   callback;
+    gpointer              user_data;
+    gboolean              is_install;
+} UsbInstallCbInfo;
+
+/**
+ * spice_usb_device_manager_drv_install_cb:
+ * @gobject: #SpiceWinUsbDriver in charge of installing the driver
+ * @res: #GAsyncResult of async win usb driver installation
+ * @user_data: #SpiceUsbDeviceManager requested the installation
+ *
+ * Called when an Windows libusb driver installation completed.
+ *
+ * If the driver installation was successful, continue with USB
+ * device redirection
+ *
+ * Always call _spice_usb_device_manager_connect_device_async.
+ * When installation fails, libusb_open fails too, but cleanup would be better.
+ */
+static void spice_usb_device_manager_drv_install_cb(GObject *gobject,
+                                                    GAsyncResult *res,
+                                                    gpointer user_data)
+{
+    SpiceUsbDeviceManager *self;
+    SpiceWinUsbDriver *installer;
+    gint status;
+    GError *err = NULL;
+    SpiceUsbDevice *device;
+    UsbInstallCbInfo *cbinfo;
+    GCancellable *cancellable;
+    GAsyncReadyCallback callback;
+    gboolean is_install;
+    const gchar *opstr;
+
+    g_return_if_fail(user_data != NULL);
+
+    cbinfo = user_data;
+    self        = cbinfo->manager;
+    device      = cbinfo->device;
+    installer   = cbinfo->installer;
+    cancellable = cbinfo->cancellable;
+    callback    = cbinfo->callback;
+    user_data   = cbinfo->user_data;
+    is_install  = cbinfo->is_install;
+
+    g_free(cbinfo);
+
+    g_return_if_fail(SPICE_IS_USB_DEVICE_MANAGER(self));
+    g_return_if_fail(SPICE_IS_WIN_USB_DRIVER(installer));
+    g_return_if_fail(device!= NULL);
+
+    opstr = is_install ? "install" : "uninstall";
+    SPICE_DEBUG("Win USB driver %s finished", opstr);
+
+    status = spice_win_usb_driver_install_finish(installer, res, &err);
+
+    spice_usb_device_unref(device);
+
+    if (is_install) {
+        spice_usb_device_set_state(device, SPICE_USB_DEVICE_STATE_INSTALLED);
+    } else {
+        spice_usb_device_set_state(device, SPICE_USB_DEVICE_STATE_NONE);
+    }
+
+    if (err) {
+        g_warning("win usb driver %s failed -- %s", opstr, err->message);
+        g_error_free(err);
+    }
+
+    if (!status) {
+        g_warning("failed to %s win usb driver (status=0)", opstr);
+    }
+
+    if (! is_install) {
+        return;
+    }
+
+    /* device is already ref'ed */
+    _spice_usb_device_manager_connect_device_async(self,
+                                                   device,
+                                                   cancellable,
+                                                   callback,
+                                                   user_data);
+
+}
+#endif
+
+/* ------------------------------------------------------------------ */
+/* private api                                                        */
+
+static gpointer spice_usb_device_manager_usb_ev_thread(gpointer user_data)
+{
+    SpiceUsbDeviceManager *self = SPICE_USB_DEVICE_MANAGER(user_data);
+    SpiceUsbDeviceManagerPrivate *priv = self->priv;
+    int rc;
+
+    while (priv->event_thread_run) {
+        rc = libusb_handle_events(priv->context);
+        if (rc && rc != LIBUSB_ERROR_INTERRUPTED) {
+            const char *desc = spice_usbutil_libusb_strerror(rc);
+            g_warning("Error handling USB events: %s [%i]", desc, rc);
+            break;
+        }
+    }
+
+    return NULL;
+}
+
+gboolean spice_usb_device_manager_start_event_listening(
+    SpiceUsbDeviceManager *self, GError **err)
+{
+    SpiceUsbDeviceManagerPrivate *priv = self->priv;
+
+    g_return_val_if_fail(err == NULL || *err == NULL, FALSE);
+
+    priv->event_listeners++;
+    if (priv->event_listeners > 1)
+        return TRUE;
+
+    /* We don't join the thread when we stop event listening, as the
+       libusb_handle_events call in the thread won't exit until the
+       libusb_close call for the device is made from usbredirhost_close. */
+    if (priv->event_thread) {
+         g_thread_join(priv->event_thread);
+         priv->event_thread = NULL;
+    }
+    priv->event_thread_run = TRUE;
+#if GLIB_CHECK_VERSION(2,31,19)
+    priv->event_thread = g_thread_new("usb_ev_thread",
+                                      spice_usb_device_manager_usb_ev_thread,
+                                      self);
+#else
+    priv->event_thread = g_thread_create(spice_usb_device_manager_usb_ev_thread,
+                                         self, TRUE, err);
+#endif
+    return priv->event_thread != NULL;
+}
+
+void spice_usb_device_manager_stop_event_listening(
+    SpiceUsbDeviceManager *self)
+{
+    SpiceUsbDeviceManagerPrivate *priv = self->priv;
+
+    g_return_if_fail(priv->event_listeners > 0);
+
+    priv->event_listeners--;
+    if (priv->event_listeners == 0)
+        priv->event_thread_run = FALSE;
+}
+
+static void spice_usb_device_manager_check_redir_on_connect(
+    SpiceUsbDeviceManager *self, SpiceChannel *channel)
+{
+    SpiceUsbDeviceManagerPrivate *priv = self->priv;
+    GSimpleAsyncResult *result;
+    SpiceUsbDevice *device;
+    libusb_device *libdev;
+    guint i;
+
+    if (priv->redirect_on_connect == NULL)
+        return;
+
+    for (i = 0; i < priv->devices->len; i++) {
+        device = g_ptr_array_index(priv->devices, i);
+
+        if (spice_usb_device_manager_is_device_connected(self, device))
+            continue;
+
+        libdev = spice_usb_device_manager_device_to_libdev(self, device);
+#ifdef G_OS_WIN32
+        if (libdev == NULL)
+            continue;
+#endif
+        if (usbredirhost_check_device_filter(
+                            priv->redirect_on_connect_rules,
+                            priv->redirect_on_connect_rules_count,
+                            libdev, 0) == 0) {
+            /* Note: re-uses spice_usb_device_manager_connect_device_async's
+               completion handling code! */
+            result = g_simple_async_result_new(G_OBJECT(self),
+                               spice_usb_device_manager_auto_connect_cb,
+                               spice_usb_device_ref(device),
+                               spice_usb_device_manager_connect_device_async);
+            spice_usbredir_channel_connect_device_async(
+                               SPICE_USBREDIR_CHANNEL(channel),
+                               libdev, device, NULL,
+                               spice_usb_device_manager_channel_connect_cb,
+                               result);
+            libusb_unref_device(libdev);
+            return; /* We've taken the channel! */
+        }
+
+        libusb_unref_device(libdev);
+    }
+}
+
+void spice_usb_device_manager_device_error(
+    SpiceUsbDeviceManager *self, SpiceUsbDevice *device, GError *err)
+{
+    g_return_if_fail(SPICE_IS_USB_DEVICE_MANAGER(self));
+    g_return_if_fail(device != NULL);
+
+    g_signal_emit(self, signals[DEVICE_ERROR], 0, device, err);
+}
+#endif
+
+static SpiceUsbredirChannel *spice_usb_device_manager_get_channel_for_dev(
+    SpiceUsbDeviceManager *manager, SpiceUsbDevice *device)
+{
+#ifdef USE_USBREDIR
+    SpiceUsbDeviceManagerPrivate *priv = manager->priv;
+    guint i;
+
+    for (i = 0; i < priv->channels->len; i++) {
+        SpiceUsbredirChannel *channel = g_ptr_array_index(priv->channels, i);
+        libusb_device *libdev = spice_usbredir_channel_get_device(channel);
+        if (spice_usb_device_equal_libdev(device, libdev))
+            return channel;
+    }
+#endif
+    return NULL;
+}
+
+/* ------------------------------------------------------------------ */
+/* public api                                                         */
+
+/**
+ * spice_usb_device_manager_get_devices_with_filter:
+ * @manager: the #SpiceUsbDeviceManager manager
+ * @filter: (allow-none): filter string for selecting which devices to return,
+ *      see #SpiceUsbDeviceManager:auto-connect-filter for the f ilter
+ *      string format
+ *
+ * Returns: (element-type SpiceUsbDevice) (transfer full): a
+ * %GPtrArray array of %SpiceUsbDevice
+ *
+ * Since: 0.20
+ */
+GPtrArray* spice_usb_device_manager_get_devices_with_filter(
+    SpiceUsbDeviceManager *self, const gchar *filter)
+{
+    GPtrArray *devices_copy = NULL;
+
+    g_return_val_if_fail(SPICE_IS_USB_DEVICE_MANAGER(self), NULL);
+
+#ifdef USE_USBREDIR
+    SpiceUsbDeviceManagerPrivate *priv = self->priv;
+    struct usbredirfilter_rule *rules = NULL;;
+    int r, count = 0;
+    guint i;
+
+    if (filter) {
+        r = usbredirfilter_string_to_rules(filter, ",", "|", &rules, &count);
+        if (r) {
+            if (r == -ENOMEM)
+                g_error("Failed to allocate memory for filter");
+            g_warning("Error parsing filter, ignoring");
+            rules = NULL;
+            count = 0;
+        }
+    }
+
+    devices_copy = g_ptr_array_new_with_free_func((GDestroyNotify)
+                                                  spice_usb_device_unref);
+    for (i = 0; i < priv->devices->len; i++) {
+        SpiceUsbDevice *device = g_ptr_array_index(priv->devices, i);
+
+        if (rules) {
+            libusb_device *libdev =
+                spice_usb_device_manager_device_to_libdev(self, device);
+#ifdef G_OS_WIN32
+            if (libdev == NULL)
+                continue;
+#endif
+            if (usbredirhost_check_device_filter(rules, count, libdev, 0) != 0)
+                continue;
+        }
+        g_ptr_array_add(devices_copy, spice_usb_device_ref(device));
+    }
+
+    free(rules);
+#endif
+
+    return devices_copy;
+}
+
+/**
+ * spice_usb_device_manager_get_devices:
+ * @manager: the #SpiceUsbDeviceManager manager
+ *
+ * Returns: (element-type SpiceUsbDevice) (transfer full): a %GPtrArray array of %SpiceUsbDevice
+ */
+GPtrArray* spice_usb_device_manager_get_devices(SpiceUsbDeviceManager *self)
+{
+    return spice_usb_device_manager_get_devices_with_filter(self, NULL);
+}
+
+/**
+ * spice_usb_device_manager_is_device_connected:
+ * @manager: the #SpiceUsbDeviceManager manager
+ * @device: a #SpiceUsbDevice
+ *
+ * Returns: %TRUE if @device has an associated USB redirection channel
+ */
+gboolean spice_usb_device_manager_is_device_connected(SpiceUsbDeviceManager *self,
+                                                      SpiceUsbDevice *device)
+{
+    g_return_val_if_fail(SPICE_IS_USB_DEVICE_MANAGER(self), FALSE);
+    g_return_val_if_fail(device != NULL, FALSE);
+
+    return !!spice_usb_device_manager_get_channel_for_dev(self, device);
+}
+
+/**
+ * spice_usb_device_manager_connect_device_async:
+ * @manager: the #SpiceUsbDeviceManager manager
+ * @device: a #SpiceUsbDevice to redirect
+ * @cancellable: a #GCancellable or NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to callback
+ */
+static void
+_spice_usb_device_manager_connect_device_async(SpiceUsbDeviceManager *self,
+                                               SpiceUsbDevice *device,
+                                               GCancellable *cancellable,
+                                               GAsyncReadyCallback callback,
+                                               gpointer user_data)
+{
+    GSimpleAsyncResult *result;
+
+    g_return_if_fail(SPICE_IS_USB_DEVICE_MANAGER(self));
+    g_return_if_fail(device != NULL);
+
+    SPICE_DEBUG("connecting device %p", device);
+
+    result = g_simple_async_result_new(G_OBJECT(self), callback, user_data,
+                               spice_usb_device_manager_connect_device_async);
+
+#ifdef USE_USBREDIR
+    SpiceUsbDeviceManagerPrivate *priv = self->priv;
+    libusb_device *libdev;
+    guint i;
+
+    if (spice_usb_device_manager_is_device_connected(self, device)) {
+        g_simple_async_result_set_error(result,
+                            SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                            "Cannot connect an already connected usb device");
+        goto done;
+    }
+
+    for (i = 0; i < priv->channels->len; i++) {
+        SpiceUsbredirChannel *channel = g_ptr_array_index(priv->channels, i);
+
+        if (spice_usbredir_channel_get_device(channel))
+            continue; /* Skip already used channels */
+
+        libdev = spice_usb_device_manager_device_to_libdev(self, device);
+#ifdef G_OS_WIN32
+        if (libdev == NULL) {
+            /* Most likely, the device was plugged out at driver installation
+             * time, and its remove-device event was ignored.
+             * So remove the device now
+             */
+            SPICE_DEBUG("libdev does not exist for %p -- removing", device);
+            spice_usb_device_ref(device);
+            g_ptr_array_remove(priv->devices, device);
+            g_signal_emit(self, signals[DEVICE_REMOVED], 0, device);
+            spice_usb_device_unref(device);
+            g_simple_async_result_set_error(result,
+                                            SPICE_CLIENT_ERROR,
+                                            SPICE_CLIENT_ERROR_FAILED,
+                                            _("Device was not found"));
+            goto done;
+        }
+#endif
+        spice_usbredir_channel_connect_device_async(channel,
+                                 libdev,
+                                 device,
+                                 cancellable,
+                                 spice_usb_device_manager_channel_connect_cb,
+                                 result);
+        libusb_unref_device(libdev);
+        return;
+    }
+#endif
+
+    g_simple_async_result_set_error(result,
+                            SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                            _("No free USB channel"));
+#ifdef USE_USBREDIR
+done:
+#endif
+    g_simple_async_result_complete_in_idle(result);
+    g_object_unref(result);
+}
+
+
+void spice_usb_device_manager_connect_device_async(SpiceUsbDeviceManager *self,
+                                             SpiceUsbDevice *device,
+                                             GCancellable *cancellable,
+                                             GAsyncReadyCallback callback,
+                                             gpointer user_data)
+{
+
+#if defined(USE_USBREDIR) && defined(G_OS_WIN32)
+    SpiceWinUsbDriver *installer;
+    UsbInstallCbInfo *cbinfo;
+
+    spice_usb_device_set_state(device, SPICE_USB_DEVICE_STATE_INSTALLING);
+    if (! self->priv->installer) {
+        self->priv->installer = spice_win_usb_driver_new();
+    }
+    installer = self->priv->installer;
+    cbinfo = g_new0(UsbInstallCbInfo, 1);
+    cbinfo->manager     = self;
+    cbinfo->device      = spice_usb_device_ref(device);
+    cbinfo->installer   = installer;
+    cbinfo->cancellable = cancellable;
+    cbinfo->callback    = callback;
+    cbinfo->user_data   = user_data;
+    cbinfo->is_install  = TRUE;
+
+    spice_win_usb_driver_install(installer, device, cancellable,
+                                 spice_usb_device_manager_drv_install_cb,
+                                 cbinfo);
+#else
+    _spice_usb_device_manager_connect_device_async(self,
+                                                   device,
+                                                   cancellable,
+                                                   callback,
+                                                   user_data);
+#endif
+}
+
+gboolean spice_usb_device_manager_connect_device_finish(
+    SpiceUsbDeviceManager *self, GAsyncResult *res, GError **err)
+{
+    GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT(res);
+
+    g_return_val_if_fail(g_simple_async_result_is_valid(res, G_OBJECT(self),
+                               spice_usb_device_manager_connect_device_async),
+                         FALSE);
+
+    if (g_simple_async_result_propagate_error(simple, err))
+        return FALSE;
+
+    return TRUE;
+}
+
+/**
+ * spice_usb_device_manager_disconnect_device:
+ * @manager: the #SpiceUsbDeviceManager manager
+ * @device: a #SpiceUsbDevice to disconnect
+ *
+ * Returns: %TRUE if @device has an associated USB redirection channel
+ */
+void spice_usb_device_manager_disconnect_device(SpiceUsbDeviceManager *self,
+                                                SpiceUsbDevice *device)
+{
+    g_return_if_fail(SPICE_IS_USB_DEVICE_MANAGER(self));
+    g_return_if_fail(device != NULL);
+
+    SPICE_DEBUG("disconnecting device %p", device);
+
+#ifdef USE_USBREDIR
+    SpiceUsbredirChannel *channel;
+
+    channel = spice_usb_device_manager_get_channel_for_dev(self, device);
+    if (channel)
+        spice_usbredir_channel_disconnect_device(channel);
+
+#ifdef G_OS_WIN32
+    SpiceWinUsbDriver *installer;
+    UsbInstallCbInfo *cbinfo;
+    guint8 state;
+
+    g_warn_if_fail(device != NULL);
+    g_warn_if_fail(self->priv->installer != NULL);
+
+    state = spice_usb_device_get_state(device);
+    if ((state != SPICE_USB_DEVICE_STATE_INSTALLED) &&
+        (state != SPICE_USB_DEVICE_STATE_CONNECTED)) {
+        return;
+    }
+
+    spice_usb_device_set_state(device, SPICE_USB_DEVICE_STATE_UNINSTALLING);
+    if (! self->priv->installer) {
+        self->priv->installer = spice_win_usb_driver_new();
+    }
+    installer = self->priv->installer;
+    cbinfo = g_new0(UsbInstallCbInfo, 1);
+    cbinfo->manager     = self;
+    cbinfo->device      = spice_usb_device_ref(device);
+    cbinfo->installer   = installer;
+    cbinfo->cancellable = NULL;
+    cbinfo->callback    = NULL;
+    cbinfo->user_data   = NULL;
+    cbinfo->is_install  = FALSE;
+
+    spice_win_usb_driver_uninstall(installer, device, NULL,
+                                   spice_usb_device_manager_drv_install_cb,
+                                   cbinfo);
+#endif
+
+#endif
+}
+
+gboolean
+spice_usb_device_manager_can_redirect_device(SpiceUsbDeviceManager  *self,
+                                             SpiceUsbDevice         *device,
+                                             GError                **err)
+{
+#ifdef USE_USBREDIR
+    const struct usbredirfilter_rule *guest_filter_rules = NULL;
+    SpiceUsbDeviceManagerPrivate *priv = self->priv;
+    int i, guest_filter_rules_count;
+
+    g_return_val_if_fail(SPICE_IS_USB_DEVICE_MANAGER(self), FALSE);
+    g_return_val_if_fail(device != NULL, FALSE);
+    g_return_val_if_fail(err == NULL || *err == NULL, FALSE);
+
+    if (!spice_session_get_usbredir_enabled(priv->session)) {
+        g_set_error_literal(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                            _("USB redirection is disabled"));
+        return FALSE;
+    }
+
+    if (!priv->channels->len) {
+        g_set_error_literal(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                            _("The connected VM is not configured for USB redirection"));
+        return FALSE;
+    }
+
+    /* Skip the other checks for already connected devices */
+    if (spice_usb_device_manager_is_device_connected(self, device))
+        return TRUE;
+
+    /* We assume all channels have the same filter, so we just take the
+       filter from the first channel */
+    spice_usbredir_channel_get_guest_filter(
+        g_ptr_array_index(priv->channels, 0),
+        &guest_filter_rules, &guest_filter_rules_count);
+
+    if (guest_filter_rules) {
+        gboolean filter_ok;
+        libusb_device *libdev;
+
+        libdev = spice_usb_device_manager_device_to_libdev(self, device);
+#ifdef G_OS_WIN32
+        if (libdev == NULL) {
+            g_set_error_literal(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                                _("Some USB devices were not found"));
+            return FALSE;
+        }
+#endif
+        filter_ok = (usbredirhost_check_device_filter(
+                            guest_filter_rules, guest_filter_rules_count,
+                            libdev, 0) == 0);
+        libusb_unref_device(libdev);
+        if (!filter_ok) {
+            g_set_error_literal(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                                _("Some USB devices are blocked by host policy"));
+            return FALSE;
+        }
+    }
+
+    /* Check if there are free channels */
+    for (i = 0; i < priv->channels->len; i++) {
+        SpiceUsbredirChannel *channel = g_ptr_array_index(priv->channels, i);
+
+        if (!spice_usbredir_channel_get_device(channel))
+            break;
+    }
+    if (i == priv->channels->len) {
+        g_set_error_literal(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                            _("There are no free USB channels"));
+        return FALSE;
+    }
+
+    return TRUE;
+#else
+    g_set_error_literal(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                        _("USB redirection support not compiled in"));
+    return FALSE;
+#endif
+}
+
+/**
+ * spice_usb_device_get_description:
+ * @device: #SpiceUsbDevice to get the description of
+ * @format: (allow-none): an optional printf() format string with
+ * positional parameters
+ *
+ * Get a string describing the device which is suitable as a description of
+ * the device for the end user. The returned string should be freed with
+ * g_free() when no longer needed.
+ *
+ * The @format positional parameters are the following:
+ * - '%%1$s' manufacturer
+ * - '%%2$s' product
+ * - '%%3$s' descriptor (a [vendor_id:product_id] string)
+ * - '%%4$d' bus
+ * - '%%5$d' address
+ *
+ * (the default format string is "%%s %%s %%s at %%d-%%d")
+ *
+ * Returns: a newly-allocated string holding the description, or %NULL if failed
+ */
+gchar *spice_usb_device_get_description(SpiceUsbDevice *device, const gchar *format)
+{
+#ifdef USE_USBREDIR
+    int bus, address, vid, pid;
+    gchar *description, *descriptor, *manufacturer = NULL, *product = NULL;
+
+    g_return_val_if_fail(device != NULL, NULL);
+
+    bus     = spice_usb_device_get_busnum(device);
+    address = spice_usb_device_get_devaddr(device);
+    vid     = spice_usb_device_get_vid(device);
+    pid     = spice_usb_device_get_pid(device);
+
+    if ((vid > 0) && (pid > 0)) {
+        descriptor = g_strdup_printf("[%04x:%04x]", vid, pid);
+    } else {
+        descriptor = g_strdup("");
+    }
+
+    spice_usb_util_get_device_strings(bus, address, vid, pid,
+                                      &manufacturer, &product);
+
+    if (!format)
+        format = _("%s %s %s at %d-%d");
+
+    description = g_strdup_printf(format, manufacturer, product, descriptor, bus, address);
+
+    g_free(manufacturer);
+    g_free(descriptor);
+    g_free(product);
+
+    return description;
+#else
+    return NULL;
+#endif
+}
+
+
+
+#ifdef USE_USBREDIR
+/*
+ * SpiceUsbDeviceInfo
+ */
+static SpiceUsbDeviceInfo *spice_usb_device_new(libusb_device *libdev)
+{
+    SpiceUsbDeviceInfo *info;
+    int vid, pid;
+    guint8 bus, addr;
+
+    g_return_val_if_fail(libdev != NULL, NULL);
+
+    bus = libusb_get_bus_number(libdev);
+    addr = libusb_get_device_address(libdev);
+
+    if (!spice_usb_device_manager_get_libdev_vid_pid(libdev, &vid, &pid)) {
+        return NULL;
+    }
+
+    info = g_new0(SpiceUsbDeviceInfo, 1);
+
+    info->busnum  = bus;
+    info->devaddr = addr;
+    info->vid = vid;
+    info->pid = pid;
+    info->ref = 1;
+#ifndef G_OS_WIN32
+    info->libdev = libusb_ref_device(libdev);
+#endif
+
+    return info;
+}
+
+guint8 spice_usb_device_get_busnum(const SpiceUsbDevice *device)
+{
+    const SpiceUsbDeviceInfo *info = (const SpiceUsbDeviceInfo *)device;
+
+    g_return_val_if_fail(info != NULL, 0);
+
+    return info->busnum;
+}
+
+guint8 spice_usb_device_get_devaddr(const SpiceUsbDevice *device)
+{
+    const SpiceUsbDeviceInfo *info = (const SpiceUsbDeviceInfo *)device;
+
+    g_return_val_if_fail(info != NULL, 0);
+
+    return info->devaddr;
+}
+
+guint16 spice_usb_device_get_vid(const SpiceUsbDevice *device)
+{
+    const SpiceUsbDeviceInfo *info = (const SpiceUsbDeviceInfo *)device;
+
+    g_return_val_if_fail(info != NULL, 0);
+
+    return info->vid;
+}
+
+guint16 spice_usb_device_get_pid(const SpiceUsbDevice *device)
+{
+    const SpiceUsbDeviceInfo *info = (const SpiceUsbDeviceInfo *)device;
+
+    g_return_val_if_fail(info != NULL, 0);
+
+    return info->pid;
+}
+
+#ifdef G_OS_WIN32
+void spice_usb_device_set_state(SpiceUsbDevice *device, guint8 state)
+{
+    SpiceUsbDeviceInfo *info = (SpiceUsbDeviceInfo *)device;
+
+    g_return_if_fail(info != NULL);
+
+    info->state = state;
+}
+
+guint8 spice_usb_device_get_state(SpiceUsbDevice *device)
+{
+    SpiceUsbDeviceInfo *info = (SpiceUsbDeviceInfo *)device;
+
+    g_return_val_if_fail(info != NULL, 0);
+
+    return info->state;
+}
+#endif
+
+static SpiceUsbDevice *spice_usb_device_ref(SpiceUsbDevice *device)
+{
+    SpiceUsbDeviceInfo *info = (SpiceUsbDeviceInfo *)device;
+
+    g_return_val_if_fail(info != NULL, NULL);
+    g_atomic_int_inc(&info->ref);
+    return device;
+}
+
+static void spice_usb_device_unref(SpiceUsbDevice *device)
+{
+    gboolean ref_count_is_0;
+
+    SpiceUsbDeviceInfo *info = (SpiceUsbDeviceInfo *)device;
+
+    g_return_if_fail(info != NULL);
+
+    ref_count_is_0 = g_atomic_int_dec_and_test(&info->ref);
+    if (ref_count_is_0) {
+#ifndef G_OS_WIN32
+        libusb_unref_device(info->libdev);
+#endif
+        g_free(info);
+    }
+}
+
+#ifndef G_OS_WIN32 /* Linux -- directly compare libdev */
+static gboolean
+spice_usb_device_equal_libdev(SpiceUsbDevice *device,
+                              libusb_device  *libdev)
+{
+    SpiceUsbDeviceInfo *info = (SpiceUsbDeviceInfo *)device;
+
+    if ((device == NULL) || (libdev == NULL))
+        return FALSE;
+
+    return info->libdev == libdev;
+}
+#else /* Windows -- compare vid:pid of device and libdev */
+static gboolean
+spice_usb_device_equal_libdev(SpiceUsbDevice *device,
+                              libusb_device  *libdev)
+{
+    int vid1, vid2, pid1, pid2;
+
+    if ((device == NULL) || (libdev == NULL))
+        return FALSE;
+
+    vid1 = spice_usb_device_get_vid(device);
+    pid1 = spice_usb_device_get_pid(device);
+
+    if (!spice_usb_device_manager_get_libdev_vid_pid(libdev, &vid2, &pid2)) {
+        return FALSE;
+    }
+
+    return ((vid1 == vid2) && (pid1 == pid2));
+}
+#endif
+
+/*
+ * Caller must libusb_unref_device the libusb_device returned by this function.
+ * Returns a libusb_device, or NULL upon failure
+ */
+static libusb_device *
+spice_usb_device_manager_device_to_libdev(SpiceUsbDeviceManager *self,
+                                          SpiceUsbDevice *device)
+{
+#ifdef G_OS_WIN32
+    /*
+     * On win32 we need to do this the hard and slow way, by asking libusb to
+     * re-enumerate all devices and then finding a matching device.
+     * We cannot cache the libusb_device like we do under Linux since the
+     * driver swap we do under windows invalidates the cached libdev.
+     */
+
+    libusb_device *d, **devlist;
+    int bus, addr;
+    int i;
+
+    g_return_val_if_fail(SPICE_IS_USB_DEVICE_MANAGER(self), NULL);
+    g_return_val_if_fail(device != NULL, NULL);
+    g_return_val_if_fail(self->priv != NULL, NULL);
+    g_return_val_if_fail(self->priv->context != NULL, NULL);
+
+    /* On windows we match by vid / pid, since the address may change */
+    bus  = spice_usb_device_get_vid(device);
+    addr = spice_usb_device_get_pid(device);
+
+    libusb_get_device_list(self->priv->context, &devlist);
+    if (!devlist)
+        return NULL;
+
+    for (i = 0; (d = devlist[i]) != NULL; i++) {
+        if (spice_usb_device_manager_libdev_match(d, bus, addr)) {
+            libusb_ref_device(d);
+            break;
+        }
+    }
+
+    libusb_free_device_list(devlist, 1);
+
+    return d;
+
+#else
+    /* Simply return a ref to the cached libdev */
+    SpiceUsbDeviceInfo *info = (SpiceUsbDeviceInfo *)device;
+
+    return libusb_ref_device(info->libdev);
+#endif
+}
+#endif /* USE_USBREDIR */
diff --git a/src/usb-device-manager.h b/src/usb-device-manager.h
new file mode 100644
index 0000000..5b4cfbe
--- /dev/null
+++ b/src/usb-device-manager.h
@@ -0,0 +1,122 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2011, 2012 Red Hat, Inc.
+
+   Red Hat Authors:
+   Hans de Goede <hdegoede at redhat.com>
+
+   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_USB_DEVICE_MANAGER_H__
+#define __SPICE_USB_DEVICE_MANAGER_H__
+
+#include "spice-client.h"
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_USB_DEVICE_MANAGER            (spice_usb_device_manager_get_type ())
+#define SPICE_USB_DEVICE_MANAGER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), SPICE_TYPE_USB_DEVICE_MANAGER, SpiceUsbDeviceManager))
+#define SPICE_USB_DEVICE_MANAGER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SPICE_TYPE_USB_DEVICE_MANAGER, SpiceUsbDeviceManagerClass))
+#define SPICE_IS_USB_DEVICE_MANAGER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SPICE_TYPE_USB_DEVICE_MANAGER))
+#define SPICE_IS_USB_DEVICE_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SPICE_TYPE_USB_DEVICE_MANAGER))
+#define SPICE_USB_DEVICE_MANAGER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SPICE_TYPE_USB_DEVICE_MANAGER, SpiceUsbDeviceManagerClass))
+
+#define SPICE_TYPE_USB_DEVICE                    (spice_usb_device_get_type())
+
+typedef struct _SpiceUsbDeviceManager SpiceUsbDeviceManager;
+typedef struct _SpiceUsbDeviceManagerClass SpiceUsbDeviceManagerClass;
+typedef struct _SpiceUsbDeviceManagerPrivate SpiceUsbDeviceManagerPrivate;
+
+typedef struct _SpiceUsbDevice SpiceUsbDevice;
+
+/**
+ * SpiceUsbDeviceManager:
+ *
+ * The #SpiceUsbDeviceManager struct is opaque and should not be accessed directly.
+ */
+struct _SpiceUsbDeviceManager
+{
+    GObject parent;
+
+    /*< private >*/
+    SpiceUsbDeviceManagerPrivate *priv;
+    /* Do not add fields to this struct */
+};
+
+/**
+ * SpiceUsbDeviceManagerClass:
+ * @parent_class: Parent class.
+ * @device_added: Signal class handler for the #SpiceUsbDeviceManager::device-added signal.
+ * @device_removed: Signal class handler for the #SpiceUsbDeviceManager::device-removed signal.
+ * @auto_connect_failed: Signal class handler for the #SpiceUsbDeviceManager::auto-connect-failed signal.
+ *
+ * Class structure for #SpiceUsbDeviceManager.
+ */
+struct _SpiceUsbDeviceManagerClass
+{
+    GObjectClass parent_class;
+
+    /* signals */
+    void (*device_added) (SpiceUsbDeviceManager *manager,
+                          SpiceUsbDevice *device);
+    void (*device_removed) (SpiceUsbDeviceManager *manager,
+                            SpiceUsbDevice *device);
+    void (*auto_connect_failed) (SpiceUsbDeviceManager *manager,
+                                 SpiceUsbDevice *device, GError *error);
+    void (*device_error) (SpiceUsbDeviceManager *manager,
+                          SpiceUsbDevice *device, GError *error);
+    /*< private >*/
+    /*
+     * If adding fields to this struct, remove corresponding
+     * amount of padding to avoid changing overall struct size
+     */
+    gchar _spice_reserved[SPICE_RESERVED_PADDING];
+};
+
+GType spice_usb_device_get_type(void);
+GType spice_usb_device_manager_get_type(void);
+
+gchar *spice_usb_device_get_description(SpiceUsbDevice *device, const gchar *format);
+gconstpointer spice_usb_device_get_libusb_device(const SpiceUsbDevice *device);
+
+SpiceUsbDeviceManager *spice_usb_device_manager_get(SpiceSession *session,
+                                                    GError **err);
+
+GPtrArray *spice_usb_device_manager_get_devices(SpiceUsbDeviceManager *manager);
+GPtrArray* spice_usb_device_manager_get_devices_with_filter(
+    SpiceUsbDeviceManager *manager, const gchar *filter);
+
+gboolean spice_usb_device_manager_is_device_connected(SpiceUsbDeviceManager *manager,
+                                                      SpiceUsbDevice *device);
+void spice_usb_device_manager_connect_device_async(
+                                             SpiceUsbDeviceManager *manager,
+                                             SpiceUsbDevice *device,
+                                             GCancellable *cancellable,
+                                             GAsyncReadyCallback callback,
+                                             gpointer user_data);
+gboolean spice_usb_device_manager_connect_device_finish(
+    SpiceUsbDeviceManager *self, GAsyncResult *res, GError **err);
+
+void spice_usb_device_manager_disconnect_device(SpiceUsbDeviceManager *manager,
+                                                SpiceUsbDevice *device);
+
+gboolean
+spice_usb_device_manager_can_redirect_device(SpiceUsbDeviceManager  *self,
+                                             SpiceUsbDevice         *device,
+                                             GError                **err);
+
+G_END_DECLS
+
+#endif /* __SPICE_USB_DEVICE_MANAGER_H__ */
diff --git a/src/usb-device-widget.c b/src/usb-device-widget.c
new file mode 100644
index 0000000..1ec30e3
--- /dev/null
+++ b/src/usb-device-widget.c
@@ -0,0 +1,554 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2012 Red Hat, Inc.
+
+   Red Hat Authors:
+   Hans de Goede <hdegoede at redhat.com>
+
+   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.h>
+#include "glib-compat.h"
+#include "spice-client.h"
+#include "spice-marshal.h"
+#include "usb-device-widget.h"
+
+/**
+ * SECTION:usb-device-widget
+ * @short_description: USB device selection widget
+ * @title: Spice USB device selection widget
+ * @section_id:
+ * @see_also:
+ * @stability: Stable
+ * @include: usb-device-widget.h
+ *
+ * #SpiceUsbDeviceWidget is a gtk widget which apps can use to easily
+ * add an UI to select USB devices to redirect (or unredirect).
+ */
+
+/* ------------------------------------------------------------------ */
+/* Prototypes for callbacks  */
+static void device_added_cb(SpiceUsbDeviceManager *manager,
+    SpiceUsbDevice *device, gpointer user_data);
+static void device_removed_cb(SpiceUsbDeviceManager *manager,
+    SpiceUsbDevice *device, gpointer user_data);
+static void device_error_cb(SpiceUsbDeviceManager *manager,
+    SpiceUsbDevice *device, GError *err, gpointer user_data);
+static gboolean spice_usb_device_widget_update_status(gpointer user_data);
+
+/* ------------------------------------------------------------------ */
+/* gobject glue                                                       */
+
+#define SPICE_USB_DEVICE_WIDGET_GET_PRIVATE(obj) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_USB_DEVICE_WIDGET, \
+                                 SpiceUsbDeviceWidgetPrivate))
+
+enum {
+    PROP_0,
+    PROP_SESSION,
+    PROP_DEVICE_FORMAT_STRING,
+};
+
+enum {
+    CONNECT_FAILED,
+    LAST_SIGNAL,
+};
+
+struct _SpiceUsbDeviceWidgetPrivate {
+    SpiceSession *session;
+    gchar *device_format_string;
+    SpiceUsbDeviceManager *manager;
+    GtkWidget *info_bar;
+    gchar *err_msg;
+    gsize device_count;
+};
+
+static guint signals[LAST_SIGNAL] = { 0, };
+
+#if GTK_CHECK_VERSION(3,0,0)
+G_DEFINE_TYPE(SpiceUsbDeviceWidget, spice_usb_device_widget, GTK_TYPE_BOX);
+#else
+G_DEFINE_TYPE(SpiceUsbDeviceWidget, spice_usb_device_widget, GTK_TYPE_VBOX);
+#endif
+
+
+static void spice_usb_device_widget_get_property(GObject     *gobject,
+                                                 guint        prop_id,
+                                                 GValue      *value,
+                                                 GParamSpec  *pspec)
+{
+    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(gobject);
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+
+    switch (prop_id) {
+    case PROP_SESSION:
+        g_value_set_object(value, priv->session);
+        break;
+    case PROP_DEVICE_FORMAT_STRING:
+        g_value_set_string(value, priv->device_format_string);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
+        break;
+    }
+}
+
+static void spice_usb_device_widget_set_property(GObject       *gobject,
+                                                 guint          prop_id,
+                                                 const GValue  *value,
+                                                 GParamSpec    *pspec)
+{
+    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(gobject);
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+
+    switch (prop_id) {
+    case PROP_SESSION:
+        priv->session = g_value_dup_object(value);
+        break;
+    case PROP_DEVICE_FORMAT_STRING:
+        priv->device_format_string = g_value_dup_string(value);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
+        break;
+    }
+}
+
+static void spice_usb_device_widget_hide_info_bar(SpiceUsbDeviceWidget *self)
+{
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+
+    if (priv->info_bar) {
+        gtk_widget_destroy(priv->info_bar);
+        priv->info_bar = NULL;
+    }
+}
+
+static void
+spice_usb_device_widget_show_info_bar(SpiceUsbDeviceWidget *self,
+                                      const gchar          *message,
+                                      GtkMessageType        message_type,
+                                      const gchar          *stock_icon_id)
+{
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+    GtkWidget *info_bar, *content_area, *hbox, *widget;
+
+    spice_usb_device_widget_hide_info_bar(self);
+
+    info_bar = gtk_info_bar_new();
+    gtk_info_bar_set_message_type(GTK_INFO_BAR(info_bar), message_type);
+
+    content_area = gtk_info_bar_get_content_area(GTK_INFO_BAR(info_bar));
+#if GTK_CHECK_VERSION(3,0,0)
+    hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 12);
+#else
+    hbox = gtk_hbox_new(FALSE, 12);
+#endif
+    gtk_container_add(GTK_CONTAINER(content_area), hbox);
+
+    widget = gtk_image_new_from_stock(stock_icon_id,
+                                      GTK_ICON_SIZE_SMALL_TOOLBAR);
+    gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, FALSE, 0);
+
+    widget = gtk_label_new(message);
+    gtk_box_pack_start(GTK_BOX(hbox), widget, TRUE, TRUE, 0);
+
+    priv->info_bar = gtk_alignment_new(0.0, 0.0, 1.0, 0.0);
+    gtk_alignment_set_padding(GTK_ALIGNMENT(priv->info_bar), 0, 0, 12, 0);
+    gtk_container_add(GTK_CONTAINER(priv->info_bar), info_bar);
+    gtk_box_pack_start(GTK_BOX(self), priv->info_bar, FALSE, FALSE, 0);
+    gtk_widget_show_all(priv->info_bar);
+}
+
+static GObject *spice_usb_device_widget_constructor(
+    GType gtype, guint n_properties, GObjectConstructParam *properties)
+{
+    GObject *obj;
+    SpiceUsbDeviceWidget *self;
+    SpiceUsbDeviceWidgetPrivate *priv;
+    GPtrArray *devices = NULL;
+    GError *err = NULL;
+    GtkWidget *label;
+    gchar *str;
+    int i;
+
+    {
+        /* Always chain up to the parent constructor */
+        GObjectClass *parent_class;
+        parent_class = G_OBJECT_CLASS(spice_usb_device_widget_parent_class);
+        obj = parent_class->constructor(gtype, n_properties, properties);
+    }
+
+    self = SPICE_USB_DEVICE_WIDGET(obj);
+    priv = self->priv;
+    if (!priv->session)
+        g_error("SpiceUsbDeviceWidget constructed without a session");
+
+    label = gtk_label_new(NULL);
+    str = g_strdup_printf("<b>%s</b>", _("Select USB devices to redirect"));
+    gtk_label_set_markup(GTK_LABEL (label), str);
+    g_free(str);
+    gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
+    gtk_box_pack_start(GTK_BOX(self), label, FALSE, FALSE, 0);
+
+    priv->manager = spice_usb_device_manager_get(priv->session, &err);
+    if (err) {
+        spice_usb_device_widget_show_info_bar(self, err->message,
+                                              GTK_MESSAGE_WARNING,
+                                              GTK_STOCK_DIALOG_WARNING);
+        g_clear_error(&err);
+        return obj;
+    }
+
+    g_signal_connect(priv->manager, "device-added",
+                     G_CALLBACK(device_added_cb), self);
+    g_signal_connect(priv->manager, "device-removed",
+                     G_CALLBACK(device_removed_cb), self);
+    g_signal_connect(priv->manager, "device-error",
+                     G_CALLBACK(device_error_cb), self);
+
+    devices = spice_usb_device_manager_get_devices(priv->manager);
+    if (!devices)
+        goto end;
+
+    for (i = 0; i < devices->len; i++)
+        device_added_cb(NULL, g_ptr_array_index(devices, i), self);
+
+    g_ptr_array_unref(devices);
+
+end:
+    spice_usb_device_widget_update_status(self);
+
+    return obj;
+}
+
+static void spice_usb_device_widget_finalize(GObject *object)
+{
+    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(object);
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+
+    if (priv->manager) {
+        g_signal_handlers_disconnect_by_func(priv->manager,
+                                             device_added_cb, self);
+        g_signal_handlers_disconnect_by_func(priv->manager,
+                                             device_removed_cb, self);
+        g_signal_handlers_disconnect_by_func(priv->manager,
+                                             device_error_cb, self);
+    }
+    g_object_unref(priv->session);
+    g_free(priv->device_format_string);
+
+    if (G_OBJECT_CLASS(spice_usb_device_widget_parent_class)->finalize)
+        G_OBJECT_CLASS(spice_usb_device_widget_parent_class)->finalize(object);
+}
+
+static void spice_usb_device_widget_class_init(
+    SpiceUsbDeviceWidgetClass *klass)
+{
+    GObjectClass *gobject_class = (GObjectClass *)klass;
+    GParamSpec *pspec;
+
+    g_type_class_add_private (klass, sizeof (SpiceUsbDeviceWidgetPrivate));
+
+    gobject_class->constructor  = spice_usb_device_widget_constructor;
+    gobject_class->finalize     = spice_usb_device_widget_finalize;
+    gobject_class->get_property = spice_usb_device_widget_get_property;
+    gobject_class->set_property = spice_usb_device_widget_set_property;
+
+    /**
+     * SpiceUsbDeviceWidget:session:
+     *
+     * #SpiceSession this #SpiceUsbDeviceWidget is associated with
+     *
+     **/
+    pspec = g_param_spec_object("session",
+                                "Session",
+                                "SpiceSession",
+                                SPICE_TYPE_SESSION,
+                                G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+                                G_PARAM_STATIC_STRINGS);
+    g_object_class_install_property(gobject_class, PROP_SESSION, pspec);
+
+    /**
+     * SpiceUsbDeviceWidget:device-format-string:
+     *
+     * Format string to pass to spice_usb_device_get_description() for getting
+     * the device USB descriptions.
+     */
+    pspec = g_param_spec_string("device-format-string",
+                                "Device format string",
+                                "Format string for device description",
+                                NULL,
+                                G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+                                G_PARAM_STATIC_STRINGS);
+    g_object_class_install_property(gobject_class, PROP_DEVICE_FORMAT_STRING,
+                                    pspec);
+
+    /**
+     * SpiceUsbDeviceWidget::connect-failed:
+     * @widget: The #SpiceUsbDeviceWidget that emitted the signal
+     * @device: #SpiceUsbDevice boxed object corresponding to the added device
+     * @error:  #GError describing the reason why the connect failed
+     *
+     * The #SpiceUsbDeviceWidget::connect-failed signal is emitted whenever
+     * the user has requested for a device to be redirected and this has
+     * failed.
+     **/
+    signals[CONNECT_FAILED] =
+        g_signal_new("connect-failed",
+                    G_OBJECT_CLASS_TYPE(gobject_class),
+                    G_SIGNAL_RUN_FIRST,
+                    G_STRUCT_OFFSET(SpiceUsbDeviceWidgetClass, connect_failed),
+                    NULL, NULL,
+                    g_cclosure_user_marshal_VOID__BOXED_BOXED,
+                    G_TYPE_NONE,
+                    2,
+                    SPICE_TYPE_USB_DEVICE,
+                    G_TYPE_ERROR);
+}
+
+static void spice_usb_device_widget_init(SpiceUsbDeviceWidget *self)
+{
+    self->priv = SPICE_USB_DEVICE_WIDGET_GET_PRIVATE(self);
+}
+
+/* ------------------------------------------------------------------ */
+/* public api                                                         */
+
+/**
+ * spice_usb_device_widget_new:
+ * @session: #SpiceSession for which to widget will control USB redirection
+ * @device_format_string: (allow-none): String passed to
+ * spice_usb_device_get_description()
+ *
+ * Returns: a new #SpiceUsbDeviceWidget instance
+ */
+GtkWidget *spice_usb_device_widget_new(SpiceSession    *session,
+                                       const gchar     *device_format_string)
+{
+    return g_object_new(SPICE_TYPE_USB_DEVICE_WIDGET,
+                        "orientation", GTK_ORIENTATION_VERTICAL,
+                        "session", session,
+                        "device-format-string", device_format_string,
+                        "spacing", 6,
+                        NULL);
+}
+
+/* ------------------------------------------------------------------ */
+/* callbacks                                                          */
+
+static SpiceUsbDevice *get_usb_device(GtkWidget *widget)
+{
+    if (!GTK_IS_ALIGNMENT(widget))
+        return NULL;
+
+    widget = gtk_bin_get_child(GTK_BIN(widget));
+    return g_object_get_data(G_OBJECT(widget), "usb-device");
+}
+
+static void check_can_redirect(GtkWidget *widget, gpointer user_data)
+{
+    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+    SpiceUsbDevice *device;
+    gboolean can_redirect;
+    GError *err = NULL;
+
+    device = get_usb_device(widget);
+    if (!device)
+        return; /* Non device widget, ie the info_bar */
+
+    priv->device_count++;
+    can_redirect = spice_usb_device_manager_can_redirect_device(priv->manager,
+                                                                device, &err);
+    gtk_widget_set_sensitive(widget, can_redirect);
+
+    /* If we cannot redirect this device, append the error message to
+       err_msg, but only if it is *not* already there! */
+    if (!can_redirect) {
+        if (priv->err_msg) {
+            if (!strstr(priv->err_msg, err->message)) {
+                gchar *old_err_msg = priv->err_msg;
+
+                priv->err_msg = g_strdup_printf("%s\n%s", priv->err_msg,
+                                                err->message);
+                g_free(old_err_msg);
+            }
+        } else {
+            priv->err_msg = g_strdup(err->message);
+        }
+    }
+
+    g_clear_error(&err);
+}
+
+static gboolean spice_usb_device_widget_update_status(gpointer user_data)
+{
+    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+
+    priv->device_count = 0;
+    gtk_container_foreach(GTK_CONTAINER(self), check_can_redirect, self);
+
+    if (priv->err_msg) {
+        spice_usb_device_widget_show_info_bar(self, priv->err_msg,
+                                              GTK_MESSAGE_INFO,
+                                              GTK_STOCK_DIALOG_WARNING);
+        g_free(priv->err_msg);
+        priv->err_msg = NULL;
+    } else {
+        spice_usb_device_widget_hide_info_bar(self);
+    }
+
+    if (priv->device_count == 0)
+        spice_usb_device_widget_show_info_bar(self, _("No USB devices detected"),
+                                              GTK_MESSAGE_INFO,
+                                              GTK_STOCK_DIALOG_INFO);
+    return FALSE;
+}
+
+typedef struct _connect_cb_data {
+    GtkWidget *check;
+    SpiceUsbDeviceWidget *self;
+} connect_cb_data;
+
+static void connect_cb(GObject *gobject, GAsyncResult *res, gpointer user_data)
+{
+    SpiceUsbDeviceManager *manager = SPICE_USB_DEVICE_MANAGER(gobject);
+    connect_cb_data *data = user_data;
+    SpiceUsbDeviceWidget *self = data->self;
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+    SpiceUsbDevice *device;
+    GError *err = NULL;
+    gchar *desc;
+
+    spice_usb_device_manager_connect_device_finish(manager, res, &err);
+    if (err) {
+        device = g_object_get_data(G_OBJECT(data->check), "usb-device");
+        desc = spice_usb_device_get_description(device,
+                                                priv->device_format_string);
+        g_prefix_error(&err, "Could not redirect %s: ", desc);
+        g_free(desc);
+
+        SPICE_DEBUG("%s", err->message);
+        g_signal_emit(self, signals[CONNECT_FAILED], 0, device, err);
+        g_error_free(err);
+
+        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(data->check), FALSE);
+        spice_usb_device_widget_update_status(self);
+    }
+
+    g_object_unref(data->check);
+    g_object_unref(data->self);
+    g_free(data);
+}
+
+static void checkbox_clicked_cb(GtkWidget *check, gpointer user_data)
+{
+    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+    SpiceUsbDevice *device;
+
+    device = g_object_get_data(G_OBJECT(check), "usb-device");
+
+    if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(check))) {
+        connect_cb_data *data = g_new(connect_cb_data, 1);
+        data->check = g_object_ref(check);
+        data->self  = g_object_ref(self);
+        spice_usb_device_manager_connect_device_async(priv->manager,
+                                                      device,
+                                                      NULL,
+                                                      connect_cb,
+                                                      data);
+    } else {
+        spice_usb_device_manager_disconnect_device(priv->manager,
+                                                   device);
+    }
+    spice_usb_device_widget_update_status(self);
+}
+
+static void checkbox_usb_device_destroy_notify(gpointer data)
+{
+    g_boxed_free(spice_usb_device_get_type(), data);
+}
+
+static void device_added_cb(SpiceUsbDeviceManager *manager,
+    SpiceUsbDevice *device, gpointer user_data)
+{
+    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+    GtkWidget *align, *check;
+    gchar *desc;
+
+    desc = spice_usb_device_get_description(device,
+                                            priv->device_format_string);
+    check = gtk_check_button_new_with_label(desc);
+    g_free(desc);
+
+    if (spice_usb_device_manager_is_device_connected(priv->manager,
+                                                     device))
+        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), TRUE);
+
+    g_object_set_data_full(
+            G_OBJECT(check), "usb-device",
+            g_boxed_copy(spice_usb_device_get_type(), device),
+            checkbox_usb_device_destroy_notify);
+    g_signal_connect(G_OBJECT(check), "clicked",
+                     G_CALLBACK(checkbox_clicked_cb), self);
+
+    align = gtk_alignment_new(0, 0, 0, 0);
+    gtk_alignment_set_padding(GTK_ALIGNMENT(align), 0, 0, 12, 0);
+    gtk_container_add(GTK_CONTAINER(align), check);
+    gtk_box_pack_end(GTK_BOX(self), align, FALSE, FALSE, 0);
+    spice_usb_device_widget_update_status(self);
+    gtk_widget_show_all(align);
+}
+
+static void destroy_widget_by_usb_device(GtkWidget *widget, gpointer user_data)
+{
+    if (get_usb_device(widget) == user_data)
+        gtk_widget_destroy(widget);
+}
+
+static void device_removed_cb(SpiceUsbDeviceManager *manager,
+    SpiceUsbDevice *device, gpointer user_data)
+{
+    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
+
+    gtk_container_foreach(GTK_CONTAINER(self),
+                          destroy_widget_by_usb_device, device);
+
+    spice_usb_device_widget_update_status(self);
+}
+
+static void set_inactive_by_usb_device(GtkWidget *widget, gpointer user_data)
+{
+    if (get_usb_device(widget) == user_data) {
+        GtkWidget *check = gtk_bin_get_child(GTK_BIN(widget));
+        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), FALSE);
+    }
+}
+
+static void device_error_cb(SpiceUsbDeviceManager *manager,
+    SpiceUsbDevice *device, GError *err, gpointer user_data)
+{
+    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
+
+    gtk_container_foreach(GTK_CONTAINER(self),
+                          set_inactive_by_usb_device, device);
+
+    spice_usb_device_widget_update_status(self);
+}
diff --git a/src/usb-device-widget.h b/src/usb-device-widget.h
new file mode 100644
index 0000000..b68cc6b
--- /dev/null
+++ b/src/usb-device-widget.h
@@ -0,0 +1,81 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2012 Red Hat, Inc.
+
+   Red Hat Authors:
+   Hans de Goede <hdegoede at redhat.com>
+
+   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_USB_DEVICE_WIDGET_H__
+#define __SPICE_USB_DEVICE_WIDGET_H__
+
+#include <gtk/gtk.h>
+#include "spice-client.h"
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_USB_DEVICE_WIDGET            (spice_usb_device_widget_get_type ())
+#define SPICE_USB_DEVICE_WIDGET(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), SPICE_TYPE_USB_DEVICE_WIDGET, SpiceUsbDeviceWidget))
+#define SPICE_USB_DEVICE_WIDGET_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SPICE_TYPE_USB_DEVICE_WIDGET, SpiceUsbDeviceWidgetClass))
+#define SPICE_IS_USB_DEVICE_WIDGET(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SPICE_TYPE_USB_DEVICE_WIDGET))
+#define SPICE_IS_USB_DEVICE_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SPICE_TYPE_USB_DEVICE_WIDGET))
+#define SPICE_USB_DEVICE_WIDGET_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SPICE_TYPE_USB_DEVICE_WIDGET, SpiceUsbDeviceWidgetClass))
+
+typedef struct _SpiceUsbDeviceWidget SpiceUsbDeviceWidget;
+typedef struct _SpiceUsbDeviceWidgetClass SpiceUsbDeviceWidgetClass;
+typedef struct _SpiceUsbDeviceWidgetPrivate SpiceUsbDeviceWidgetPrivate;
+
+/**
+ * SpiceUsbDeviceWidget:
+ *
+ * The #SpiceUsbDeviceWidget struct is opaque and should not be accessed directly.
+ */
+struct _SpiceUsbDeviceWidget
+{
+    GtkVBox parent;
+
+    /*< private >*/
+    SpiceUsbDeviceWidgetPrivate *priv;
+    /* Do not add fields to this struct */
+};
+
+/**
+ * SpiceUsbDeviceWidgetClass:
+ * @connect_failed: Signal class handler for the #SpiceUsbDeviceWidget::connect-failed signal.
+ *
+ * Class structure for #SpiceUsbDeviceWidget.
+ */
+struct _SpiceUsbDeviceWidgetClass
+{
+    GtkVBoxClass parent_class;
+
+    /* signals */
+    void (*connect_failed) (SpiceUsbDeviceWidget *widget,
+                            SpiceUsbDevice *device, GError *error);
+    /*< private >*/
+    /*
+     * If adding fields to this struct, remove corresponding
+     * amount of padding to avoid changing overall struct size
+     */
+    gchar _spice_reserved[SPICE_RESERVED_PADDING];
+};
+
+GType spice_usb_device_widget_get_type(void);
+GtkWidget *spice_usb_device_widget_new(SpiceSession    *session,
+                                       const gchar     *device_format_string);
+
+G_END_DECLS
+
+#endif /* __SPICE_USB_DEVICE_WIDGET_H__ */
diff --git a/src/usbutil.c b/src/usbutil.c
new file mode 100644
index 0000000..16d757b
--- /dev/null
+++ b/src/usbutil.c
@@ -0,0 +1,323 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2012 Red Hat, Inc.
+
+   Red Hat Authors:
+   Hans de Goede <hdegoede at redhat.com>
+
+   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-object.h>
+#include <glib/gi18n.h>
+#include <ctype.h>
+#include <stdlib.h>
+
+#include "glib-compat.h"
+
+#ifdef USE_USBREDIR
+#ifdef __linux__
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#endif
+#include "usbutil.h"
+#include "spice-util-priv.h"
+
+#define VENDOR_NAME_LEN (122 - sizeof(void *))
+#define PRODUCT_NAME_LEN 126
+
+typedef struct _usb_product_info {
+    guint16 product_id;
+    char name[PRODUCT_NAME_LEN];
+} usb_product_info;
+
+typedef struct _usb_vendor_info {
+    usb_product_info *product_info;
+    int product_count;
+    guint16 vendor_id;
+    char name[VENDOR_NAME_LEN];
+} usb_vendor_info;
+
+static GStaticMutex usbids_load_mutex = G_STATIC_MUTEX_INIT;
+static int usbids_vendor_count = 0; /* < 0: failed, 0: empty, > 0: loaded */
+static usb_vendor_info *usbids_vendor_info = NULL;
+
+G_GNUC_INTERNAL
+const char *spice_usbutil_libusb_strerror(enum libusb_error error_code)
+{
+    switch (error_code) {
+    case LIBUSB_SUCCESS:
+        return "Success";
+    case LIBUSB_ERROR_IO:
+        return "Input/output error";
+    case LIBUSB_ERROR_INVALID_PARAM:
+        return "Invalid parameter";
+    case LIBUSB_ERROR_ACCESS:
+        return "Access denied (insufficient permissions)";
+    case LIBUSB_ERROR_NO_DEVICE:
+        return "No such device (it may have been disconnected)";
+    case LIBUSB_ERROR_NOT_FOUND:
+        return "Entity not found";
+    case LIBUSB_ERROR_BUSY:
+        return "Resource busy";
+    case LIBUSB_ERROR_TIMEOUT:
+        return "Operation timed out";
+    case LIBUSB_ERROR_OVERFLOW:
+        return "Overflow";
+    case LIBUSB_ERROR_PIPE:
+        return "Pipe error";
+    case LIBUSB_ERROR_INTERRUPTED:
+        return "System call interrupted (perhaps due to signal)";
+    case LIBUSB_ERROR_NO_MEM:
+        return "Insufficient memory";
+    case LIBUSB_ERROR_NOT_SUPPORTED:
+        return "Operation not supported or unimplemented on this platform";
+    case LIBUSB_ERROR_OTHER:
+        return "Other error";
+    }
+    return "Unknown error";
+}
+
+#ifdef __linux__
+/* <Sigh> libusb does not allow getting the manufacturer and product strings
+   without opening the device, so grab them directly from sysfs */
+static gchar *spice_usbutil_get_sysfs_attribute(int bus, int address,
+                                                const char *attribute)
+{
+    struct stat stat_buf;
+    char filename[256];
+    gchar *contents;
+
+    snprintf(filename, sizeof(filename), "/dev/bus/usb/%03d/%03d",
+             bus, address);
+    if (stat(filename, &stat_buf) != 0)
+        return NULL;
+
+    snprintf(filename, sizeof(filename), "/sys/dev/char/%d:%d/%s",
+             major(stat_buf.st_rdev), minor(stat_buf.st_rdev), attribute);
+    if (!g_file_get_contents(filename, &contents, NULL, NULL))
+        return NULL;
+
+    /* Remove the newline at the end */
+    contents[strlen(contents) - 1] = '\0';
+
+    return contents;
+}
+#endif
+
+static gboolean spice_usbutil_parse_usbids(gchar *path)
+{
+    gchar *contents, *line, **lines;
+    usb_product_info *product_info;
+    int i, j, id, product_count = 0;
+
+    usbids_vendor_count = 0;
+    if (!g_file_get_contents(path, &contents, NULL, NULL)) {
+        usbids_vendor_count = -1;
+        return FALSE;
+    }
+
+    lines = g_strsplit(contents, "\n", -1);
+
+    for (i = 0; lines[i]; i++) {
+        if (!isxdigit(lines[i][0]) || !isxdigit(lines[i][1]))
+            continue;
+
+        for (j = 1; lines[i + j] &&
+                   (lines[i + j][0] == '\t' ||
+                    lines[i + j][0] == '#'  ||
+                    lines[i + j][0] == '\0'); j++) {
+            if (lines[i + j][0] == '\t' && isxdigit(lines[i + j][1]))
+                product_count++;
+        }
+        i += j - 1;
+
+        usbids_vendor_count++;
+    }
+
+    usbids_vendor_info = g_new(usb_vendor_info, usbids_vendor_count);
+    product_info = g_new(usb_product_info, product_count);
+
+    usbids_vendor_count = 0;
+    for (i = 0; lines[i]; i++) {
+        line = lines[i];
+
+        if (!isxdigit(line[0]) || !isxdigit(line[1]))
+            continue;
+
+        id = strtoul(line, &line, 16);
+        while (isspace(line[0]))
+            line++;
+
+        usbids_vendor_info[usbids_vendor_count].vendor_id = id;
+        snprintf(usbids_vendor_info[usbids_vendor_count].name,
+                 VENDOR_NAME_LEN, "%s", line);
+
+        product_count = 0;
+        for (j = 1; lines[i + j] &&
+                   (lines[i + j][0] == '\t' ||
+                    lines[i + j][0] == '#'  ||
+                    lines[i + j][0] == '\0'); j++) {
+            line = lines[i + j];
+
+            if (line[0] != '\t' || !isxdigit(line[1]))
+                continue;
+
+            id = strtoul(line + 1, &line, 16);
+            while (isspace(line[0]))
+                line++;
+            product_info[product_count].product_id = id;
+            snprintf(product_info[product_count].name,
+                     PRODUCT_NAME_LEN, "%s", line);
+
+            product_count++;
+        }
+        i += j - 1;
+
+        usbids_vendor_info[usbids_vendor_count].product_count = product_count;
+        usbids_vendor_info[usbids_vendor_count].product_info  = product_info;
+        product_info += product_count;
+        usbids_vendor_count++;
+    }
+
+    g_strfreev(lines);
+    g_free(contents);
+
+#if 0 /* Testing only */
+    for (i = 0; i < usbids_vendor_count; i++) {
+        printf("%04x  %s\n", usbids_vendor_info[i].vendor_id,
+               usbids_vendor_info[i].name);
+        product_info = usbids_vendor_info[i].product_info;
+        for (j = 0; j < usbids_vendor_info[i].product_count; j++) {
+            printf("\t%04x  %s\n", product_info[j].product_id,
+                   product_info[j].name);
+        }
+    }
+#endif
+
+    return TRUE;
+}
+
+static gboolean spice_usbutil_load_usbids(void)
+{
+    gboolean success = FALSE;
+
+    g_static_mutex_lock(&usbids_load_mutex);
+    if (usbids_vendor_count) {
+        success = usbids_vendor_count > 0;
+        goto leave;
+    }
+
+#ifdef WITH_USBIDS
+    success = spice_usbutil_parse_usbids(USB_IDS);
+#else
+    {
+        const gchar * const *dirs = g_get_system_data_dirs();
+        gchar *path = NULL;
+        int i;
+
+        for (i = 0; dirs[i]; ++i) {
+            path = g_build_filename(dirs[i], "hwdata", "usb.ids", NULL);
+            success = spice_usbutil_parse_usbids(path);
+            SPICE_DEBUG("loading %s success: %s", path, spice_yes_no(success));
+            g_free(path);
+
+            if (success)
+                goto leave;
+        }
+    }
+#endif
+
+leave:
+    g_static_mutex_unlock(&usbids_load_mutex);
+    return success;
+}
+
+G_GNUC_INTERNAL
+void spice_usb_util_get_device_strings(int bus, int address,
+                                       int vendor_id, int product_id,
+                                       gchar **manufacturer, gchar **product)
+{
+    usb_product_info *product_info;
+    int i, j;
+
+    g_return_if_fail(manufacturer != NULL);
+    g_return_if_fail(product != NULL);
+
+    *manufacturer = NULL;
+    *product = NULL;
+
+#if __linux__
+    *manufacturer = spice_usbutil_get_sysfs_attribute(bus, address, "manufacturer");
+    *product = spice_usbutil_get_sysfs_attribute(bus, address, "product");
+#endif
+
+    if ((!*manufacturer || !*product) &&
+        spice_usbutil_load_usbids()) {
+
+        for (i = 0; i < usbids_vendor_count; i++) {
+            if ((int)usbids_vendor_info[i].vendor_id != vendor_id)
+                continue;
+
+            if (!*manufacturer && usbids_vendor_info[i].name[0])
+                *manufacturer = g_strdup(usbids_vendor_info[i].name);
+
+            product_info = usbids_vendor_info[i].product_info;
+            for (j = 0; j < usbids_vendor_info[i].product_count; j++) {
+                if ((int)product_info[j].product_id != product_id)
+                    continue;
+
+                if (!*product && product_info[j].name[0])
+                    *product = g_strdup(product_info[j].name);
+
+                break;
+            }
+            break;
+        }
+    }
+
+    if (!*manufacturer)
+        *manufacturer = g_strdup(_("USB"));
+    if (!*product)
+        *product = g_strdup(_("Device"));
+
+    /* Some devices have unwanted whitespace in their strings */
+    g_strstrip(*manufacturer);
+    g_strstrip(*product);
+
+    /* Some devices repeat the manufacturer at the beginning of product */
+    if (g_str_has_prefix(*product, *manufacturer) &&
+            strlen(*product) > strlen(*manufacturer)) {
+        gchar *tmp = g_strdup(*product + strlen(*manufacturer));
+        g_free(*product);
+        *product = tmp;
+        g_strstrip(*product);
+    }
+}
+
+#endif
+
+#ifdef USBUTIL_TEST
+int main()
+{
+    if (spice_usbutil_load_usbids())
+        exit(0);
+
+    exit(1);
+}
+#endif
diff --git a/src/usbutil.h b/src/usbutil.h
new file mode 100644
index 0000000..de5e92a
--- /dev/null
+++ b/src/usbutil.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2012 Red Hat, Inc.
+
+   Red Hat Authors:
+   Hans de Goede <hdegoede at redhat.com>
+
+   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_USBUTIL_H__
+#define __SPICE_USBUTIL_H__
+
+#include <glib.h>
+
+#ifdef USE_USBREDIR
+#include <libusb.h>
+
+G_BEGIN_DECLS
+
+const char *spice_usbutil_libusb_strerror(enum libusb_error error_code);
+void spice_usb_util_get_device_strings(int bus, int address,
+                                       int vendor_id, int product_id,
+                                       gchar **manufacturer, gchar **product);
+
+G_END_DECLS
+
+#endif /* USE_USBREDIR */
+#endif /* __SPICE_USBUTIL_H__ */
diff --git a/src/vmcstream.c b/src/vmcstream.c
new file mode 100644
index 0000000..483dd5a
--- /dev/null
+++ b/src/vmcstream.c
@@ -0,0 +1,535 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+  Copyright (C) 2013 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 <string.h>
+
+#include "vmcstream.h"
+#include "spice-channel-priv.h"
+#include "gio-coroutine.h"
+#include "glib-compat.h"
+
+struct _SpiceVmcInputStream
+{
+    GInputStream parent_instance;
+    GSimpleAsyncResult *result;
+    struct coroutine *coroutine;
+
+    SpiceChannel *channel;
+    gboolean all;
+    guint8 *buffer;
+    gsize count;
+    gsize pos;
+
+    GCancellable *cancellable;
+    gulong cancel_id;
+};
+
+struct _SpiceVmcInputStreamClass
+{
+    GInputStreamClass parent_class;
+};
+
+static gssize   spice_vmc_input_stream_read        (GInputStream        *stream,
+                                                    void                *buffer,
+                                                    gsize                count,
+                                                    GCancellable        *cancellable,
+                                                    GError             **error);
+static void     spice_vmc_input_stream_read_async  (GInputStream        *stream,
+                                                    void                *buffer,
+                                                    gsize                count,
+                                                    int                  io_priority,
+                                                    GCancellable        *cancellable,
+                                                    GAsyncReadyCallback  callback,
+                                                    gpointer             user_data);
+static gssize   spice_vmc_input_stream_read_finish (GInputStream        *stream,
+                                                    GAsyncResult        *result,
+                                                    GError             **error);
+static gssize   spice_vmc_input_stream_skip        (GInputStream        *stream,
+                                                    gsize                count,
+                                                    GCancellable        *cancellable,
+                                                    GError             **error);
+static gboolean spice_vmc_input_stream_close       (GInputStream        *stream,
+                                                    GCancellable        *cancellable,
+                                                    GError             **error);
+
+G_DEFINE_TYPE(SpiceVmcInputStream, spice_vmc_input_stream, G_TYPE_INPUT_STREAM)
+
+
+static void
+spice_vmc_input_stream_class_init(SpiceVmcInputStreamClass *klass)
+{
+    GInputStreamClass *istream_class;
+
+    istream_class = G_INPUT_STREAM_CLASS(klass);
+    istream_class->read_fn = spice_vmc_input_stream_read;
+    istream_class->read_async = spice_vmc_input_stream_read_async;
+    istream_class->read_finish = spice_vmc_input_stream_read_finish;
+    istream_class->skip = spice_vmc_input_stream_skip;
+    istream_class->close_fn = spice_vmc_input_stream_close;
+}
+
+static void
+spice_vmc_input_stream_init(SpiceVmcInputStream *self)
+{
+}
+
+static SpiceVmcInputStream *
+spice_vmc_input_stream_new(void)
+{
+    SpiceVmcInputStream *self;
+
+    self = g_object_new(SPICE_TYPE_VMC_INPUT_STREAM, NULL);
+
+    return self;
+}
+
+/* coroutine */
+/**
+ * Feed a SpiceVmc stream with new data from a coroutine
+ *
+ * The other end will be waiting on read_async() until data is fed
+ * here.
+ */
+G_GNUC_INTERNAL void
+spice_vmc_input_stream_co_data(SpiceVmcInputStream *self,
+                               const gpointer d, gsize size)
+{
+    guint8 *data = d;
+
+    g_return_if_fail(SPICE_IS_VMC_INPUT_STREAM(self));
+    g_return_if_fail(self->coroutine == NULL);
+
+    self->coroutine = coroutine_self();
+
+    while (size > 0) {
+        SPICE_DEBUG("spicevmc co_data %p", self->result);
+        if (!self->result)
+            coroutine_yield(NULL);
+
+        g_return_if_fail(self->result != NULL);
+
+        gsize min = MIN(self->count, size);
+        memcpy(self->buffer, data, min);
+
+        size -= min;
+        data += min;
+
+        SPICE_DEBUG("spicevmc co_data complete: %" G_GSIZE_FORMAT
+                    "/%" G_GSIZE_FORMAT, min, self->count);
+
+        self->pos += min;
+        self->buffer += min;
+
+        if (self->all && min > 0 && self->pos != self->count)
+            continue;
+
+        g_simple_async_result_set_op_res_gssize(self->result, self->pos);
+
+        g_simple_async_result_complete_in_idle(self->result);
+        g_clear_object(&self->result);
+        if (self->cancellable) {
+            g_cancellable_disconnect(self->cancellable, self->cancel_id);
+            g_clear_object(&self->cancellable);
+        }
+    }
+
+    self->coroutine = NULL;
+}
+
+static void
+read_cancelled(GCancellable *cancellable,
+               gpointer user_data)
+{
+    SpiceVmcInputStream *self = SPICE_VMC_INPUT_STREAM(user_data);
+
+    SPICE_DEBUG("read cancelled, %p", self->result);
+    g_simple_async_result_set_error(self->result,
+                                    G_IO_ERROR, G_IO_ERROR_CANCELLED,
+                                    "read cancelled");
+    g_simple_async_result_complete_in_idle(self->result);
+
+    g_clear_object(&self->result);
+
+    /* See FIXME */
+    /* if (self->cancellable) { */
+    /*     g_cancellable_disconnect(self->cancellable, self->cancel_id); */
+    /*     g_clear_object(&self->cancellable); */
+    /* } */
+}
+
+G_GNUC_INTERNAL void
+spice_vmc_input_stream_read_all_async(GInputStream        *stream,
+                                      void                *buffer,
+                                      gsize                count,
+                                      int                  io_priority,
+                                      GCancellable        *cancellable,
+                                      GAsyncReadyCallback  callback,
+                                      gpointer             user_data)
+{
+    SpiceVmcInputStream *self = SPICE_VMC_INPUT_STREAM(stream);
+    GSimpleAsyncResult *result;
+
+    /* no concurrent read permitted by ginputstream */
+    g_return_if_fail(self->result == NULL);
+    g_return_if_fail(self->cancellable == NULL);
+    self->all = TRUE;
+    self->buffer = buffer;
+    self->count = count;
+    self->pos = 0;
+    result = g_simple_async_result_new(G_OBJECT(self),
+                                       callback,
+                                       user_data,
+                                       spice_vmc_input_stream_read_async);
+    self->result = result;
+    self->cancellable = g_object_ref(cancellable);
+    if (cancellable)
+        self->cancel_id =
+            g_cancellable_connect(cancellable, G_CALLBACK(read_cancelled), self, NULL);
+
+    if (self->coroutine)
+        coroutine_yieldto(self->coroutine, NULL);
+}
+
+G_GNUC_INTERNAL gssize
+spice_vmc_input_stream_read_all_finish(GInputStream *stream,
+                                       GAsyncResult *result,
+                                       GError **error)
+{
+    GSimpleAsyncResult *simple;
+    SpiceVmcInputStream *self = SPICE_VMC_INPUT_STREAM(stream);
+
+    g_return_val_if_fail(g_simple_async_result_is_valid(result,
+                                                        G_OBJECT(self),
+                                                        spice_vmc_input_stream_read_async),
+                         -1);
+
+    simple = (GSimpleAsyncResult *)result;
+
+    /* FIXME: calling _finish() is required. Disconnecting in
+       read_cancelled() causes a deadlock. #705395 */
+    if (self->cancellable) {
+        g_cancellable_disconnect(self->cancellable, self->cancel_id);
+        g_clear_object(&self->cancellable);
+    }
+
+    if (g_simple_async_result_propagate_error(simple, error))
+        return -1;
+
+    return g_simple_async_result_get_op_res_gssize(simple);
+}
+
+static void
+spice_vmc_input_stream_read_async(GInputStream        *stream,
+                                  void                *buffer,
+                                  gsize                count,
+                                  int                  io_priority,
+                                  GCancellable        *cancellable,
+                                  GAsyncReadyCallback  callback,
+                                  gpointer             user_data)
+{
+    SpiceVmcInputStream *self = SPICE_VMC_INPUT_STREAM(stream);
+    GSimpleAsyncResult *result;
+
+    /* no concurrent read permitted by ginputstream */
+    g_return_if_fail(self->result == NULL);
+    g_return_if_fail(self->cancellable == NULL);
+    self->all = FALSE;
+    self->buffer = buffer;
+    self->count = count;
+    self->pos = 0;
+    result = g_simple_async_result_new(G_OBJECT(self),
+                                       callback,
+                                       user_data,
+                                       spice_vmc_input_stream_read_async);
+    self->result = result;
+    self->cancellable = g_object_ref(cancellable);
+    if (cancellable)
+        self->cancel_id =
+            g_cancellable_connect(cancellable, G_CALLBACK(read_cancelled), self, NULL);
+
+    if (self->coroutine)
+        coroutine_yieldto(self->coroutine, NULL);
+}
+
+static gssize
+spice_vmc_input_stream_read_finish(GInputStream *stream,
+                                   GAsyncResult *result,
+                                   GError **error)
+{
+    GSimpleAsyncResult *simple;
+    SpiceVmcInputStream *self = SPICE_VMC_INPUT_STREAM(stream);
+
+    g_return_val_if_fail(g_simple_async_result_is_valid(result,
+                                                        G_OBJECT(self),
+                                                        spice_vmc_input_stream_read_async),
+                         -1);
+
+    simple = (GSimpleAsyncResult *)result;
+
+    /* FIXME: calling _finish() is required. Disconnecting in
+       read_cancelled() causes a deadlock. #705395 */
+    if (self->cancellable) {
+        g_cancellable_disconnect(self->cancellable, self->cancel_id);
+        g_clear_object(&self->cancellable);
+    }
+
+    if (g_simple_async_result_propagate_error(simple, error))
+        return -1;
+
+    return g_simple_async_result_get_op_res_gssize(simple);
+}
+
+static gssize
+spice_vmc_input_stream_read(GInputStream  *stream,
+                            void          *buffer,
+                            gsize          count,
+                            GCancellable  *cancellable,
+                            GError       **error)
+{
+    g_return_val_if_reached(-1);
+}
+
+static gssize
+spice_vmc_input_stream_skip(GInputStream  *stream,
+                            gsize          count,
+                            GCancellable  *cancellable,
+                            GError       **error)
+{
+    g_return_val_if_reached(-1);
+}
+
+static gboolean
+spice_vmc_input_stream_close(GInputStream  *stream,
+                             GCancellable  *cancellable,
+                             GError       **error)
+{
+    SPICE_DEBUG("fake close");
+    return TRUE;
+}
+
+/* OUTPUT */
+
+struct _SpiceVmcOutputStream
+{
+    GOutputStream parent_instance;
+
+    SpiceChannel *channel; /* weak */
+};
+
+struct _SpiceVmcOutputStreamClass
+{
+    GOutputStreamClass parent_class;
+};
+
+static gssize   spice_vmc_output_stream_write_fn     (GOutputStream   *stream,
+                                                      const void      *buffer,
+                                                      gsize            count,
+                                                      GCancellable    *cancellable,
+                                                      GError         **error);
+static gssize   spice_vmc_output_stream_write_finish (GOutputStream   *stream,
+                                                      GAsyncResult    *result,
+                                                      GError         **error);
+static void     spice_vmc_output_stream_write_async  (GOutputStream   *stream,
+                                                      const void      *buffer,
+                                                      gsize            count,
+                                                      int              io_priority,
+                                                      GCancellable    *cancellable,
+                                                      GAsyncReadyCallback callback,
+                                                      gpointer         user_data);
+
+G_DEFINE_TYPE(SpiceVmcOutputStream, spice_vmc_output_stream, G_TYPE_OUTPUT_STREAM)
+
+
+static void
+spice_vmc_output_stream_class_init(SpiceVmcOutputStreamClass *klass)
+{
+    GOutputStreamClass *ostream_class;
+
+    ostream_class = G_OUTPUT_STREAM_CLASS(klass);
+    ostream_class->write_fn = spice_vmc_output_stream_write_fn;
+    ostream_class->write_async = spice_vmc_output_stream_write_async;
+    ostream_class->write_finish = spice_vmc_output_stream_write_finish;
+}
+
+static void
+spice_vmc_output_stream_init(SpiceVmcOutputStream *self)
+{
+}
+
+static SpiceVmcOutputStream *
+spice_vmc_output_stream_new(SpiceChannel *channel)
+{
+    SpiceVmcOutputStream *self;
+
+    self = g_object_new(SPICE_TYPE_VMC_OUTPUT_STREAM, NULL);
+    self->channel = channel;
+
+    return self;
+}
+
+static gssize
+spice_vmc_output_stream_write_fn(GOutputStream   *stream,
+                                 const void      *buffer,
+                                 gsize            count,
+                                 GCancellable    *cancellable,
+                                 GError         **error)
+{
+    SpiceVmcOutputStream *self = SPICE_VMC_OUTPUT_STREAM(stream);
+    SpiceMsgOut *msg_out;
+
+    msg_out = spice_msg_out_new(SPICE_CHANNEL(self->channel),
+                                SPICE_MSGC_SPICEVMC_DATA);
+    spice_marshaller_add(msg_out->marshaller, buffer, count);
+    spice_msg_out_send(msg_out);
+
+    return count;
+}
+
+static gssize
+spice_vmc_output_stream_write_finish(GOutputStream *stream,
+                                     GAsyncResult *simple,
+                                     GError **error)
+{
+    SpiceVmcOutputStream *self = SPICE_VMC_OUTPUT_STREAM(stream);
+    GSimpleAsyncResult *res =
+        g_simple_async_result_get_op_res_gpointer(G_SIMPLE_ASYNC_RESULT(simple));
+
+    SPICE_DEBUG("spicevmc write finish");
+    return spice_vmc_write_finish(self->channel, G_ASYNC_RESULT(res), error);
+}
+
+static void
+write_cb(GObject *source_object,
+         GAsyncResult *res,
+         gpointer user_data)
+{
+    GSimpleAsyncResult *simple = user_data;
+
+    g_simple_async_result_set_op_res_gpointer(simple, res, NULL);
+
+    g_simple_async_result_complete(simple);
+    g_object_unref(simple);
+}
+
+static void
+spice_vmc_output_stream_write_async(GOutputStream *stream,
+                                    const void *buffer,
+                                    gsize count,
+                                    int io_priority,
+                                    GCancellable *cancellable,
+                                    GAsyncReadyCallback callback,
+                                    gpointer user_data)
+{
+    SpiceVmcOutputStream *self = SPICE_VMC_OUTPUT_STREAM(stream);
+    GSimpleAsyncResult *simple;
+
+    SPICE_DEBUG("spicevmc write async");
+    /* an AsyncResult to forward async op to channel */
+    simple = g_simple_async_result_new(G_OBJECT(self), callback, user_data,
+                                       spice_vmc_output_stream_write_async);
+
+    spice_vmc_write_async(self->channel, buffer, count,
+                          cancellable, write_cb,
+                          simple);
+}
+
+/* STREAM */
+
+struct _SpiceVmcStream
+{
+    GIOStream parent_instance;
+
+    SpiceChannel *channel; /* weak */
+    SpiceVmcInputStream *in;
+    SpiceVmcOutputStream *out;
+};
+
+struct _SpiceVmcStreamClass
+{
+    GIOStreamClass parent_class;
+};
+
+static void            spice_vmc_stream_finalize          (GObject   *object);
+static GInputStream *  spice_vmc_stream_get_input_stream  (GIOStream *stream);
+static GOutputStream * spice_vmc_stream_get_output_stream (GIOStream *stream);
+
+G_DEFINE_TYPE(SpiceVmcStream, spice_vmc_stream, G_TYPE_IO_STREAM)
+
+static void
+spice_vmc_stream_class_init(SpiceVmcStreamClass *klass)
+{
+    GObjectClass *object_class;
+    GIOStreamClass *iostream_class;
+
+    object_class = G_OBJECT_CLASS(klass);
+    object_class->finalize = spice_vmc_stream_finalize;
+
+    iostream_class = G_IO_STREAM_CLASS(klass);
+    iostream_class->get_input_stream = spice_vmc_stream_get_input_stream;
+    iostream_class->get_output_stream = spice_vmc_stream_get_output_stream;
+}
+
+static void
+spice_vmc_stream_finalize(GObject *object)
+{
+    SpiceVmcStream *self = SPICE_VMC_STREAM(object);
+
+    g_clear_object(&self->in);
+    g_clear_object(&self->out);
+
+    G_OBJECT_CLASS(spice_vmc_stream_parent_class)->finalize(object);
+}
+
+static void
+spice_vmc_stream_init(SpiceVmcStream *self)
+{
+}
+
+G_GNUC_INTERNAL SpiceVmcStream *
+spice_vmc_stream_new(SpiceChannel *channel)
+{
+    SpiceVmcStream *self;
+
+    self = g_object_new(SPICE_TYPE_VMC_STREAM, NULL);
+    self->channel = channel;
+
+    return self;
+}
+
+static GInputStream *
+spice_vmc_stream_get_input_stream(GIOStream *stream)
+{
+    SpiceVmcStream *self = SPICE_VMC_STREAM(stream);
+
+    if (!self->in)
+        self->in = spice_vmc_input_stream_new();
+
+    return G_INPUT_STREAM(self->in);
+}
+
+static GOutputStream *
+spice_vmc_stream_get_output_stream(GIOStream *stream)
+{
+    SpiceVmcStream *self = SPICE_VMC_STREAM(stream);
+
+    if (!self->out)
+        self->out = spice_vmc_output_stream_new(self->channel);
+
+    return G_OUTPUT_STREAM(self->out);
+}
diff --git a/src/vmcstream.h b/src/vmcstream.h
new file mode 100644
index 0000000..1316b77
--- /dev/null
+++ b/src/vmcstream.h
@@ -0,0 +1,81 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2013 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_VMC_STREAM_H__
+#define __SPICE_VMC_STREAM_H__
+
+#include <gio/gio.h>
+
+#include "spice-types.h"
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_VMC_INPUT_STREAM         (spice_vmc_input_stream_get_type ())
+#define SPICE_VMC_INPUT_STREAM(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), SPICE_TYPE_VMC_INPUT_STREAM, SpiceVmcInputStream))
+#define SPICE_VMC_INPUT_STREAM_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), SPICE_TYPE_VMC_INPUT_STREAM, SpiceVmcInputStreamClass))
+#define SPICE_IS_VMC_INPUT_STREAM(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), SPICE_TYPE_VMC_INPUT_STREAM))
+#define SPICE_IS_VMC_INPUT_STREAM_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), SPICE_TYPE_VMC_INPUT_STREAM))
+#define SPICE_VMC_INPUT_STREAM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), SPICE_TYPE_VMC_INPUT_STREAM, SpiceVmcInputStreamClass))
+
+typedef struct _SpiceVmcInputStreamClass     SpiceVmcInputStreamClass;
+typedef struct _SpiceVmcInputStream          SpiceVmcInputStream;
+
+GType          spice_vmc_input_stream_get_type   (void) G_GNUC_CONST;
+void           spice_vmc_input_stream_co_data    (SpiceVmcInputStream *input,
+                                                  const gpointer data,
+                                                  gsize size);
+
+void           spice_vmc_input_stream_read_all_async(GInputStream        *stream,
+                                                     void                *buffer,
+                                                     gsize                count,
+                                                     int                  io_priority,
+                                                     GCancellable        *cancellable,
+                                                     GAsyncReadyCallback  callback,
+                                                     gpointer             user_data);
+gssize         spice_vmc_input_stream_read_all_finish(GInputStream       *stream,
+                                                      GAsyncResult       *result,
+                                                      GError            **error);
+
+
+#define SPICE_TYPE_VMC_OUTPUT_STREAM         (spice_vmc_output_stream_get_type ())
+#define SPICE_VMC_OUTPUT_STREAM(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), SPICE_TYPE_VMC_OUTPUT_STREAM, SpiceVmcOutputStream))
+#define SPICE_VMC_OUTPUT_STREAM_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), SPICE_TYPE_VMC_OUTPUT_STREAM, SpiceVmcOutputStreamClass))
+#define SPICE_IS_VMC_OUTPUT_STREAM(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), SPICE_TYPE_VMC_OUTPUT_STREAM))
+#define SPICE_IS_VMC_OUTPUT_STREAM_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), SPICE_TYPE_VMC_OUTPUT_STREAM))
+#define SPICE_VMC_OUTPUT_STREAM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), SPICE_TYPE_VMC_OUTPUT_STREAM, SpiceVmcOutputStreamClass))
+
+typedef struct _SpiceVmcOutputStreamClass     SpiceVmcOutputStreamClass;
+typedef struct _SpiceVmcOutputStream          SpiceVmcOutputStream;
+
+GType           spice_vmc_output_stream_get_type (void) G_GNUC_CONST;
+
+#define SPICE_TYPE_VMC_STREAM                (spice_vmc_stream_get_type ())
+#define SPICE_VMC_STREAM(o)                  (G_TYPE_CHECK_INSTANCE_CAST ((o), SPICE_TYPE_VMC_STREAM, SpiceVmcStream))
+#define SPICE_VMC_STREAM_CLASS(k)            (G_TYPE_CHECK_CLASS_CAST((k), SPICE_TYPE_VMC_STREAM, SpiceVmcStreamClass))
+#define SPICE_IS_VMC_STREAM(o)               (G_TYPE_CHECK_INSTANCE_TYPE ((o), SPICE_TYPE_VMC_STREAM))
+#define SPICE_IS_VMC_STREAM_CLASS(k)         (G_TYPE_CHECK_CLASS_TYPE ((k), SPICE_TYPE_VMC_STREAM))
+#define SPICE_VMC_STREAM_GET_CLASS(o)        (G_TYPE_INSTANCE_GET_CLASS ((o), SPICE_TYPE_VMC_STREAM, SpiceVmcStreamClass))
+
+typedef struct _SpiceVmcStreamClass           SpiceVmcStreamClass;
+typedef struct _SpiceVmcStream                SpiceVmcStream;
+
+GType           spice_vmc_stream_get_type        (void) G_GNUC_CONST;
+SpiceVmcStream* spice_vmc_stream_new             (SpiceChannel *channel);
+
+G_END_DECLS
+
+#endif /* __SPICE_VMC_STREAM_H__ */
diff --git a/src/vncdisplaykeymap.c b/src/vncdisplaykeymap.c
new file mode 100644
index 0000000..6bf351f
--- /dev/null
+++ b/src/vncdisplaykeymap.c
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2008  Anthony Liguori <anthony at codemonkey.ws>
+ * Copyright (C) 2009-2010 Daniel P. Berrange <dan at berrange.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+#include "config.h"
+
+#include <gtk/gtk.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkkeysyms.h>
+#include "gtk-compat.h"
+#include "vncdisplaykeymap.h"
+
+#include "spice-util.h"
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "vnc-keymap"
+#define VNC_DEBUG(message) SPICE_DEBUG(message);
+
+/*
+ * This table is taken from QEMU x_keymap.c, under the terms:
+ *
+ * Copyright (c) 2003 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+
+/* Compatability code to allow build on Gtk2 and Gtk3 */
+#ifndef GDK_Tab
+#define GDK_Tab GDK_KEY_Tab
+#endif
+
+/* keycode translation for sending ISO_Left_Send
+ * to vncserver
+ */
+static struct {
+	GdkKeymapKey *keys;
+	gint n_keys;
+	guint keyval;
+} untranslated_keys[] = {{NULL, 0, GDK_Tab}};
+
+static unsigned int ref_count_for_untranslated_keys = 0;
+
+#ifdef GDK_WINDOWING_WAYLAND
+#include <gdk/gdkwayland.h>
+#endif
+
+#ifdef GDK_WINDOWING_BROADWAY
+#include <gdk/gdkbroadway.h>
+#endif
+
+#if defined(GDK_WINDOWING_X11) || defined(GDK_WINDOWING_WAYLAND)
+/* Xorg Linux + evdev (offset evdev keycodes) */
+#include "vncdisplaykeymap_xorgevdev2xtkbd.c"
+#endif
+
+#ifdef GDK_WINDOWING_X11
+#include <gdk/gdkx.h>
+#include <X11/XKBlib.h>
+#include <stdbool.h>
+#include <string.h>
+
+/* Xorg Linux + kbd (offset + mangled XT keycodes) */
+#include "vncdisplaykeymap_xorgkbd2xtkbd.c"
+/* Xorg OS-X aka XQuartz (offset OS-X keycodes) */
+#include "vncdisplaykeymap_xorgxquartz2xtkbd.c"
+/* Xorg Cygwin aka XWin (offset + mangled XT keycodes) */
+#include "vncdisplaykeymap_xorgxwin2xtkbd.c"
+
+/* Gtk2 compat */
+#ifndef GDK_IS_X11_WINDOW
+#define GDK_IS_X11_WINDOW(win) (win == win)
+#endif
+#endif
+
+#ifdef GDK_WINDOWING_WIN32
+/* Win32 native virtual keycodes */
+#include "vncdisplaykeymap_win322xtkbd.c"
+
+/* Gtk2 compat */
+#ifndef GDK_IS_WIN32_WINDOW
+#define GDK_IS_WIN32_WINDOW(win) (win == win)
+#endif
+#endif
+
+#ifdef GDK_WINDOWING_QUARTZ
+/* OS-X native keycodes */
+#include "vncdisplaykeymap_osx2xtkbd.c"
+
+/* Gtk2 compat */
+#ifndef GDK_IS_QUARTZ_WINDOW
+#define GDK_IS_QUARTZ_WINDOW(win) (win == win)
+#endif
+#endif
+
+#ifdef GDK_WINDOWING_BROADWAY
+/* X11 keysyms */
+#include "vncdisplaykeymap_x112xtkbd.c"
+
+/* Gtk2 compat */
+#ifndef GDK_IS_BROADWAY_WINDOW
+#define GDK_IS_BROADWAY_WINDOW(win) (win == win)
+#endif
+
+#endif
+
+#ifdef GDK_WINDOWING_X11
+
+#define STRPREFIX(a,b) (strncmp((a),(b),strlen((b))) == 0)
+
+static gboolean check_for_xwin(GdkDisplay *dpy)
+{
+	char *vendor = ServerVendor(gdk_x11_display_get_xdisplay(dpy));
+
+	if (strstr(vendor, "Cygwin/X"))
+		return TRUE;
+
+	return FALSE;
+}
+
+static gboolean check_for_xquartz(GdkDisplay *dpy)
+{
+	int nextensions;
+	int i;
+	gboolean match = FALSE;
+	char **extensions = XListExtensions(gdk_x11_display_get_xdisplay(dpy),
+					    &nextensions);
+	for (i = 0 ; extensions != NULL && i < nextensions ; i++) {
+		if (strcmp(extensions[i], "Apple-WM") == 0 ||
+		    strcmp(extensions[i], "Apple-DRI") == 0)
+			match = TRUE;
+	}
+	if (extensions)
+		XFreeExtensionList(extensions);
+
+	return match;
+}
+#endif
+
+const guint16 *vnc_display_keymap_gdk2xtkbd_table(GdkWindow *window,
+                                                  size_t *maplen)
+{
+#ifdef GDK_WINDOWING_X11
+	if (GDK_IS_X11_WINDOW(window)) {
+		XkbDescPtr desc;
+		const gchar *keycodes = NULL;
+                GdkDisplay *dpy = gdk_window_get_display(window);
+
+		/* There is no easy way to determine what X11 server
+		 * and platform & keyboard driver is in use. Thus we
+		 * do best guess heuristics.
+		 *
+		 * This will need more work for people with other
+		 * X servers..... patches welcomed.
+		 */
+
+		Display *xdisplay = gdk_x11_display_get_xdisplay(dpy);
+		desc = XkbGetMap(xdisplay,
+				      XkbGBN_AllComponentsMask,
+				      XkbUseCoreKbd);
+		if (desc) {
+			if (XkbGetNames(xdisplay, XkbKeycodesNameMask, desc) == Success) {
+				keycodes = gdk_x11_get_xatom_name(desc->names->keycodes);
+				if (!keycodes)
+					g_warning("could not lookup keycode name");
+			}
+			XkbFreeKeyboard(desc, XkbGBN_AllComponentsMask, True);
+		}
+
+		if (check_for_xwin(dpy)) {
+			VNC_DEBUG("Using xwin keycode mapping");
+			*maplen = G_N_ELEMENTS(keymap_xorgxwin2xtkbd);
+			return keymap_xorgxwin2xtkbd;
+		} else if (check_for_xquartz(dpy)) {
+			VNC_DEBUG("Using xquartz keycode mapping");
+			*maplen = G_N_ELEMENTS(keymap_xorgxquartz2xtkbd);
+			return keymap_xorgxquartz2xtkbd;
+		} else if (keycodes && STRPREFIX(keycodes, "evdev")) {
+			VNC_DEBUG("Using evdev keycode mapping");
+			*maplen = G_N_ELEMENTS(keymap_xorgevdev2xtkbd);
+			return keymap_xorgevdev2xtkbd;
+		} else if (keycodes && STRPREFIX(keycodes, "xfree86")) {
+			VNC_DEBUG("Using xfree86 keycode mapping");
+			*maplen = G_N_ELEMENTS(keymap_xorgkbd2xtkbd);
+			return keymap_xorgkbd2xtkbd;
+		} else {
+			g_warning("Unknown keycode mapping '%s'.\n"
+				  "Please report to gtk-vnc-list at gnome.org\n"
+				  "including the following information:\n"
+				  "\n"
+				  "  - Operating system\n"
+				  "  - GDK build\n"
+				  "  - X11 Server\n"
+				  "  - xprop -root\n"
+				  "  - xdpyinfo\n",
+				  keycodes);
+			return NULL;
+		}
+	}
+#endif
+
+#ifdef GDK_WINDOWING_WIN32
+	if (GDK_IS_WIN32_WINDOW(window)) {
+		VNC_DEBUG("Using Win32 virtual keycode mapping");
+		*maplen = G_N_ELEMENTS(keymap_win322xtkbd);
+		return keymap_win322xtkbd;
+	}
+#endif
+
+#ifdef GDK_WINDOWING_QUARTZ
+	if (GDK_IS_QUARTZ_WINDOW(window)) {
+		VNC_DEBUG("Using OS-X virtual keycode mapping");
+		*maplen = G_N_ELEMENTS(keymap_osx2xtkbd);
+		return keymap_osx2xtkbd;
+	}
+#endif
+
+#ifdef GDK_WINDOWING_WAYLAND
+	if (GDK_IS_WAYLAND_WINDOW(window)) {
+		VNC_DEBUG("Using Wayland Xorg/evdev virtual keycode mapping");
+		*maplen = G_N_ELEMENTS(keymap_xorgevdev2xtkbd);
+		return keymap_xorgevdev2xtkbd;
+        }
+#endif
+
+#ifdef GDK_WINDOWING_BROADWAY
+	if (GDK_IS_BROADWAY_WINDOW(window)) {
+                g_warning("experimental: using broadway, x11 virtual keysym mapping - with very limited support. See also https://bugzilla.gnome.org/show_bug.cgi?id=700105");
+
+			*maplen = G_N_ELEMENTS(keymap_x112xtkbd);
+			return keymap_x112xtkbd;
+        }
+#endif
+
+	g_warning("Unsupported GDK Windowing platform.\n"
+		  "Disabling extended keycode tables.\n"
+		  "Please report to gtk-vnc-list at gnome.org\n"
+		  "including the following information:\n"
+		  "\n"
+		  "  - Operating system\n"
+		  "  - GDK Windowing system build\n");
+	return NULL;
+}
+
+guint16 vnc_display_keymap_gdk2xtkbd(const guint16 *keycode_map,
+				     size_t keycode_maplen,
+				     guint16 keycode)
+{
+	if (!keycode_map)
+		return 0;
+	if (keycode >= keycode_maplen)
+		return 0;
+	return keycode_map[keycode];
+}
+
+/* Set the keymap entries */
+void vnc_display_keyval_set_entries(void)
+{
+	size_t i;
+	if (ref_count_for_untranslated_keys == 0)
+		for (i = 0; i < sizeof(untranslated_keys) / sizeof(untranslated_keys[0]); i++)
+			gdk_keymap_get_entries_for_keyval(gdk_keymap_get_default(),
+							  untranslated_keys[i].keyval,
+							  &untranslated_keys[i].keys,
+							  &untranslated_keys[i].n_keys);
+	ref_count_for_untranslated_keys++;
+}
+
+/* Free the keymap entries */
+void vnc_display_keyval_free_entries(void)
+{
+	size_t i;
+
+	if (ref_count_for_untranslated_keys == 0)
+		return;
+
+	ref_count_for_untranslated_keys--;
+	if (ref_count_for_untranslated_keys == 0)
+		for (i = 0; i < sizeof(untranslated_keys) / sizeof(untranslated_keys[0]); i++)
+			g_free(untranslated_keys[i].keys);
+
+}
+
+/* Get the keyval from the keycode without the level. */
+guint vnc_display_keyval_from_keycode(guint keycode, guint keyval)
+{
+	size_t i;
+	for (i = 0; i < sizeof(untranslated_keys) / sizeof(untranslated_keys[0]); i++) {
+		if (keycode == untranslated_keys[i].keys[0].keycode) {
+			return untranslated_keys[i].keyval;
+		}
+	}
+
+	return keyval;
+}
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ *  tab-width: 8
+ * End:
+ */
diff --git a/src/vncdisplaykeymap.h b/src/vncdisplaykeymap.h
new file mode 100644
index 0000000..3ec55d5
--- /dev/null
+++ b/src/vncdisplaykeymap.h
@@ -0,0 +1,36 @@
+/*
+ * GTK VNC Widget
+ *
+ * Copyright (C) 2006  Anthony Liguori <anthony at codemonkey.ws>
+ * Copyright (C) 2009-2010 Daniel P. Berrange <dan at berrange.com>
+ *
+ * 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.0 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+#ifndef VNC_DISPLAY_KEYMAP_H
+#define VNC_DISPLAY_KEYMAP_H
+
+#include <glib.h>
+
+const guint16 *vnc_display_keymap_gdk2xtkbd_table(GdkWindow *window,
+                                                  size_t *maplen);
+guint16 vnc_display_keymap_gdk2xtkbd(const guint16 *keycode_map,
+                                     size_t keycode_maplen,
+                                     guint16 keycode);
+void vnc_display_keyval_set_entries(void);
+void vnc_display_keyval_free_entries(void);
+guint vnc_display_keyval_from_keycode(guint keycode, guint keyval);
+
+#endif /* VNC_DISPLAY_KEYMAP_H */
diff --git a/src/win-usb-clerk.h b/src/win-usb-clerk.h
new file mode 100644
index 0000000..24da3b4
--- /dev/null
+++ b/src/win-usb-clerk.h
@@ -0,0 +1,36 @@
+#ifndef _H_USBCLERK
+#define _H_USBCLERK
+
+#include <windows.h>
+
+#define USB_CLERK_PIPE_NAME     TEXT("\\\\.\\pipe\\usbclerkpipe")
+#define USB_CLERK_MAGIC         0xDADA
+#define USB_CLERK_VERSION       0x0003
+
+typedef struct USBClerkHeader {
+    UINT16 magic;
+    UINT16 version;
+    UINT16 type;
+    UINT16 size;
+} USBClerkHeader;
+
+enum {
+    USB_CLERK_DRIVER_INSTALL = 1,
+    USB_CLERK_DRIVER_REMOVE,
+    USB_CLERK_REPLY,
+    USB_CLERK_DRIVER_SESSION_INSTALL,
+    USB_CLERK_END_MESSAGE,
+};
+
+typedef struct USBClerkDriverOp {
+    USBClerkHeader hdr;
+    UINT16 vid;
+    UINT16 pid;
+} USBClerkDriverOp;
+
+typedef struct USBClerkReply {
+    USBClerkHeader hdr;
+    UINT32 status;
+} USBClerkReply;
+
+#endif
diff --git a/src/win-usb-dev.c b/src/win-usb-dev.c
new file mode 100644
index 0000000..1e4b2d6
--- /dev/null
+++ b/src/win-usb-dev.c
@@ -0,0 +1,542 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2012 Red Hat, Inc.
+
+   Red Hat Authors:
+   Arnon Gilboa <agilboa at redhat.com>
+   Uri Lublin   <uril at redhat.com>
+
+   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 <windows.h>
+#include <libusb.h>
+#include "win-usb-dev.h"
+#include "spice-marshal.h"
+#include "spice-util.h"
+#include "usbutil.h"
+
+#define G_UDEV_CLIENT_GET_PRIVATE(obj) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((obj), G_UDEV_TYPE_CLIENT, GUdevClientPrivate))
+
+struct _GUdevClientPrivate {
+    libusb_context *ctx;
+    gssize udev_list_size;
+    GList *udev_list;
+    HWND hwnd;
+};
+
+#define G_UDEV_CLIENT_WINCLASS_NAME  TEXT("G_UDEV_CLIENT")
+
+static void g_udev_client_initable_iface_init(GInitableIface  *iface);
+
+G_DEFINE_TYPE_WITH_CODE(GUdevClient, g_udev_client, G_TYPE_OBJECT,
+                        G_IMPLEMENT_INTERFACE(G_TYPE_INITABLE, g_udev_client_initable_iface_init));
+
+
+typedef struct _GUdevDeviceInfo GUdevDeviceInfo;
+
+struct _GUdevDeviceInfo {
+    guint16 bus;
+    guint16 addr;
+    guint16 vid;
+    guint16 pid;
+    guint16 class;
+    gchar sclass[4];
+    gchar sbus[4];
+    gchar saddr[4];
+    gchar svid[8];
+    gchar spid[8];
+};
+
+struct _GUdevDevicePrivate
+{
+    /* FixMe: move above fields to this structure and access them directly */
+    GUdevDeviceInfo *udevinfo;
+};
+
+G_DEFINE_TYPE(GUdevDevice, g_udev_device, G_TYPE_OBJECT)
+
+
+enum
+{
+    UEVENT_SIGNAL,
+    LAST_SIGNAL,
+};
+
+static guint signals[LAST_SIGNAL] = { 0, };
+static GUdevClient *singleton = NULL;
+
+static GUdevDevice *g_udev_device_new(GUdevDeviceInfo *udevinfo);
+static LRESULT CALLBACK wnd_proc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam);
+static gboolean get_usb_dev_info(libusb_device *dev, GUdevDeviceInfo *udevinfo);
+
+//uncomment to debug gudev device lists.
+//#define DEBUG_GUDEV_DEVICE_LISTS
+
+#ifdef DEBUG_GUDEV_DEVICE_LISTS
+static void g_udev_device_print_list(GList *l, const gchar *msg);
+#else
+static void g_udev_device_print_list(GList *l, const gchar *msg) {}
+#endif
+static void g_udev_device_print(GUdevDevice *udev, const gchar *msg);
+
+static gboolean g_udev_skip_search(GUdevDevice *udev);
+
+GQuark g_udev_client_error_quark(void)
+{
+    return g_quark_from_static_string("win-gudev-client-error-quark");
+}
+
+GUdevClient *g_udev_client_new(const gchar* const *subsystems)
+{
+    if (!singleton) {
+        singleton = g_initable_new(G_UDEV_TYPE_CLIENT, NULL, NULL, NULL);
+        return singleton;
+    } else {
+        return g_object_ref(singleton);
+    }
+}
+
+
+/*
+ * devs [in,out] an empty devs list in, full devs list out
+ * Returns: number-of-devices, or a negative value on error.
+ */
+static ssize_t
+g_udev_client_list_devices(GUdevClient *self, GList **devs,
+                           GError **err, const gchar *name)
+{
+    gssize rc;
+    libusb_device **lusb_list, **dev;
+    GUdevClientPrivate *priv;
+    GUdevDeviceInfo *udevinfo;
+    GUdevDevice *udevice;
+    ssize_t n;
+
+    g_return_val_if_fail(G_UDEV_IS_CLIENT(self), -1);
+    g_return_val_if_fail(devs != NULL, -2);
+
+    priv = self->priv;
+
+    g_return_val_if_fail(self->priv->ctx != NULL, -3);
+
+    rc = libusb_get_device_list(priv->ctx, &lusb_list);
+    if (rc < 0) {
+        const char *errstr = spice_usbutil_libusb_strerror(rc);
+        g_warning("%s: libusb_get_device_list failed", name);
+        g_set_error(err, G_UDEV_CLIENT_ERROR, G_UDEV_CLIENT_LIBUSB_FAILED,
+                    "%s: Error getting device list from libusb: %s [%"G_GSSIZE_FORMAT"]",
+                    name, errstr, rc);
+        return -4;
+    }
+
+    n = 0;
+    for (dev = lusb_list; *dev; dev++) {
+        udevinfo = g_new0(GUdevDeviceInfo, 1);
+        get_usb_dev_info(*dev, udevinfo);
+        udevice = g_udev_device_new(udevinfo);
+        if (g_udev_skip_search(udevice)) {
+            g_object_unref(udevice);
+            continue;
+        }
+        *devs = g_list_prepend(*devs, udevice);
+        n++;
+    }
+    libusb_free_device_list(lusb_list, 1);
+
+    return n;
+}
+
+static void g_udev_client_free_device_list(GList **devs)
+{
+    g_return_if_fail(devs != NULL);
+    if (*devs) {
+        g_list_free_full(*devs, g_object_unref);
+        *devs = NULL;
+    }
+}
+
+
+static gboolean
+g_udev_client_initable_init(GInitable *initable, GCancellable *cancellable,
+                            GError **err)
+{
+    GUdevClient *self;
+    GUdevClientPrivate *priv;
+    WNDCLASS wcls;
+    int rc;
+
+    g_return_val_if_fail(G_UDEV_IS_CLIENT(initable), FALSE);
+    g_return_val_if_fail(cancellable == NULL, FALSE);
+
+    self = G_UDEV_CLIENT(initable);
+    priv = self->priv;
+
+    rc = libusb_init(&priv->ctx);
+    if (rc < 0) {
+        const char *errstr = spice_usbutil_libusb_strerror(rc);
+        g_warning("Error initializing USB support: %s [%i]", errstr, rc);
+        g_set_error(err, G_UDEV_CLIENT_ERROR, G_UDEV_CLIENT_LIBUSB_FAILED,
+                    "Error initializing USB support: %s [%i]", errstr, rc);
+        return FALSE;
+    }
+
+    /* get initial device list */
+    priv->udev_list_size = g_udev_client_list_devices(self, &priv->udev_list,
+                                                      err, __FUNCTION__);
+    if (priv->udev_list_size < 0) {
+        goto g_udev_client_init_failed;
+    }
+
+    g_udev_device_print_list(priv->udev_list, "init: first list is: ");
+
+    /* create a hidden window */
+    memset(&wcls, 0, sizeof(wcls));
+    wcls.lpfnWndProc = wnd_proc;
+    wcls.lpszClassName = G_UDEV_CLIENT_WINCLASS_NAME;
+    if (!RegisterClass(&wcls)) {
+        DWORD e = GetLastError();
+        g_warning("RegisterClass failed , %ld", (long)e);
+        g_set_error(err, G_UDEV_CLIENT_ERROR, G_UDEV_CLIENT_WINAPI_FAILED,
+                    "RegisterClass failed: %ld", (long)e);
+        goto g_udev_client_init_failed;
+    }
+    priv->hwnd = CreateWindow(G_UDEV_CLIENT_WINCLASS_NAME,
+                              NULL, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL);
+    if (!priv->hwnd) {
+        DWORD e = GetLastError();
+        g_warning("CreateWindow failed: %ld", (long)e);
+        g_set_error(err, G_UDEV_CLIENT_ERROR, G_UDEV_CLIENT_LIBUSB_FAILED,
+                    "CreateWindow failed: %ld", (long)e);
+        goto g_udev_client_init_failed_unreg;
+    }
+
+    return TRUE;
+
+ g_udev_client_init_failed_unreg:
+    UnregisterClass(G_UDEV_CLIENT_WINCLASS_NAME, NULL);
+ g_udev_client_init_failed:
+    libusb_exit(priv->ctx);
+    priv->ctx = NULL;
+
+    return FALSE;
+}
+
+static void g_udev_client_initable_iface_init(GInitableIface *iface)
+{
+    iface->init = g_udev_client_initable_init;
+}
+
+GList *g_udev_client_query_by_subsystem(GUdevClient *self, const gchar *subsystem)
+{
+    GList *l = g_list_copy(self->priv->udev_list);
+    g_list_foreach(l, (GFunc)g_object_ref, NULL);
+    return l;
+}
+
+static void g_udev_client_init(GUdevClient *self)
+{
+    self->priv = G_UDEV_CLIENT_GET_PRIVATE(self);
+}
+
+static void g_udev_client_finalize(GObject *gobject)
+{
+    GUdevClient *self = G_UDEV_CLIENT(gobject);
+    GUdevClientPrivate *priv = self->priv;
+
+    singleton = NULL;
+    DestroyWindow(priv->hwnd);
+    UnregisterClass(G_UDEV_CLIENT_WINCLASS_NAME, NULL);
+    g_udev_client_free_device_list(&priv->udev_list);
+
+    /* free libusb context initializing by libusb_init() */
+    g_warn_if_fail(priv->ctx != NULL);
+    libusb_exit(priv->ctx);
+
+    /* Chain up to the parent class */
+    if (G_OBJECT_CLASS(g_udev_client_parent_class)->finalize)
+        G_OBJECT_CLASS(g_udev_client_parent_class)->finalize(gobject);
+}
+
+static void g_udev_client_class_init(GUdevClientClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+
+    gobject_class->finalize = g_udev_client_finalize;
+
+    signals[UEVENT_SIGNAL] =
+        g_signal_new("uevent",
+                     G_OBJECT_CLASS_TYPE(klass),
+                     G_SIGNAL_RUN_FIRST,
+                     G_STRUCT_OFFSET(GUdevClientClass, uevent),
+                     NULL, NULL,
+                     g_cclosure_user_marshal_VOID__BOXED_BOXED,
+                     G_TYPE_NONE,
+                     2,
+                     G_TYPE_STRING,
+                     G_UDEV_TYPE_DEVICE);
+
+    g_type_class_add_private(klass, sizeof(GUdevClientPrivate));
+}
+
+static gboolean get_usb_dev_info(libusb_device *dev, GUdevDeviceInfo *udevinfo)
+{
+    struct libusb_device_descriptor desc;
+
+    g_return_val_if_fail(dev, FALSE);
+    g_return_val_if_fail(udevinfo, FALSE);
+
+    if (libusb_get_device_descriptor(dev, &desc) < 0) {
+        g_warning("cannot get device descriptor %p", dev);
+        return FALSE;
+    }
+
+    udevinfo->bus   = libusb_get_bus_number(dev);
+    udevinfo->addr  = libusb_get_device_address(dev);
+    udevinfo->class = desc.bDeviceClass;
+    udevinfo->vid   = desc.idVendor;
+    udevinfo->pid   = desc.idProduct;
+    snprintf(udevinfo->sclass, sizeof(udevinfo->sclass), "%d", udevinfo->class);
+    snprintf(udevinfo->sbus,   sizeof(udevinfo->sbus),   "%d", udevinfo->bus);
+    snprintf(udevinfo->saddr,  sizeof(udevinfo->saddr),  "%d", udevinfo->addr);
+    snprintf(udevinfo->svid,   sizeof(udevinfo->svid),   "%d", udevinfo->vid);
+    snprintf(udevinfo->spid,   sizeof(udevinfo->spid),   "%d", udevinfo->pid);
+    return TRUE;
+}
+
+/* Only vid:pid are compared */
+static gboolean gudev_devices_are_equal(GUdevDevice *a, GUdevDevice *b)
+{
+    GUdevDeviceInfo *ai, *bi;
+    gboolean same_vid;
+    gboolean same_pid;
+
+    ai = a->priv->udevinfo;
+    bi = b->priv->udevinfo;
+
+    same_vid  = (ai->vid == bi->vid);
+    same_pid  = (ai->pid == bi->pid);
+
+    return (same_pid && same_vid);
+}
+
+
+/* Assumes each event stands for a single device change (at most) */
+static void handle_dev_change(GUdevClient *self)
+{
+    GUdevClientPrivate *priv = self->priv;
+    GUdevDevice *changed_dev = NULL;
+    ssize_t dev_count;
+    int is_dev_change;
+    GError *err = NULL;
+    GList *now_devs = NULL;
+    GList *llist, *slist; /* long-list and short-list*/
+    GList *lit, *sit; /* iterators for long-list and short-list */
+    GUdevDevice *ldev, *sdev; /* devices on long-list and short-list */
+
+    dev_count = g_udev_client_list_devices(self, &now_devs, &err,
+                                           __FUNCTION__);
+    g_return_if_fail(dev_count >= 0);
+
+    SPICE_DEBUG("number of current devices %"G_GSSIZE_FORMAT
+                ", I know about %"G_GSSIZE_FORMAT" devices",
+                dev_count, priv->udev_list_size);
+
+    is_dev_change = dev_count - priv->udev_list_size;
+    if (is_dev_change == 0) {
+        g_udev_client_free_device_list(&now_devs);
+        return;
+    }
+
+    if (is_dev_change > 0) {
+        llist  = now_devs;
+        slist = priv->udev_list;
+    } else {
+        llist = priv->udev_list;
+        slist  = now_devs;
+    }
+
+    g_udev_device_print_list(llist, "handle_dev_change: long list:");
+    g_udev_device_print_list(slist, "handle_dev_change: short list:");
+
+    /* Go over the longer list */
+    for (lit = g_list_first(llist); lit != NULL; lit=g_list_next(lit)) {
+        ldev = lit->data;
+        /* Look for dev in the shorther list */
+        for (sit = g_list_first(slist); sit != NULL; sit=g_list_next(sit)) {
+            sdev = sit->data;
+            if (gudev_devices_are_equal(ldev, sdev))
+                break;
+        }
+        if (sit == NULL) {
+            /* Found a device which appears only in the longer list */
+            changed_dev = ldev;
+            break;
+        }
+    }
+
+    if (!changed_dev) {
+        g_warning("couldn't find any device change");
+        goto leave;
+    }
+
+    if (is_dev_change > 0) {
+        g_udev_device_print(changed_dev, ">>> USB device inserted");
+        g_signal_emit(self, signals[UEVENT_SIGNAL], 0, "add", changed_dev);
+    } else {
+        g_udev_device_print(changed_dev, "<<< USB device removed");
+        g_signal_emit(self, signals[UEVENT_SIGNAL], 0, "remove", changed_dev);
+    }
+
+leave:
+    /* keep most recent info: free previous list, and keep current list */
+    g_udev_client_free_device_list(&priv->udev_list);
+    priv->udev_list = now_devs;
+    priv->udev_list_size = dev_count;
+}
+
+static LRESULT CALLBACK wnd_proc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
+{
+    /* Only DBT_DEVNODES_CHANGED recieved */
+    if (message == WM_DEVICECHANGE) {
+        handle_dev_change(singleton);
+    }
+    return DefWindowProc(hwnd, message, wparam, lparam);
+}
+
+/*** GUdevDevice ***/
+
+static void g_udev_device_finalize(GObject *object)
+{
+    GUdevDevice *device =  G_UDEV_DEVICE(object);
+
+    g_free(device->priv->udevinfo);
+    if (G_OBJECT_CLASS(g_udev_device_parent_class)->finalize != NULL)
+        (* G_OBJECT_CLASS(g_udev_device_parent_class)->finalize)(object);
+}
+
+static void g_udev_device_class_init(GUdevDeviceClass *klass)
+{
+    GObjectClass *gobject_class = (GObjectClass *) klass;
+
+    gobject_class->finalize = g_udev_device_finalize;
+    g_type_class_add_private (klass, sizeof(GUdevDevicePrivate));
+}
+
+static void g_udev_device_init(GUdevDevice *device)
+{
+    device->priv = G_TYPE_INSTANCE_GET_PRIVATE(device, G_UDEV_TYPE_DEVICE, GUdevDevicePrivate);
+}
+
+static GUdevDevice *g_udev_device_new(GUdevDeviceInfo *udevinfo)
+{
+    GUdevDevice *device;
+
+    g_return_val_if_fail(udevinfo != NULL, NULL);
+
+    device =  G_UDEV_DEVICE(g_object_new(G_UDEV_TYPE_DEVICE, NULL));
+    device->priv->udevinfo = udevinfo;
+    return device;
+}
+
+const gchar *g_udev_device_get_property(GUdevDevice *udev, const gchar *property)
+{
+    GUdevDeviceInfo* udevinfo;
+
+    g_return_val_if_fail(G_UDEV_DEVICE(udev), NULL);
+    g_return_val_if_fail(property != NULL, NULL);
+
+    udevinfo = udev->priv->udevinfo;
+    g_return_val_if_fail(udevinfo != NULL, NULL);
+
+    if (g_strcmp0(property, "BUSNUM") == 0) {
+        return udevinfo->sbus;
+    } else if (g_strcmp0(property, "DEVNUM") == 0) {
+        return udevinfo->saddr;
+    } else if (g_strcmp0(property, "DEVTYPE") == 0) {
+        return "usb_device";
+    } else if (g_strcmp0(property, "VID") == 0) {
+        return udevinfo->svid;
+    } else if (g_strcmp0(property, "PID") == 0) {
+        return udevinfo->spid;
+    }
+
+    g_warn_if_reached();
+    return NULL;
+}
+
+const gchar *g_udev_device_get_sysfs_attr(GUdevDevice *udev, const gchar *attr)
+{
+    GUdevDeviceInfo* udevinfo;
+
+    g_return_val_if_fail(G_UDEV_DEVICE(udev), NULL);
+    g_return_val_if_fail(attr != NULL, NULL);
+
+    udevinfo = udev->priv->udevinfo;
+    g_return_val_if_fail(udevinfo != NULL, NULL);
+
+
+    if (g_strcmp0(attr, "bDeviceClass") == 0) {
+        return udevinfo->sclass;
+    }
+    g_warn_if_reached();
+    return NULL;
+}
+
+#ifdef DEBUG_GUDEV_DEVICE_LISTS
+static void g_udev_device_print_list(GList *l, const gchar *msg)
+{
+    GList *it;
+
+    for (it = g_list_first(l); it != NULL; it=g_list_next(it)) {
+        g_udev_device_print(it->data, msg);
+    }
+}
+#endif
+
+static void g_udev_device_print(GUdevDevice *udev, const gchar *msg)
+{
+    GUdevDeviceInfo* udevinfo;
+
+    g_return_if_fail(G_UDEV_DEVICE(udev));
+
+    udevinfo = udev->priv->udevinfo;
+    g_return_if_fail(udevinfo != NULL);
+
+    SPICE_DEBUG("%s: %d.%d 0x%04x:0x%04x class %d", msg,
+                udevinfo->bus, udevinfo->addr,
+                udevinfo->vid, udevinfo->pid, udevinfo->class);
+}
+
+static gboolean g_udev_skip_search(GUdevDevice *udev)
+{
+    GUdevDeviceInfo* udevinfo;
+    gboolean skip;
+
+    g_return_val_if_fail(G_UDEV_DEVICE(udev), FALSE);
+
+    udevinfo = udev->priv->udevinfo;
+    g_return_val_if_fail(udevinfo != NULL, FALSE);
+
+    skip = ((udevinfo->addr == 0xff) ||  /* root hub (HCD) */
+#if defined(LIBUSBX_API_VERSION) && (LIBUSBX_API_VERSION >= 0x010000FF)
+            (udevinfo->addr == 1) || /* root hub addr for libusbx >= 1.0.13 */
+#endif
+            (udevinfo->class == LIBUSB_CLASS_HUB) || /* hub*/
+            (udevinfo->addr == 0)); /* bad address */
+    return skip;
+}
diff --git a/src/win-usb-dev.h b/src/win-usb-dev.h
new file mode 100644
index 0000000..b5c4fce
--- /dev/null
+++ b/src/win-usb-dev.h
@@ -0,0 +1,110 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2012 Red Hat, Inc.
+
+   Red Hat Authors:
+   Arnon Gilboa <agilboa at redhat.com>
+   Uri Lublin   <uril at redhat.com>
+
+   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 __WIN_USB_DEV_H__
+#define __WIN_USB_DEV_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+/* GUdevDevice */
+
+#define G_UDEV_TYPE_DEVICE         (g_udev_device_get_type())
+#define G_UDEV_DEVICE(o)           (G_TYPE_CHECK_INSTANCE_CAST((o), G_UDEV_TYPE_DEVICE, GUdevDevice))
+#define G_UDEV_DEVICE_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), G_UDEV_TYPE_DEVICE, GUdevDeviceClass))
+#define G_UDEV_IS_DEVICE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_UDEV_TYPE_DEVICE))
+#define G_UDEV_IS_DEVICE_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE((k), G_UDEV_TYPE_DEVICE))
+#define G_UDEV_DEVICE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), G_UDEV_TYPE_DEVICE, GUdevDeviceClass))
+
+typedef struct _GUdevDevice GUdevDevice;
+typedef struct _GUdevDeviceClass GUdevDeviceClass;
+typedef struct _GUdevDevicePrivate GUdevDevicePrivate;
+
+struct _GUdevDevice
+{
+  GObject parent;
+  GUdevDevicePrivate *priv;
+};
+
+struct _GUdevDeviceClass
+{
+  GObjectClass parent_class;
+};
+
+/* GUdevClient */
+
+#define G_UDEV_TYPE_CLIENT         (g_udev_client_get_type())
+#define G_UDEV_CLIENT(o)           (G_TYPE_CHECK_INSTANCE_CAST((o), G_UDEV_TYPE_CLIENT, GUdevClient))
+#define G_UDEV_CLIENT_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), G_UDEV_TYPE_CLIENT, GUdevClientClass))
+#define G_UDEV_IS_CLIENT(o)        (G_TYPE_CHECK_INSTANCE_TYPE((o), G_UDEV_TYPE_CLIENT))
+#define G_UDEV_IS_CLIENT_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE((k), G_UDEV_TYPE_CLIENT))
+#define G_UDEV_CLIENT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), G_UDEV_TYPE_CLIENT, GUdevClientClass))
+
+typedef struct _GUdevClient GUdevClient;
+typedef struct _GUdevClientClass GUdevClientClass;
+typedef struct _GUdevClientPrivate GUdevClientPrivate;
+
+struct _GUdevClient
+{
+    GObject parent;
+
+    GUdevClientPrivate *priv;
+};
+
+struct _GUdevClientClass
+{
+    GObjectClass parent_class;
+
+    /* signals */
+    void (*uevent)(GUdevClient *client, const gchar *action, GUdevDevice  *device);
+};
+
+GType g_udev_client_get_type(void) G_GNUC_CONST;
+GUdevClient *g_udev_client_new(const gchar* const *subsystems);
+GList *g_udev_client_query_by_subsystem(GUdevClient *client, const gchar *subsystem);
+
+GType g_udev_device_get_type(void) G_GNUC_CONST;
+const gchar *g_udev_device_get_property(GUdevDevice *udev, const gchar *property);
+const gchar *g_udev_device_get_sysfs_attr(GUdevDevice *udev, const gchar *attr);
+
+GQuark g_udev_client_error_quark(void);
+#define G_UDEV_CLIENT_ERROR g_udev_client_error_quark()
+
+/**
+ * GUdevClientError:
+ * @G_UDEV_CLIENT_ERROR_FAILED: generic error code
+ * @G_UDEV_CLIENT_LIBUSB_FAILED: a libusb call failed
+ * @G_UDEV_CLIENT_WINAPI_FAILED: a winapi call failed
+ *
+ * Error codes returned by spice-client API.
+ */
+typedef enum
+{
+    G_UDEV_CLIENT_ERROR_FAILED = 1,
+    G_UDEV_CLIENT_LIBUSB_FAILED,
+    G_UDEV_CLIENT_WINAPI_FAILED
+} GUdevClientError;
+
+
+G_END_DECLS
+
+#endif /* __WIN_USB_DEV_H__ */
diff --git a/src/win-usb-driver-install.c b/src/win-usb-driver-install.c
new file mode 100644
index 0000000..674a7c6
--- /dev/null
+++ b/src/win-usb-driver-install.c
@@ -0,0 +1,398 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2011 Red Hat, Inc.
+
+   Red Hat Authors:
+   Uri Lublin <uril at redhat.com>
+
+   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/>.
+*/
+
+/*
+ * Some notes:
+ * Each installer (instance) opens a named-pipe to talk with win-usb-clerk.
+ * Each installer (instance) requests driver installation for a single device.
+ */
+
+#include "config.h"
+
+#include <windows.h>
+#include <gio/gio.h>
+#include <gio/gwin32inputstream.h>
+#include <gio/gwin32outputstream.h>
+#include "spice-util.h"
+#include "win-usb-clerk.h"
+#include "win-usb-driver-install.h"
+#include "usb-device-manager-priv.h"
+
+/* ------------------------------------------------------------------ */
+/* gobject glue                                                       */
+
+#define SPICE_WIN_USB_DRIVER_GET_PRIVATE(obj)     \
+    (G_TYPE_INSTANCE_GET_PRIVATE ((obj), SPICE_TYPE_WIN_USB_DRIVER, SpiceWinUsbDriverPrivate))
+
+struct _SpiceWinUsbDriverPrivate {
+    USBClerkReply         reply;
+    GSimpleAsyncResult    *result;
+    GCancellable          *cancellable;
+    HANDLE                handle;
+    SpiceUsbDevice        *device;
+};
+
+
+
+G_DEFINE_TYPE(SpiceWinUsbDriver, spice_win_usb_driver, G_TYPE_OBJECT);
+
+static void spice_win_usb_driver_init(SpiceWinUsbDriver *self)
+{
+    self->priv = SPICE_WIN_USB_DRIVER_GET_PRIVATE(self);
+}
+
+static void spice_win_usb_driver_close(SpiceWinUsbDriver *self)
+{
+    if (self->priv->handle) {
+        CloseHandle(self->priv->handle);
+        self->priv->handle = 0;
+    }
+}
+
+static void spice_win_usb_driver_finalize(GObject *gobject)
+{
+    SpiceWinUsbDriver *self = SPICE_WIN_USB_DRIVER(gobject);
+    SpiceWinUsbDriverPrivate *priv = self->priv;
+
+    spice_win_usb_driver_close(self);
+    g_clear_object(&priv->result);
+
+    if (G_OBJECT_CLASS(spice_win_usb_driver_parent_class)->finalize)
+        G_OBJECT_CLASS(spice_win_usb_driver_parent_class)->finalize(gobject);
+}
+
+static void spice_win_usb_driver_class_init(SpiceWinUsbDriverClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+    gobject_class->finalize     = spice_win_usb_driver_finalize;
+
+    g_type_class_add_private(klass, sizeof(SpiceWinUsbDriverPrivate));
+}
+
+/* ------------------------------------------------------------------ */
+/* callbacks                                                          */
+
+void win_usb_driver_handle_reply_cb(GObject *gobject,
+                                    GAsyncResult *read_res,
+                                    gpointer user_data)
+{
+    SpiceWinUsbDriver *self;
+    SpiceWinUsbDriverPrivate *priv;
+
+    GInputStream *istream;
+    GError *err = NULL;
+    gssize bytes;
+
+    g_return_if_fail(SPICE_IS_WIN_USB_DRIVER(user_data));
+    self = SPICE_WIN_USB_DRIVER(user_data);
+    priv = self->priv;
+    istream = G_INPUT_STREAM(gobject);
+
+    bytes = g_input_stream_read_finish(istream, read_res, &err);
+
+    SPICE_DEBUG("Finished reading reply-msg from usbclerk: bytes=%ld "
+                "err_exist?=%d", (long)bytes, err!=NULL);
+
+    g_warn_if_fail(g_input_stream_close(istream, NULL, NULL));
+    g_clear_object(&istream);
+
+    if (err) {
+        g_warning("failed to read reply from usbclerk (%s)", err->message);
+        g_simple_async_result_take_error(priv->result, err);
+        goto failed_reply;
+    }
+
+    if (bytes == 0) {
+        g_warning("unexpected EOF from usbclerk");
+        g_simple_async_result_set_error(priv->result,
+                                        SPICE_WIN_USB_DRIVER_ERROR,
+                                        SPICE_WIN_USB_DRIVER_ERROR_FAILED,
+                                        "unexpected EOF from usbclerk");
+        goto failed_reply;
+    }
+
+    if (bytes != sizeof(priv->reply)) {
+        g_warning("usbclerk size mismatch: read %"G_GSSIZE_FORMAT" bytes,expected "
+                  "%"G_GSSIZE_FORMAT" (header %"G_GSSIZE_FORMAT", size in header %d)",
+                  bytes, sizeof(priv->reply), sizeof(priv->reply.hdr), priv->reply.hdr.size);
+        /* For now just warn, do not fail */
+    }
+
+    if (priv->reply.hdr.magic != USB_CLERK_MAGIC) {
+        g_warning("usbclerk magic mismatch: mine=0x%04x  server=0x%04x",
+                  USB_CLERK_MAGIC, priv->reply.hdr.magic);
+        g_simple_async_result_set_error(priv->result,
+                                        SPICE_WIN_USB_DRIVER_ERROR,
+                                        SPICE_WIN_USB_DRIVER_ERROR_MESSAGE,
+                                        "usbclerk magic mismatch");
+        goto failed_reply;
+    }
+
+    if (priv->reply.hdr.version != USB_CLERK_VERSION) {
+        g_warning("usbclerk version mismatch: mine=0x%04x  server=0x%04x",
+                  USB_CLERK_VERSION, priv->reply.hdr.version);
+        g_simple_async_result_set_error(priv->result,
+                                        SPICE_WIN_USB_DRIVER_ERROR,
+                                        SPICE_WIN_USB_DRIVER_ERROR_MESSAGE,
+                                        "usbclerk version mismatch");
+    }
+
+    if (priv->reply.hdr.type != USB_CLERK_REPLY) {
+        g_warning("usbclerk message with unexpected type %d",
+                  priv->reply.hdr.type);
+        g_simple_async_result_set_error(priv->result,
+                                        SPICE_WIN_USB_DRIVER_ERROR,
+                                        SPICE_WIN_USB_DRIVER_ERROR_MESSAGE,
+                                        "usbclerk message with unexpected type");
+        goto failed_reply;
+    }
+
+    if (priv->reply.hdr.size != bytes) {
+        g_warning("usbclerk message size mismatch: read %"G_GSSIZE_FORMAT" bytes  hdr.size=%d",
+                  bytes, priv->reply.hdr.size);
+        g_simple_async_result_set_error(priv->result,
+                                        SPICE_WIN_USB_DRIVER_ERROR,
+                                        SPICE_WIN_USB_DRIVER_ERROR_MESSAGE,
+                                        "usbclerk message with unexpected size");
+        goto failed_reply;
+    }
+
+ failed_reply:
+    g_simple_async_result_complete_in_idle(priv->result);
+    g_clear_object(&priv->result);
+}
+
+/* ------------------------------------------------------------------ */
+/* helper functions                                                   */
+
+static
+gboolean spice_win_usb_driver_send_request(SpiceWinUsbDriver *self, guint16 op,
+                                           guint16 vid, guint16 pid, GError **err)
+{
+    USBClerkDriverOp req;
+    GOutputStream *ostream;
+    SpiceWinUsbDriverPrivate *priv;
+    gsize bytes;
+    gboolean ret;
+
+    SPICE_DEBUG("sending a request to usbclerk service (op=%d vid=0x%04x pid=0x%04x",
+                op, vid, pid);
+
+    g_return_val_if_fail(SPICE_IS_WIN_USB_DRIVER(self), FALSE);
+    priv = self->priv;
+
+    memset(&req, 0, sizeof(req));
+    req.hdr.magic   = USB_CLERK_MAGIC;
+    req.hdr.version = USB_CLERK_VERSION;
+    req.hdr.type    = op;
+    req.hdr.size    = sizeof(req);
+    req.vid = vid;
+    req.pid = pid;
+
+    ostream = g_win32_output_stream_new(priv->handle, FALSE);
+
+    ret = g_output_stream_write_all(ostream, &req, sizeof(req), &bytes, NULL, err);
+    g_warn_if_fail(g_output_stream_close(ostream, NULL, NULL));
+    g_object_unref(ostream);
+    SPICE_DEBUG("write_all request returned %d written bytes %"G_GSIZE_FORMAT
+                " expecting %"G_GSIZE_FORMAT,
+                ret, bytes, sizeof(req));
+    return ret;
+}
+
+static
+void spice_win_usb_driver_read_reply_async(SpiceWinUsbDriver *self)
+{
+    SpiceWinUsbDriverPrivate *priv;
+    GInputStream  *istream;
+
+    g_return_if_fail(SPICE_IS_WIN_USB_DRIVER(self));
+    priv = self->priv;
+
+    SPICE_DEBUG("waiting for a reply from usbclerk");
+
+    istream = g_win32_input_stream_new(priv->handle, FALSE);
+
+    g_input_stream_read_async(istream, &priv->reply, sizeof(priv->reply),
+                              G_PRIORITY_DEFAULT, priv->cancellable,
+                              win_usb_driver_handle_reply_cb, self);
+}
+
+
+/* ------------------------------------------------------------------ */
+/* private api                                                        */
+
+
+G_GNUC_INTERNAL
+SpiceWinUsbDriver *spice_win_usb_driver_new(void)
+{
+    GObject *obj;
+
+    obj = g_object_new(SPICE_TYPE_WIN_USB_DRIVER, NULL);
+
+    return SPICE_WIN_USB_DRIVER(obj);
+}
+
+static
+void spice_win_usb_driver_op(SpiceWinUsbDriver *self,
+                             SpiceUsbDevice *device,
+                             guint16 op_type,
+                             GCancellable *cancellable,
+                             GAsyncReadyCallback callback,
+                             gpointer user_data)
+{
+    guint16 vid, pid;
+    GError *err = NULL;
+    GSimpleAsyncResult *result;
+    SpiceWinUsbDriverPrivate *priv;
+
+    g_return_if_fail(SPICE_IS_WIN_USB_DRIVER(self));
+    g_return_if_fail(device != NULL);
+
+    priv = self->priv;
+
+    result = g_simple_async_result_new(G_OBJECT(self), callback, user_data,
+                                       spice_win_usb_driver_op);
+
+    if (priv->result) { /* allow one install/uninstall request at a time */
+        g_warning("Another request exists -- try later");
+        g_simple_async_result_set_error(result,
+                  SPICE_WIN_USB_DRIVER_ERROR, SPICE_WIN_USB_DRIVER_ERROR_FAILED,
+                  "Another request exists -- try later");
+        goto failed_request;
+    }
+
+
+    vid = spice_usb_device_get_vid(device);
+    pid = spice_usb_device_get_pid(device);
+
+    if (! priv->handle ) {
+        SPICE_DEBUG("win-usb-driver-install: connecting to usbclerk named pipe");
+        priv->handle = CreateFile(USB_CLERK_PIPE_NAME,
+                                  GENERIC_READ | GENERIC_WRITE,
+                                  0, NULL,
+                                  OPEN_EXISTING,
+                                  FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
+                                  NULL);
+        if (priv->handle == INVALID_HANDLE_VALUE) {
+            DWORD errval  = GetLastError();
+            gchar *errstr = g_win32_error_message(errval);
+            g_warning("failed to create a named pipe to usbclerk (%ld) %s",
+                      errval,errstr);
+            g_simple_async_result_set_error(result,
+                      G_IO_ERROR, G_IO_ERROR_FAILED,
+                      "Failed to create named pipe (%ld) %s", errval, errstr);
+            goto failed_request;
+        }
+    }
+
+    if (!spice_win_usb_driver_send_request(self, op_type,
+                                           vid, pid, &err)) {
+        g_warning("failed to send a request to usbclerk %s", err->message);
+        g_simple_async_result_take_error(result, err);
+        goto failed_request;
+    }
+
+    /* set up for async read */
+    priv->result = result;
+    priv->device = device;
+    priv->cancellable = cancellable;
+
+    spice_win_usb_driver_read_reply_async(self);
+
+    return;
+
+ failed_request:
+    g_simple_async_result_complete_in_idle(result);
+    g_clear_object(&result);
+}
+
+
+
+/**
+ * spice_win_usb_driver_install:
+ * Start libusb driver installation for @device
+ *
+ * A new NamedPipe is created for each request.
+ *
+ * Returns: TRUE if a request was sent to usbclerk
+ *          FALSE upon failure to send a request.
+ */
+G_GNUC_INTERNAL
+void spice_win_usb_driver_install(SpiceWinUsbDriver *self,
+                                  SpiceUsbDevice *device,
+                                  GCancellable *cancellable,
+                                  GAsyncReadyCallback callback,
+                                  gpointer user_data)
+{
+    SPICE_DEBUG("Win usb driver installation started");
+
+    spice_win_usb_driver_op(self, device, USB_CLERK_DRIVER_SESSION_INSTALL,
+                            cancellable, callback, user_data);
+}
+
+G_GNUC_INTERNAL
+void spice_win_usb_driver_uninstall(SpiceWinUsbDriver *self,
+                                    SpiceUsbDevice *device,
+                                    GCancellable *cancellable,
+                                    GAsyncReadyCallback callback,
+                                    gpointer user_data)
+{
+    SPICE_DEBUG("Win usb driver uninstall operation started");
+
+    spice_win_usb_driver_op(self, device, USB_CLERK_DRIVER_REMOVE, cancellable,
+                            callback, user_data);
+}
+
+
+/**
+ * Returns: currently returns 0 (failure) and 1 (success)
+ * possibly later we'll add error-codes
+ */
+G_GNUC_INTERNAL
+gint spice_win_usb_driver_install_finish(SpiceWinUsbDriver *self,
+                                          GAsyncResult *res, GError **err)
+{
+    GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT(res);
+
+    g_return_val_if_fail(SPICE_IS_WIN_USB_DRIVER(self), 0);
+    g_return_val_if_fail(g_simple_async_result_is_valid(res, G_OBJECT(self),
+                                                        spice_win_usb_driver_op),
+                         FALSE);
+    if (g_simple_async_result_propagate_error(result, err))
+        return 0;
+
+    return self->priv->reply.status;
+}
+
+G_GNUC_INTERNAL
+SpiceUsbDevice *spice_win_usb_driver_get_device(SpiceWinUsbDriver *self)
+{
+    g_return_val_if_fail(SPICE_IS_WIN_USB_DRIVER(self), 0);
+
+    return self->priv->device;
+}
+
+GQuark spice_win_usb_driver_error_quark(void)
+{
+    return g_quark_from_static_string("spice-win-usb-driver-error-quark");
+}
diff --git a/src/win-usb-driver-install.h b/src/win-usb-driver-install.h
new file mode 100644
index 0000000..034abf9
--- /dev/null
+++ b/src/win-usb-driver-install.h
@@ -0,0 +1,104 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2011 Red Hat, Inc.
+
+   Red Hat Authors:
+   Uri Lublin <uril at redhat.com>
+
+   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_WIN_USB_DRIVER_H
+#define SPICE_WIN_USB_DRIVER_H
+
+#include "usb-device-manager.h"
+
+G_BEGIN_DECLS
+
+GQuark win_usb_driver_error_quark(void);
+
+
+#define SPICE_TYPE_WIN_USB_DRIVER      (spice_win_usb_driver_get_type ())
+#define SPICE_WIN_USB_DRIVER(obj)      (G_TYPE_CHECK_INSTANCE_CAST ((obj),    \
+            SPICE_TYPE_WIN_USB_DRIVER, SpiceWinUsbDriver))
+#define SPICE_IS_WIN_USB_DRIVER(obj)   (G_TYPE_CHECK_INSTANCE_TYPE ((obj),    \
+            SPICE_TYPE_WIN_USB_DRIVER))
+#define SPICE_WIN_USB_DRIVER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass),  \
+            SPICE_TYPE_WIN_USB_DRIVER, SpiceWinUsbDriverClass))
+#define SPICE_IS_WIN_USB_DRIVER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),\
+            SPICE_TYPE_WIN_USB_DRIVER))
+#define SPICE_WIN_USB_DRIVER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),\
+            SPICE_TYPE_WIN_USB_DRIVER, SpiceWinUsbDriverClass))
+
+typedef struct _SpiceWinUsbDriver          SpiceWinUsbDriver;
+typedef struct _SpiceWinUsbDriverClass     SpiceWinUsbDriverClass;
+typedef struct _SpiceWinUsbDriverPrivate   SpiceWinUsbDriverPrivate;
+
+struct _SpiceWinUsbDriver
+{
+    GObject parent;
+
+    /*< private >*/
+    SpiceWinUsbDriverPrivate *priv;
+    /* Do not add fields to this struct */
+};
+
+struct _SpiceWinUsbDriverClass
+{
+    GObjectClass parent_class;
+};
+
+GType spice_win_usb_driver_get_type(void);
+
+SpiceWinUsbDriver *spice_win_usb_driver_new(void);
+
+
+void spice_win_usb_driver_install(SpiceWinUsbDriver *self,
+                                  SpiceUsbDevice *device,
+                                  GCancellable *cancellable,
+                                  GAsyncReadyCallback callback,
+                                  gpointer user_data);
+
+void spice_win_usb_driver_uninstall(SpiceWinUsbDriver *self,
+                                    SpiceUsbDevice *device,
+                                    GCancellable *cancellable,
+                                    GAsyncReadyCallback callback,
+                                    gpointer user_data);
+
+gint spice_win_usb_driver_install_finish(SpiceWinUsbDriver *self,
+                                         GAsyncResult *res, GError **err);
+
+
+SpiceUsbDevice *spice_win_usb_driver_get_device(SpiceWinUsbDriver *self);
+
+#define SPICE_WIN_USB_DRIVER_ERROR spice_win_usb_driver_error_quark()
+
+/**
+ * SpiceWinUsbDriverError:
+ * @SPICE_WIN_USB_DRIVER_ERROR_FAILED: generic error code
+ * @SPICE_WIN_USB_DRIVER_ERROR_MESSAGE: bad message read from clerk
+ *
+ * Error codes returned by spice-client API.
+ */
+typedef enum
+{
+    SPICE_WIN_USB_DRIVER_ERROR_FAILED,
+    SPICE_WIN_USB_DRIVER_ERROR_MESSAGE,
+} SpiceWinUsbDriverError;
+
+GQuark spice_win_usb_driver_error_quark(void);
+
+G_END_DECLS
+
+#endif /* SPICE_WIN_USB_DRIVER_H */
diff --git a/src/wocky-http-proxy.c b/src/wocky-http-proxy.c
new file mode 100644
index 0000000..ce23b0e
--- /dev/null
+++ b/src/wocky-http-proxy.c
@@ -0,0 +1,537 @@
+ /* wocky-http-proxy.c: Source for WockyHttpProxy
+ *
+ * Copyright (C) 2010 Collabora, Ltd.
+ * Copyright (C) 2014 Red Hat, Inc.
+ * @author Nicolas Dufresne <nicolas.dufresne at collabora.co.uk>
+ * @author Marc-André Lureau <marcandre.lureau at redhat.com>
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include "config.h"
+
+#include "glib-compat.h"
+#include "wocky-http-proxy.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+
+struct _WockyHttpProxy
+{
+  GObject parent;
+};
+
+struct _WockyHttpProxyClass
+{
+  GObjectClass parent_class;
+};
+
+static void wocky_http_proxy_iface_init (GProxyInterface *proxy_iface);
+
+#define wocky_http_proxy_get_type _wocky_http_proxy_get_type
+G_DEFINE_TYPE_WITH_CODE (WockyHttpProxy, wocky_http_proxy, G_TYPE_OBJECT,
+  G_IMPLEMENT_INTERFACE (G_TYPE_PROXY,
+    wocky_http_proxy_iface_init)
+  g_io_extension_point_set_required_type (
+    g_io_extension_point_register (G_PROXY_EXTENSION_POINT_NAME),
+    G_TYPE_PROXY);
+  g_io_extension_point_implement (G_PROXY_EXTENSION_POINT_NAME,
+    g_define_type_id, "http", 0))
+
+static void
+wocky_http_proxy_init (WockyHttpProxy *proxy)
+{
+}
+
+#define HTTP_END_MARKER "\r\n\r\n"
+
+static gchar *
+create_request (GProxyAddress *proxy_address, gboolean *has_cred)
+{
+  const gchar *hostname;
+  gint port;
+  const gchar *username;
+  const gchar *password;
+  GString *request;
+  gchar *ascii_hostname;
+
+  if (has_cred)
+    *has_cred = FALSE;
+
+  hostname = g_proxy_address_get_destination_hostname (proxy_address);
+  port = g_proxy_address_get_destination_port (proxy_address);
+  username = g_proxy_address_get_username (proxy_address);
+  password = g_proxy_address_get_password (proxy_address);
+
+  request = g_string_new (NULL);
+
+  ascii_hostname = g_hostname_to_ascii (hostname);
+  g_string_append_printf (request,
+      "CONNECT %s:%i HTTP/1.0\r\n"
+        "Host: %s:%i\r\n"
+        "Proxy-Connection: keep-alive\r\n"
+        "User-Agent: GLib/%i.%i\r\n",
+      ascii_hostname, port,
+      ascii_hostname, port,
+      GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION);
+  g_free (ascii_hostname);
+
+  if (username != NULL && password != NULL)
+    {
+      gchar *cred;
+      gchar *base64_cred;
+
+      if (has_cred)
+        *has_cred = TRUE;
+
+      cred = g_strdup_printf ("%s:%s", username, password);
+      base64_cred = g_base64_encode ((guchar *) cred, strlen (cred));
+      g_free (cred);
+      g_string_append_printf (request,
+          "Proxy-Authorization: Basic %s\r\n",
+          base64_cred);
+      g_free (base64_cred);
+    }
+
+  g_string_append (request, "\r\n");
+
+  return g_string_free (request, FALSE);
+}
+
+static gboolean
+check_reply (const gchar *buffer, gboolean has_cred, GError **error)
+{
+  gint err_code;
+  const gchar *ptr = buffer + 7;
+
+  if (strncmp (buffer, "HTTP/1.", 7) != 0
+      || (*ptr != '0' && *ptr != '1'))
+    {
+      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PROXY_FAILED,
+          "Bad HTTP proxy reply");
+      return FALSE;
+    }
+
+  ptr++;
+  while (*ptr == ' ') ptr++;
+
+  err_code = atoi (ptr);
+
+  if (err_code < 200 || err_code >= 300)
+    {
+      const gchar *msg_start;
+      gchar *msg;
+
+      while (g_ascii_isdigit (*ptr))
+        ptr++;
+
+      while (*ptr == ' ')
+        ptr++;
+
+      msg_start = ptr;
+
+      ptr = strchr (msg_start, '\r');
+
+      if (ptr == NULL)
+        ptr = strchr (msg_start, '\0');
+
+      msg = g_strndup (msg_start, ptr - msg_start);
+
+      if (err_code == 407)
+        {
+          if (has_cred)
+            g_set_error (error, G_IO_ERROR, G_IO_ERROR_PROXY_AUTH_FAILED,
+                "HTTP proxy authentication failed");
+          else
+            g_set_error (error, G_IO_ERROR, G_IO_ERROR_PROXY_NEED_AUTH,
+                "HTTP proxy authentication required");
+        }
+      else if (msg[0] == '\0')
+        g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PROXY_FAILED,
+            "Connection failed due to broken HTTP reply");
+      else
+        g_set_error (error, G_IO_ERROR, G_IO_ERROR_PROXY_FAILED,
+            "HTTP proxy connection failed: %i %s",
+            err_code, msg);
+
+      g_free (msg);
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+static GIOStream *
+wocky_http_proxy_connect (GProxy *proxy,
+    GIOStream *io_stream,
+    GProxyAddress *proxy_address,
+    GCancellable *cancellable,
+    GError **error)
+{
+  GInputStream *in;
+  GOutputStream *out;
+  GDataInputStream *data_in = NULL;
+  gchar *buffer = NULL;
+  gboolean has_cred;
+  GIOStream *tlsconn = NULL;
+
+  if (WOCKY_IS_HTTPS_PROXY (proxy))
+    {
+      tlsconn = g_tls_client_connection_new (io_stream,
+                                             G_SOCKET_CONNECTABLE(proxy_address),
+                                             error);
+      if (!tlsconn)
+          goto error;
+
+      GTlsCertificateFlags tls_validation_flags = G_TLS_CERTIFICATE_VALIDATE_ALL;
+#ifdef DEBUG
+      tls_validation_flags &= ~(G_TLS_CERTIFICATE_UNKNOWN_CA | G_TLS_CERTIFICATE_BAD_IDENTITY);
+#endif
+      g_tls_client_connection_set_validation_flags (G_TLS_CLIENT_CONNECTION (tlsconn),
+                                                    tls_validation_flags);
+      if (!g_tls_connection_handshake (G_TLS_CONNECTION (tlsconn), cancellable, error))
+          goto error;
+
+      io_stream = tlsconn;
+    }
+
+  in = g_io_stream_get_input_stream (io_stream);
+  out = g_io_stream_get_output_stream (io_stream);
+
+  data_in = g_data_input_stream_new (in);
+  g_filter_input_stream_set_close_base_stream (G_FILTER_INPUT_STREAM (data_in),
+      FALSE);
+
+  buffer = create_request (proxy_address, &has_cred);
+  if (!g_output_stream_write_all (out, buffer, strlen (buffer), NULL,
+        cancellable, error))
+      goto error;
+
+  g_free (buffer);
+  buffer = g_data_input_stream_read_until (data_in, HTTP_END_MARKER, NULL,
+      cancellable, error);
+  g_object_unref (data_in);
+  data_in = NULL;
+
+  if (buffer == NULL)
+    {
+      if (error && (*error == NULL))
+        g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PROXY_FAILED,
+            "HTTP proxy server closed connection unexpectedly.");
+      goto error;
+    }
+
+  if (!check_reply (buffer, has_cred, error))
+    goto error;
+
+  g_free (buffer);
+
+  g_object_ref (io_stream);
+  g_clear_object (&tlsconn);
+
+  return io_stream;
+
+error:
+  g_clear_object (&tlsconn);
+  g_clear_object (&data_in);
+  g_free (buffer);
+  return NULL;
+}
+
+
+typedef struct
+{
+  GSimpleAsyncResult *simple;
+  GIOStream *io_stream;
+  gchar *buffer;
+  gssize length;
+  gssize offset;
+  GDataInputStream *data_in;
+  gboolean has_cred;
+  GCancellable *cancellable;
+} ConnectAsyncData;
+
+static void request_write_cb (GObject *source,
+    GAsyncResult *res,
+    gpointer user_data);
+static void reply_read_cb (GObject *source,
+    GAsyncResult *res,
+    gpointer user_data);
+
+static void
+free_connect_data (ConnectAsyncData *data)
+{
+  if (data->io_stream != NULL)
+    g_object_unref (data->io_stream);
+
+  g_free (data->buffer);
+
+  if (data->data_in != NULL)
+    g_object_unref (data->data_in);
+
+  if (data->cancellable != NULL)
+    g_object_unref (data->cancellable);
+
+  g_slice_free (ConnectAsyncData, data);
+}
+
+static void
+complete_async_from_error (ConnectAsyncData *data, GError *error)
+{
+  GSimpleAsyncResult *simple = data->simple;
+
+  if (error == NULL)
+    g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_PROXY_FAILED,
+        "HTTP proxy server closed connection unexpectedly.");
+
+  g_simple_async_result_set_from_error (data->simple, error);
+  g_error_free (error);
+  g_simple_async_result_set_op_res_gpointer (simple, NULL, NULL);
+  g_simple_async_result_complete (simple);
+  g_object_unref (simple);
+}
+
+static void
+do_write (GAsyncReadyCallback callback, ConnectAsyncData *data)
+{
+  GOutputStream *out;
+  out = g_io_stream_get_output_stream (data->io_stream);
+  g_output_stream_write_async (out,
+      data->buffer + data->offset,
+      data->length - data->offset,
+      G_PRIORITY_DEFAULT, data->cancellable,
+      callback, data);
+}
+
+static void
+stream_connected (ConnectAsyncData *data,
+                  GIOStream *io_stream)
+{
+  GInputStream *in;
+
+  data->io_stream = g_object_ref (io_stream);
+  in = g_io_stream_get_input_stream (io_stream);
+  data->data_in = g_data_input_stream_new (in);
+  g_filter_input_stream_set_close_base_stream (G_FILTER_INPUT_STREAM (data->data_in),
+                                               FALSE);
+
+  do_write (request_write_cb, data);
+}
+
+static void
+handshake_completed (GObject *source_object,
+                     GAsyncResult *res,
+                     gpointer user_data)
+{
+  GTlsConnection *conn = G_TLS_CONNECTION (source_object);
+  ConnectAsyncData *data = user_data;
+  GError *error = NULL;
+
+  if (!g_tls_connection_handshake_finish (conn, res, &error))
+    {
+      complete_async_from_error (data, error);
+      return;
+    }
+
+  stream_connected (data, G_IO_STREAM (conn));
+}
+
+static void
+wocky_http_proxy_connect_async (GProxy *proxy,
+    GIOStream *io_stream,
+    GProxyAddress *proxy_address,
+    GCancellable *cancellable,
+    GAsyncReadyCallback callback,
+    gpointer user_data)
+{
+  GSimpleAsyncResult *simple;
+  ConnectAsyncData *data;
+
+  simple = g_simple_async_result_new (G_OBJECT (proxy),
+                                      callback, user_data,
+                                      wocky_http_proxy_connect_async);
+
+  data = g_slice_new0 (ConnectAsyncData);
+  if (cancellable != NULL)
+    data->cancellable = g_object_ref (cancellable);
+  data->simple = simple;
+
+  data->buffer = create_request (proxy_address, &data->has_cred);
+  data->length = strlen (data->buffer);
+  data->offset = 0;
+
+  g_simple_async_result_set_op_res_gpointer (simple, data,
+                                             (GDestroyNotify) free_connect_data);
+
+  if (WOCKY_IS_HTTPS_PROXY (proxy))
+    {
+      GError *error = NULL;
+      GIOStream *tlsconn;
+
+      tlsconn = g_tls_client_connection_new (io_stream,
+                                             G_SOCKET_CONNECTABLE(proxy_address),
+                                             &error);
+      if (!tlsconn)
+        {
+          complete_async_from_error (data, error);
+          return;
+        }
+
+      g_return_if_fail (tlsconn != NULL);
+
+      GTlsCertificateFlags tls_validation_flags = G_TLS_CERTIFICATE_VALIDATE_ALL;
+#ifdef DEBUG
+      tls_validation_flags &= ~(G_TLS_CERTIFICATE_UNKNOWN_CA | G_TLS_CERTIFICATE_BAD_IDENTITY);
+#endif
+      g_tls_client_connection_set_validation_flags (G_TLS_CLIENT_CONNECTION (tlsconn),
+                                                    tls_validation_flags);
+      g_tls_connection_handshake_async (G_TLS_CONNECTION (tlsconn),
+                                        G_PRIORITY_DEFAULT, cancellable,
+                                        handshake_completed, data);
+    }
+  else
+    {
+      stream_connected (data, io_stream);
+    }
+}
+
+static void
+request_write_cb (GObject *source,
+    GAsyncResult *res,
+    gpointer user_data)
+{
+  GError *error = NULL;
+  ConnectAsyncData *data = user_data;
+  gssize written;
+
+  written = g_output_stream_write_finish (G_OUTPUT_STREAM (source),
+      res, &error);
+  if (written < 0)
+    {
+      complete_async_from_error (data, error);
+      return;
+    }
+
+  data->offset += written;
+
+   if (data->offset == data->length)
+    {
+      g_free (data->buffer);
+      data->buffer = NULL;
+
+      g_data_input_stream_read_until_async (data->data_in,
+          HTTP_END_MARKER,
+          G_PRIORITY_DEFAULT,
+          data->cancellable,
+          reply_read_cb, data);
+
+    }
+  else
+    {
+      do_write (request_write_cb, data);
+    }
+}
+
+static void
+reply_read_cb (GObject *source,
+    GAsyncResult *res,
+    gpointer user_data)
+{
+  GError *error = NULL;
+  ConnectAsyncData *data = user_data;
+
+  data->buffer = g_data_input_stream_read_until_finish (data->data_in,
+      res, NULL, &error);
+
+  if (data->buffer == NULL)
+    {
+      complete_async_from_error (data, error);
+      return;
+    }
+
+  if (!check_reply (data->buffer, data->has_cred, &error))
+    {
+      complete_async_from_error (data, error);
+      return;
+    }
+
+  g_simple_async_result_complete (data->simple);
+  g_object_unref (data->simple);
+}
+
+static GIOStream *
+wocky_http_proxy_connect_finish (GProxy *proxy,
+    GAsyncResult *result,
+    GError **error)
+{
+  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result);
+  ConnectAsyncData *data = g_simple_async_result_get_op_res_gpointer (simple);
+
+  if (g_simple_async_result_propagate_error (simple, error))
+    return NULL;
+
+  return g_object_ref (data->io_stream);
+}
+
+static gboolean
+wocky_http_proxy_supports_hostname (GProxy *proxy)
+{
+  return TRUE;
+}
+
+static void
+wocky_http_proxy_class_init (WockyHttpProxyClass *class)
+{
+}
+
+static void
+wocky_http_proxy_iface_init (GProxyInterface *proxy_iface)
+{
+  proxy_iface->connect  = wocky_http_proxy_connect;
+  proxy_iface->connect_async = wocky_http_proxy_connect_async;
+  proxy_iface->connect_finish = wocky_http_proxy_connect_finish;
+  proxy_iface->supports_hostname = wocky_http_proxy_supports_hostname;
+}
+
+struct _WockyHttpsProxy
+{
+  WockyHttpProxy parent;
+};
+
+struct _WockyHttpsProxyClass
+{
+  WockyHttpProxyClass parent_class;
+};
+
+#define wocky_https_proxy_get_type _wocky_https_proxy_get_type
+G_DEFINE_TYPE_WITH_CODE (WockyHttpsProxy, wocky_https_proxy, WOCKY_TYPE_HTTP_PROXY,
+  G_IMPLEMENT_INTERFACE (G_TYPE_PROXY,
+    wocky_http_proxy_iface_init)
+  g_io_extension_point_set_required_type (
+    g_io_extension_point_register (G_PROXY_EXTENSION_POINT_NAME),
+    G_TYPE_PROXY);
+  g_io_extension_point_implement (G_PROXY_EXTENSION_POINT_NAME,
+    g_define_type_id, "https", 0))
+
+static void
+wocky_https_proxy_init (WockyHttpsProxy *proxy)
+{
+}
+
+static void
+wocky_https_proxy_class_init (WockyHttpsProxyClass *class)
+{
+}
diff --git a/src/wocky-http-proxy.h b/src/wocky-http-proxy.h
new file mode 100644
index 0000000..9484b51
--- /dev/null
+++ b/src/wocky-http-proxy.h
@@ -0,0 +1,56 @@
+ /* wocky-http-proxy.h: Header for WockyHttpProxy
+ *
+ * Copyright (C) 2010 Collabora, Ltd.
+ * @author Nicolas Dufresne <nicolas.dufresne at collabora.co.uk>
+ *
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+#ifndef _WOCKY_HTTP_PROXY_H_
+#define _WOCKY_HTTP_PROXY_H_
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define WOCKY_TYPE_HTTP_PROXY         (_wocky_http_proxy_get_type ())
+#define WOCKY_HTTP_PROXY(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), WOCKY_TYPE_HTTP_PROXY, WockyHttpProxy))
+#define WOCKY_HTTP_PROXY_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), WOCKY_TYPE_HTTP_PROXY, WockyHttpProxyClass))
+#define WOCKY_IS_HTTP_PROXY(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), WOCKY_TYPE_HTTP_PROXY))
+#define WOCKY_IS_HTTP_PROXY_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), WOCKY_TYPE_HTTP_PROXY))
+#define WOCKY_HTTP_PROXY_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), WOCKY_TYPE_HTTP_PROXY, WockyHttpProxyClass))
+
+typedef struct _WockyHttpProxy        WockyHttpProxy;
+typedef struct _WockyHttpProxyClass   WockyHttpProxyClass;
+
+GType _wocky_http_proxy_get_type (void);
+
+#if GLIB_CHECK_VERSION(2, 28, 0)
+#define WOCKY_TYPE_HTTPS_PROXY         (_wocky_https_proxy_get_type ())
+#define WOCKY_HTTPS_PROXY(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), WOCKY_TYPE_HTTPS_PROXY, WockyHttpsProxy))
+#define WOCKY_HTTPS_PROXY_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), WOCKY_TYPE_HTTPS_PROXY, WockyHttpsProxyClass))
+#define WOCKY_IS_HTTPS_PROXY(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), WOCKY_TYPE_HTTPS_PROXY))
+#define WOCKY_IS_HTTPS_PROXY_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), WOCKY_TYPE_HTTPS_PROXY))
+#define WOCKY_HTTPS_PROXY_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), WOCKY_TYPE_HTTPS_PROXY, WockyHttpsProxyClass))
+
+typedef struct _WockyHttpsProxy        WockyHttpsProxy;
+typedef struct _WockyHttpsProxyClass   WockyHttpsProxyClass;
+
+GType _wocky_https_proxy_get_type (void);
+#endif
+
+G_END_DECLS
+
+#endif /* _WOCKY_HTTP_PROXY_H_ */
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 0a839e4..19c02b6 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -14,15 +14,15 @@ TESTS = $(noinst_PROGRAMS)
 
 AM_CPPFLAGS =					\
 	$(GIO_CFLAGS)				\
-	-I$(top_srcdir)/gtk			\
-	-I$(top_builddir)/gtk			\
+	-I$(top_srcdir)/src			\
+	-I$(top_builddir)/src			\
 	-DG_LOG_DOMAIN=\"GSpice\"		\
 	$(NULL)
 
 AM_LDFLAGS = $(GIO_LIBS) -static
 
 LDADD =							\
-	$(top_builddir)/gtk/libspice-client-glib-2.0.la	\
+	$(top_builddir)/src/libspice-client-glib-2.0.la	\
 	$(NULL)
 
 util_SOURCES = util.c
diff --git a/vapi/Makefile.am b/vapi/Makefile.am
index 5325aaa..c66b1db 100644
--- a/vapi/Makefile.am
+++ b/vapi/Makefile.am
@@ -22,17 +22,17 @@ EXTRA_DIST =						\
 
 CLEANFILES += $(vapi_DATA)
 
-spice-client-glib-2.0.vapi: $(top_builddir)/gtk/SpiceClientGLib-2.0.gir SpiceClientGLib-2.0.metadata
+spice-client-glib-2.0.vapi: $(top_builddir)/src/SpiceClientGLib-2.0.gir SpiceClientGLib-2.0.metadata
 	$(AM_V_GEN)$(VAPIGEN) -q		\
 		--metadatadir=$(srcdir)		\
 		--library spice-client-glib-2.0	\
 		--pkg gio-2.0			\
 		$<
 
-spice-client-gtk-$(SPICE_GTK_API_VERSION).vapi: $(top_builddir)/gtk/SpiceClientGtk-$(SPICE_GTK_API_VERSION).gir spice-client-glib-2.0.vapi
+spice-client-gtk-$(SPICE_GTK_API_VERSION).vapi: $(top_builddir)/src/SpiceClientGtk-$(SPICE_GTK_API_VERSION).gir spice-client-glib-2.0.vapi
 	$(AM_V_GEN)$(VAPIGEN) -q					\
 		--vapidir=$(builddir)					\
-		--girdir=$(top_builddir)/gtk				\
+		--girdir=$(top_builddir)/src				\
 		--pkg spice-client-glib-2.0				\
 		--pkg gtk+-$(GTK_API_VERSION)				\
 		--library spice-client-gtk-$(SPICE_GTK_API_VERSION)	\
-- 
2.4.2



More information about the Spice-devel mailing list