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

Guannan Ren gren at redhat.com
Wed Jun 26 14:13:51 UTC 2013


Add "Select USB devices to redirect" 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.
---
 ui/vmm-details.ui      |  9 ++++++
 virtManager/console.py | 85 ++++++++++++++++++++++++++++++++++++++++++++++++--
 virtManager/details.py | 18 ++++++++++-
 3 files changed, 109 insertions(+), 3 deletions(-)

diff --git a/ui/vmm-details.ui b/ui/vmm-details.ui
index ea4b53e..e7a8dd5 100644
--- a/ui/vmm-details.ui
+++ b/ui/vmm-details.ui
@@ -337,6 +337,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..11709fa 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 _get_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 _get_spice_session(self):
+        self.spice_session = SpiceClientGLib.Session()
+        gtk_session = SpiceClientGtk.GtkSession.get(self.spice_session)
+        gtk_session.set_property("auto-clipboard", True)
+
+        self._get_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._get_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._get_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._get_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,15 @@ class vmmConsolePages(vmmGObjectUI):
         if self.viewer and self.viewer.display:
             self.viewer.display.grab_focus()
 
+        if not self.viewer.has_usb_redirection():
+            return
+
+        devs = self.vm.get_redirdev_devices()
+        for dev in devs:
+            if dev.type == "spicevmc":
+                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 b2d496e..f2d0318 100644
--- a/virtManager/details.py
+++ b/virtManager/details.py
@@ -415,6 +415,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,
@@ -621,7 +622,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"))
         uihelpers.build_shutdown_button_menu(self.widget("control-shutdown"),
                                              self.control_vm_shutdown,
                                              self.control_vm_reboot,
@@ -1593,6 +1596,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()
 
-- 
1.8.1.4




More information about the virt-tools-list mailing list