From 88f2d1abe0ffecdf606acbf5f473034d858dc6db Mon Sep 17 00:00:00 2001 From: Guannan Ren Date: Mon, 1 Jul 2013 14:33:59 -0400 Subject: [PATCH] details: Add auto USB redirection support in console viewer 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 7301d662a..ee5bc45a4 100644 --- a/ui/vmm-details.ui +++ b/ui/vmm-details.ui @@ -184,6 +184,7 @@ False Virtual _Machine True + False @@ -331,6 +332,15 @@ + + + True + False + _Redirect USB device + True + + + diff --git a/virtManager/console.py b/virtManager/console.py index 90ec78715..5ff5cb049 100644 --- a/virtManager/console.py +++ b/virtManager/console.py @@ -286,6 +286,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): @@ -452,6 +455,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() @@ -503,6 +508,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 @@ -558,6 +565,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() @@ -565,7 +595,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) @@ -574,7 +604,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", @@ -600,6 +630,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): @@ -961,11 +1031,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("" + msg + "") 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() @@ -1005,6 +1077,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 24ae1f637..5fa31c901 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")) 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 1ce635897..0f89fc68c 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: