[virt-tools-list] [PATCH 3/3] details: Add auto USB redirection support in console viewer

Leonardo Augusto Guimarães Garcia lagarcia at linux.vnet.ibm.com
Fri Jun 28 21:32:20 UTC 2013


Patch looks good. USB redirection worked for me (almost) as expected 
when using latest spice-gtk from git.

What I mean by almost is that when I plugged my USB storage device into 
the USB Port, I got an error dialog saying:

Could not open usb device: Access denied (insufficient permissions) [-3]

And if I expand the details on the dialog, I have:

USB redirection error

None

However, the USB storage device was correctly auto-connected to the guest!

Not sure if this error message is due to my test environment (I might 
have forgotten to properly install/configure something as I have never 
built spice-gtk before). IOW, overall the patch looks good for me. One 
minor nit below.

On 06/29/2013 02:08 AM, Guannan Ren wrote:
> Add "Redirect USB device" option in console viewer. Initialize and
> embed UsbDeviceWidget object from SpiceClientGtk into a dialog to
> let user choose available USB devices for redirection.
> Throw an error message if USB connection failed.
> Auto-redirection is enable by default.
>
> There is race between creating usbredir channel and calling
> has_usb_redirection() when initializing spice session like
> happening on virt-viwer. So adding a new signal handler
> on_details_menu_virtual_manager_activate() to recheck the
> status of usbredir channel, set "Redirect USB device" option
> sensitive if it is availiable.
> ---
>   ui/vmm-details.ui      | 10 +++++++
>   virtManager/console.py | 81 ++++++++++++++++++++++++++++++++++++++++++++++++--
>   virtManager/details.py | 25 +++++++++++++++-
>   virtManager/domain.py  |  7 +++++
>   4 files changed, 120 insertions(+), 3 deletions(-)
>
> diff --git a/ui/vmm-details.ui b/ui/vmm-details.ui
> index 7301d66..ee5bc45 100644
> --- a/ui/vmm-details.ui
> +++ b/ui/vmm-details.ui
> @@ -184,6 +184,7 @@
>                   <property name="can_focus">False</property>
>                   <property name="label" translatable="yes">Virtual _Machine</property>
>                   <property name="use_underline">True</property>
> +                <signal name="activate" handler="on_details_menu_virtual_manager_activate" swapped="no"/>
>                   <child type="submenu">
>                     <object class="GtkMenu" id="virtual_machine1_menu">
>                       <property name="can_focus">False</property>
> @@ -331,6 +332,15 @@
>                           <signal name="activate" handler="on_details_menu_screenshot_activate" swapped="no"/>
>                         </object>
>                       </child>
> +                    <child>
> +                      <object class="GtkMenuItem" id="details-menu-usb-redirection">
> +                        <property name="visible">True</property>
> +                        <property name="can_focus">False</property>
> +                        <property name="label" translatable="yes">_Redirect USB device</property>
> +                        <property name="use_underline">True</property>
> +                        <signal name="activate" handler="on_details_menu_usb_redirection" swapped="no"/>
> +                      </object>
> +                    </child>
>                     </object>
>                   </child>
>                 </object>
> diff --git a/virtManager/console.py b/virtManager/console.py
> index 001318e..4085be0 100644
> --- a/virtManager/console.py
> +++ b/virtManager/console.py
> @@ -280,6 +280,9 @@ class Viewer(vmmGObject):
>       def get_desktop_resolution(self):
>           raise NotImplementedError()
>
> +    def has_usb_redirection(self):
> +        return False
> +
>
>   class VNCViewer(Viewer):
>       def __init__(self, console):
> @@ -446,6 +449,8 @@ class SpiceViewer(Viewer):
>           self.display = None
>           self.audio = None
>           self.display_channel = None
> +        self.usbdev_manager = None
> +        self.usbwidget = None
>
>       def _init_widget(self):
>           self.set_grab_keys()
> @@ -497,6 +502,8 @@ class SpiceViewer(Viewer):
>               self.display.destroy()
>           self.display = None
>           self.display_channel = None
> +        self.usbdev_manager = None
> +        self.usbwidget = None
>
>       def is_open(self):
>           return self.spice_session is not None
> @@ -552,6 +559,29 @@ class SpiceViewer(Viewer):
>               return None
>           return self.display_channel.get_properties("width", "height")
>
> +    def _create_usbdev_manager(self):
> +        if self.usbdev_manager:
> +            return self.usbdev_manager
> +
> +        if self.spice_session:
> +            self.usbdev_manager = SpiceClientGLib.UsbDeviceManager.get(self.spice_session)
> +            if self.usbdev_manager:
> +                self.usbdev_manager.connect("auto-connect-failed", self._usbdev_redirect_error)
> +                self.usbdev_manager.connect("device-error", self._usbdev_redirect_error)
> +
> +        return self.usbdev_manager
> +
> +    def _create_spice_session(self):
> +        self.spice_session = SpiceClientGLib.Session()
> +        gtk_session = SpiceClientGtk.GtkSession.get(self.spice_session)
> +        gtk_session.set_property("auto-clipboard", True)
> +
> +        self._create_usbdev_manager()
> +
> +        autoredir = self.config.get_auto_redirection()
> +        if autoredir:
> +            gtk_session.set_property("auto-usbredir", True)
> +
>       def open_host(self, ginfo, password=None):
>           host, port = ginfo.get_conn_host()
>
> @@ -559,7 +589,7 @@ class SpiceViewer(Viewer):
>           uri += str(host) + "?port=" + str(port)
>           logging.debug("spice uri: %s", uri)
>
> -        self.spice_session = SpiceClientGLib.Session()
> +        self._create_spice_session()
>           self.spice_session.set_property("uri", uri)
>           if password:
>               self.spice_session.set_property("password", password)
> @@ -568,7 +598,7 @@ class SpiceViewer(Viewer):
>           self.spice_session.connect()
>
>       def open_fd(self, fd, password=None):
> -        self.spice_session = SpiceClientGLib.Session()
> +        self._create_spice_session()
>           if password:
>               self.spice_session.set_property("password", password)
>           GObject.GObject.connect(self.spice_session, "channel-new",
> @@ -594,6 +624,46 @@ class SpiceViewer(Viewer):
>               return
>           self.display.set_property("scaling", scaling)
>
> +    def _usbdev_redirect_error(self,
> +                             spice_usbdev_widget, spice_usb_device,
> +                             errstr):
> +        ignore_widget = spice_usbdev_widget
> +        ignore_device = spice_usb_device
> +
> +        error = self.console.err
> +        error.show_err(_("USB redirection error"),
> +                         text2=str(errstr),
> +                         async=False)
> +
> +    def get_usb_widget(self):
> +
> +        # 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
> +
> +        usb_device_description_fmt = _("%s %s %s at %d-%d")
> +
> +        if self.spice_session:
> +            self.usbwidget = SpiceClientGtk.UsbDeviceWidget.new(self.spice_session,
> +                                                                usb_device_description_fmt)
> +            self.usbwidget.connect("connect-failed", self._usbdev_redirect_error)
> +            return self.usbwidget
> +
> +        return
> +
> +    def has_usb_redirection(self):
> +        usbredir_channel_type = SpiceClientGLib.Channel.string_to_type('usbredir')
> +
> +        if self.spice_session:
> +            if self._create_usbdev_manager() and \
> +               self.spice_session.has_channel_type(usbredir_channel_type):
> +                return True
> +
> +        return False
> +
>
>   class vmmConsolePages(vmmGObjectUI):
>       def __init__(self, vm, builder, topwin):
> @@ -955,11 +1025,13 @@ class vmmConsolePages(vmmGObjectUI):
>           self.close_viewer()
>           self.widget("console-pages").set_current_page(PAGE_UNAVAILABLE)
>           self.widget("details-menu-vm-screenshot").set_sensitive(False)
> +        self.widget("details-menu-usb-redirection").set_sensitive(False)
>           self.widget("console-unavailable").set_label("<b>" + msg + "</b>")
>
>       def activate_auth_page(self, withPassword=True, withUsername=False):
>           (pw, username) = self.config.get_console_password(self.vm)
>           self.widget("details-menu-vm-screenshot").set_sensitive(False)
> +        self.widget("details-menu-usb-redirection").set_sensitive(False)
>
>           if withPassword:
>               self.widget("console-auth-password").show()
> @@ -999,6 +1071,11 @@ class vmmConsolePages(vmmGObjectUI):
>           if self.viewer and self.viewer.display:
>               self.viewer.display.grab_focus()
>
> +        if self.viewer.has_usb_redirection() and \
> +           self.vm.has_spicevmc_type_redirdev():
> +            self.widget("details-menu-usb-redirection").set_sensitive(True)
> +            return
> +
>       def page_changed(self, ignore1=None, ignore2=None, ignore3=None):
>           self.set_allow_fullscreen()
>
> diff --git a/virtManager/details.py b/virtManager/details.py
> index 23b5310..e9d1083 100644
> --- a/virtManager/details.py
> +++ b/virtManager/details.py
> @@ -407,6 +407,7 @@ class vmmDetails(vmmGObjectUI):
>               "on_details_customize_finish_clicked": self.customize_finish,
>               "on_details_cancel_customize_clicked": self.close,
>
> +            "on_details_menu_virtual_manager_activate": self.control_vm_menu,
>               "on_details_menu_run_activate": self.control_vm_run,
>               "on_details_menu_poweroff_activate": self.control_vm_shutdown,
>               "on_details_menu_reboot_activate": self.control_vm_reboot,
> @@ -418,6 +419,7 @@ class vmmDetails(vmmGObjectUI):
>               "on_details_menu_migrate_activate": self.control_vm_migrate,
>               "on_details_menu_delete_activate": self.control_vm_delete,
>               "on_details_menu_screenshot_activate": self.control_vm_screenshot,
> +            "on_details_menu_usb_redirection": self.control_vm_usb_redirection,
>               "on_details_menu_view_toolbar_activate": self.toggle_toolbar,
>               "on_details_menu_view_manager_activate": self.view_manager,
>               "on_details_menu_view_details_toggled": self.details_console_changed,
> @@ -627,7 +629,9 @@ class vmmDetails(vmmGObjectUI):
>       ##########################
>
>       def init_menus(self):
> -        # Shutdown button menu
> +        # Virtual Machine menu
> +        self.widget("details-menu-usb-redirection").set_tooltip_text(
> +            _("Redirect USB device attached on host to virtual machine with SPICE graphics. USB Redirection device is required for Virtual Machine to support this functionality. Auto-redirection is enabled by default"))
Can't we set the tooltip in the UI XML file and avoid this extremely 
long line in the code? I think it should be easy to do that with glade.

Best regards,

Leonardo Garcia
>           uihelpers.build_shutdown_button_menu(self.widget("control-shutdown"),
>                                                self.control_vm_shutdown,
>                                                self.control_vm_reboot,
> @@ -1557,6 +1561,12 @@ class vmmDetails(vmmGObjectUI):
>                         self.vm.conn.get_uri(),
>                         self.vm.get_uuid())
>
> +    def control_vm_menu(self, src_ignore):
> +        if self.console.viewer.has_usb_redirection() and \
> +           self.vm.has_spicevmc_type_redirdev():
> +            widget = self.widget("details-menu-usb-redirection")
> +            if not widget.get_sensitive():
> +                widget.set_sensitive(True)
>
>       def control_vm_run(self, src_ignore):
>           self.emit("action-run-domain",
> @@ -1601,6 +1611,19 @@ class vmmDetails(vmmGObjectUI):
>           except Exception, e:
>               self.err.show_err(_("Error taking screenshot: %s") % str(e))
>
> +    def control_vm_usb_redirection(self, src):
> +        ignore = src
> +        spice_usbdev_dialog = self.err
> +
> +        spice_usbdev_widget = self.console.viewer.get_usb_widget()
> +        if not spice_usbdev_widget:
> +            self.err.show_err(_("Error initializing spice USB device widget"))
> +            return
> +
> +        spice_usbdev_widget.show()
> +        spice_usbdev_dialog.show_info(_("Select USB devices for redirection"),
> +                                      widget=spice_usbdev_widget)
> +
>       def _take_screenshot(self):
>           image = self.console.viewer.get_pixbuf()
>
> diff --git a/virtManager/domain.py b/virtManager/domain.py
> index 7ead272..7eff434 100644
> --- a/virtManager/domain.py
> +++ b/virtManager/domain.py
> @@ -329,6 +329,13 @@ class vmmDomain(vmmLibvirtObject):
>               self._is_management_domain = (self.get_id() == 0)
>           return self._is_management_domain
>
> +    def has_spicevmc_type_redirdev(self):
> +        devs = self.get_redirdev_devices()
> +        for dev in devs:
> +            if dev.type == "spicevmc":
> +                return True
> +        return False
> +
>       def get_id_pretty(self):
>           i = self.get_id()
>           if i < 0:




More information about the virt-tools-list mailing list