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@Type@Value _@enum_name@_values[] = {" \ - --vprod " { @VALUENAME@, \"@VALUENAME@\", \"@valuenick@\" }," \ - --vtail " { 0, NULL, NULL }\n};\n\n" \ - --vtail "GType\n@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_@type@_register_static (\"@EnumName@\", _@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_@ENUMSHORT@ @enum_name@_get_type()\n" \ - --eprod "GType @enum_name@_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@Type@Value _@enum_name@_values[] = {" \ - --vprod " { @VALUENAME@, \"@VALUENAME@\", \"@valuenick@\" }," \ - --vtail " { 0, NULL, NULL }\n};\n\n" \ - --vtail "GType\n@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_@type@_register_static (\"@EnumName@\", _@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_@ENUMSHORT@ @enum_name@_get_type()\n" \ - --eprod "GType @enum_name@_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@xxxxxxxxxx> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General 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@xxxxxxxxxx> - Richard Hughes <rhughes@xxxxxxxxxx> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General 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@xxxxxxxxxx> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General 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@xxxxxxxxxxxxx> - * - * This library is free software; you can redistribute it 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@xxxxxxxxxxxxx> - * - * This library is free software; you can redistribute it 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@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@_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@xxxxxxxxxxxxx> - * - * This library is free software; you can redistribute it 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@xxxxxxxxxxxxx> - * - * This library is free software; you can redistribute it 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@xxxxxxxxxxxxx> - * - * This library is free software; you can redistribute it 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@xxxxxxxxxx> - * - * This library is free software; you can redistribute it 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@xxxxxxxxxx> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General 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@xxxxxxxxxx> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General 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@xxxxxxxxxxxxx> - Copyright (C) 2009-2010 Daniel P. Berrange <dan@xxxxxxxxxxxx> - - This library is free software; you can redistribute it 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@xxxxxxxxxxxxx> - Copyright (C) 2009-2010 Daniel P. Berrange <dan@xxxxxxxxxxxx> - - This library is free software; you can redistribute it 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@xxxxxxxx> - - Red Hat Authors: - Hans de Goede <hdegoede@xxxxxxxxxx> - - 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@xxxxxxxxxxxx> - * - * This library is free software; you can redistribute it 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@xxxxxxxxxxxxx> - * Copyright (C) 2009-2010 Daniel P. Berrange <dan@xxxxxxxxxxxx> - * - * This library is free software; you can redistribute it 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@xxxxxxxxxx>", - "Marc-André Lureau <marcandre.lureau@xxxxxxxxxx>", - 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@xxxxxxxxxx> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General 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@xxxxxxxxxx> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General 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@xxxxxxxxxx> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General 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@xxxxxxxxxx> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General 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,@vendor,@product,@version,@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@xxxxxxxxxx> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General 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@xxxxxxxxxx> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General 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@xxxxxxxxxx> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General 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@xxxxxxxxxx> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General 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@xxxxxxxxxx> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General 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@xxxxxxxxxxxxx> - * Copyright (C) 2009-2010 Daniel P. Berrange <dan@xxxxxxxxxxxx> - * - * 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@xxxxxxxxx\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@xxxxxxxxx\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@xxxxxxxxxxxxx> - * Copyright (C) 2009-2010 Daniel P. Berrange <dan@xxxxxxxxxxxx> - * - * This library is free software; you can redistribute it 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@xxxxxxxxxx> - Uri Lublin <uril@xxxxxxxxxx> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General 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@xxxxxxxxxx> - Uri Lublin <uril@xxxxxxxxxx> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General 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@xxxxxxxxxx> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General 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@xxxxxxxxxx> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General 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@xxxxxxxxxxxxxxx> - * @author Marc-André Lureau <marcandre.lureau@xxxxxxxxxx> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General 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@xxxxxxxxxxxxxxx> - * - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General 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@Type@Value _@enum_name@_values[] = {" \ + --vprod " { @VALUENAME@, \"@VALUENAME@\", \"@valuenick@\" }," \ + --vtail " { 0, NULL, NULL }\n};\n\n" \ + --vtail "GType\n@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_@type@_register_static (\"@EnumName@\", _@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_@ENUMSHORT@ @enum_name@_get_type()\n" \ + --eprod "GType @enum_name@_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@Type@Value _@enum_name@_values[] = {" \ + --vprod " { @VALUENAME@, \"@VALUENAME@\", \"@valuenick@\" }," \ + --vtail " { 0, NULL, NULL }\n};\n\n" \ + --vtail "GType\n@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_@type@_register_static (\"@EnumName@\", _@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_@ENUMSHORT@ @enum_name@_get_type()\n" \ + --eprod "GType @enum_name@_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@xxxxxxxxxx> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General 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@xxxxxxxxxx> + Richard Hughes <rhughes@xxxxxxxxxx> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General 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@xxxxxxxxxx> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General 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@xxxxxxxxxxxxx> + * + * This library is free software; you can redistribute it 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@xxxxxxxxxxxxx> + * + * This library is free software; you can redistribute it 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@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@_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@xxxxxxxxxxxxx> + * + * This library is free software; you can redistribute it 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@xxxxxxxxxxxxx> + * + * This library is free software; you can redistribute it 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@xxxxxxxxxxxxx> + * + * This library is free software; you can redistribute it 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@xxxxxxxxxx> + * + * This library is free software; you can redistribute it 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@xxxxxxxxxx> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General 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@xxxxxxxxxx> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General 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@xxxxxxxxxxxxx> + Copyright (C) 2009-2010 Daniel P. Berrange <dan@xxxxxxxxxxxx> + + This library is free software; you can redistribute it 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@xxxxxxxxxxxxx> + Copyright (C) 2009-2010 Daniel P. Berrange <dan@xxxxxxxxxxxx> + + This library is free software; you can redistribute it 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@xxxxxxxx> + + Red Hat Authors: + Hans de Goede <hdegoede@xxxxxxxxxx> + + 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@xxxxxxxxxxxx> + * + * This library is free software; you can redistribute it 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@xxxxxxxxxxxxx> + * Copyright (C) 2009-2010 Daniel P. Berrange <dan@xxxxxxxxxxxx> + * + * This library is free software; you can redistribute it 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@xxxxxxxxxx>", + "Marc-André Lureau <marcandre.lureau@xxxxxxxxxx>", + 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@xxxxxxxxxx> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General 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@xxxxxxxxxx> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General 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@xxxxxxxxxx> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General 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@xxxxxxxxxx> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General 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,@vendor,@product,@version,@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@xxxxxxxxxx> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General 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@xxxxxxxxxx> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General 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@xxxxxxxxxx> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General 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@xxxxxxxxxx> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General 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@xxxxxxxxxx> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General 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@xxxxxxxxxxxxx> + * Copyright (C) 2009-2010 Daniel P. Berrange <dan@xxxxxxxxxxxx> + * + * 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@xxxxxxxxx\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@xxxxxxxxx\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@xxxxxxxxxxxxx> + * Copyright (C) 2009-2010 Daniel P. Berrange <dan@xxxxxxxxxxxx> + * + * This library is free software; you can redistribute it 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@xxxxxxxxxx> + Uri Lublin <uril@xxxxxxxxxx> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General 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@xxxxxxxxxx> + Uri Lublin <uril@xxxxxxxxxx> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General 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@xxxxxxxxxx> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General 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@xxxxxxxxxx> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General 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@xxxxxxxxxxxxxxx> + * @author Marc-André Lureau <marcandre.lureau@xxxxxxxxxx> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General 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@xxxxxxxxxxxxxxx> + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General 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 _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx http://lists.freedesktop.org/mailman/listinfo/spice-devel