# # Copyright (C) 2006-2008, 2015 Red Hat, Inc. # Copyright (C) 2006 Daniel P. Berrange # Copyright (C) 2010 Marc-Andre Lureau # # 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, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301 USA. # from gi.repository import Gtk from gi.repository import Gdk import logging from .autodrawer import AutoDrawer from .baseclass import vmmGObjectUI from .details import DETAILS_PAGE_CONSOLE from .serialcon import vmmSerialConsole from .sshtunnels import ConnectionInfo from .viewers import SpiceViewer, VNCViewer # console-pages IDs (_CONSOLE_PAGE_UNAVAILABLE, _CONSOLE_PAGE_AUTHENTICATE, _CONSOLE_PAGE_SERIAL, _CONSOLE_PAGE_VIEWER) = range(4) class vmmConsolePages(vmmGObjectUI): """ Handles all the complex UI handling dictated by the spice/vnc widgets """ def __init__(self, vm, builder, topwin): vmmGObjectUI.__init__(self, None, None, builder=builder, topwin=topwin) self.vm = vm self._pointer_is_grabbed = False self._change_title() self.vm.connect("state-changed", self._change_title) self._force_resize = False # State for disabling modifiers when keyboard is grabbed self._accel_groups = Gtk.accel_groups_from_object(self.topwin) self._gtk_settings_accel = None self._gtk_settings_mnemonic = None # Initialize display widget self._viewer = None # Fullscreen toolbar self._send_key_button = None self._fs_toolbar = None self._fs_drawer = None self._keycombo_toolbar = self._build_keycombo_menu() self._keycombo_menu = self._build_keycombo_menu() self._init_fs_toolbar() # Make viewer widget background always be black black = Gdk.Color(0, 0, 0) self.widget("console-gfx-viewport").modify_bg(Gtk.StateType.NORMAL, black) self.widget("console-pages").set_show_tabs(False) self.widget("serial-pages").set_show_tabs(False) self._serial_consoles = [] self._init_menus() # Signals are added by vmmDetails. Don't use connect_signals here # or it changes will be overwritten self.widget("console-gfx-scroll").connect("size-allocate", self._scroll_size_allocate) self._refresh_widget_states() self._refresh_scaling_from_settings() self.add_gsettings_handle( self.vm.on_console_scaling_changed( self._refresh_scaling_from_settings)) self._refresh_resizeguest_from_settings() self.add_gsettings_handle( self.vm.on_console_resizeguest_changed( self._refresh_resizeguest_from_settings)) self.add_gsettings_handle( self.config.on_console_accels_changed(self._refresh_enable_accel)) def is_visible(self): if self.topwin: return self.topwin.get_visible() else: return False def _cleanup(self): self.vm = None if self._viewer: self._viewer.cleanup() self._viewer = None self._keycombo_toolbar.destroy() self._keycombo_toolbar = None self._fs_drawer.destroy() self._fs_drawer = None self._fs_toolbar.destroy() self._fs_toolbar = None for serial in self._serial_consoles: serial.cleanup() self._serial_consoles = [] ########################## # Initialization helpers # ########################## def _build_keycombo_menu(self): # Shared with vmmDetails menu = Gtk.Menu() def make_item(name, combo): item = Gtk.MenuItem.new_with_mnemonic(name) item.connect("activate", self._do_send_key, combo) menu.add(item) make_item("Ctrl+Alt+_Backspace", ["Control_L", "Alt_L", "BackSpace"]) make_item("Ctrl+Alt+_Delete", ["Control_L", "Alt_L", "Delete"]) menu.add(Gtk.SeparatorMenuItem()) for i in range(1, 13): make_item("Ctrl+Alt+F_%d" % i, ["Control_L", "Alt_L", "F%d" % i]) menu.add(Gtk.SeparatorMenuItem()) make_item("_Printscreen", ["Print"]) menu.show_all() return menu def _init_fs_toolbar(self): scroll = self.widget("console-gfx-scroll") pages = self.widget("console-pages") pages.remove(scroll) self._fs_toolbar = Gtk.Toolbar() self._fs_toolbar.set_show_arrow(False) self._fs_toolbar.set_no_show_all(True) self._fs_toolbar.set_style(Gtk.ToolbarStyle.BOTH_HORIZ) # Exit fullscreen button button = Gtk.ToolButton.new_from_stock(Gtk.STOCK_LEAVE_FULLSCREEN) button.set_tooltip_text(_("Leave fullscreen")) button.show() self._fs_toolbar.add(button) button.connect("clicked", self._leave_fullscreen) def keycombo_menu_clicked(src): ignore = src def menu_location(menu, toolbar): ignore = menu ignore, x, y = toolbar.get_window().get_origin() height = toolbar.get_window().get_height() return x, y + height, True self._keycombo_toolbar.popup(None, None, menu_location, self._fs_toolbar, 0, Gtk.get_current_event_time()) self._send_key_button = Gtk.ToolButton() self._send_key_button.set_icon_name( "preferences-desktop-keyboard-shortcuts") self._send_key_button.set_tooltip_text(_("Send key combination")) self._send_key_button.show_all() self._send_key_button.connect("clicked", keycombo_menu_clicked) self._fs_toolbar.add(self._send_key_button) self._fs_drawer = AutoDrawer() self._fs_drawer.set_active(False) self._fs_drawer.set_over(self._fs_toolbar) self._fs_drawer.set_under(scroll) self._fs_drawer.set_offset(-1) self._fs_drawer.set_fill(False) self._fs_drawer.set_overlap_pixels(1) self._fs_drawer.set_nooverlap_pixels(0) self._fs_drawer.period = 20 self._fs_drawer.step = .1 self._fs_drawer.show_all() pages.add(self._fs_drawer) def _init_menus(self): # Serial list menu smenu = Gtk.Menu() smenu.connect("show", self._populate_serial_menu) self.widget("details-menu-view-serial-list").set_submenu(smenu) # Keycombo menu (ctrl+alt+del etc.) self.widget("details-menu-send-key").set_submenu(self._keycombo_menu) ################# # Internal APIs # ################# def _change_title(self, ignore1=None): title = (_("%(vm-name)s on %(connection-name)s") % { "vm-name": self.vm.get_name_or_title(), "connection-name": self.vm.conn.get_pretty_desc(), }) if self._pointer_is_grabbed and self._viewer: keystr = self._viewer.console_get_grab_keys() keymsg = _("Press %s to release pointer.") % keystr title = keymsg + " " + title self.topwin.set_title(title) def _someone_has_focus(self): if (self._viewer and self._viewer.console_has_focus() and self._viewer.console_is_open()): return True for serial in self._serial_consoles: if (serial.terminal and serial.terminal.get_property("has-focus")): return True def _disable_modifiers(self): if self._gtk_settings_accel is not None: return for g in self._accel_groups: self.topwin.remove_accel_group(g) settings = Gtk.Settings.get_default() self._gtk_settings_accel = settings.get_property('gtk-menu-bar-accel') settings.set_property('gtk-menu-bar-accel', None) self._gtk_settings_mnemonic = settings.get_property( "gtk-enable-mnemonics") settings.set_property("gtk-enable-mnemonics", False) def _enable_modifiers(self): if self._gtk_settings_accel is None: return settings = Gtk.Settings.get_default() settings.set_property('gtk-menu-bar-accel', self._gtk_settings_accel) self._gtk_settings_accel = None if self._gtk_settings_mnemonic is not None: settings.set_property("gtk-enable-mnemonics", self._gtk_settings_mnemonic) for g in self._accel_groups: self.topwin.add_accel_group(g) def _refresh_enable_accel(self): # Make sure modifiers are up to date self._viewer_focus_changed() def _do_send_key(self, src, keys): ignore = src if keys is not None: self._viewer.console_send_keys(keys) ########################### # Resize and scaling APIs # ########################### def _scroll_size_allocate(self, src_ignore, req): if (not self._viewer or not self._viewer.console_get_desktop_resolution()): return scroll = self.widget("console-gfx-scroll") is_scale = self._viewer.console_get_scaling() is_resizeguest = self._viewer.console_get_resizeguest() dx = 0 dy = 0 align_ratio = float(req.width) / float(req.height) # pylint: disable=unpacking-non-sequence desktop_w, desktop_h = self._viewer.console_get_desktop_resolution() if desktop_h == 0: return desktop_ratio = float(desktop_w) / float(desktop_h) if is_scale or self._force_resize: # Make sure we never show scrollbars when scaling scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.NEVER) else: scroll.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) if not self._force_resize and is_resizeguest: # With resize guest, we don't want to maintain aspect ratio, # since the guest can resize to arbitrary resolutions. self._viewer.console_set_size_request(req.width, req.height) return if not is_scale or self._force_resize: # Scaling disabled is easy, just force the VNC widget size. Since # we are inside a scrollwindow, it shouldn't cause issues. self._force_resize = False self._viewer.console_set_size_request(desktop_w, desktop_h) return # Make sure there is no hard size requirement so we can scale down self._viewer.console_set_size_request(-1, -1) # Make sure desktop aspect ratio is maintained if align_ratio > desktop_ratio: desktop_w = int(req.height * desktop_ratio) desktop_h = req.height dx = (req.width - desktop_w) / 2 else: desktop_w = req.width desktop_h = int(req.width / desktop_ratio) dy = (req.height - desktop_h) / 2 viewer_alloc = Gdk.Rectangle() viewer_alloc.x = dx viewer_alloc.y = dy viewer_alloc.width = desktop_w viewer_alloc.height = desktop_h self._viewer.console_size_allocate(viewer_alloc) def _refresh_resizeguest_from_settings(self): tooltip = "" if self._viewer: if self._viewer.viewer_type != "spice": tooltip = ( _("Graphics type '%s' does not support auto resize.") % self._viewer.viewer_type) elif not self._viewer.console_has_agent(): tooltip = _("Guest agent is not available.") val = self.vm.get_console_resizeguest() widget = self.widget("details-menu-view-resizeguest") widget.set_tooltip_text(tooltip) widget.set_sensitive(not bool(tooltip)) if not tooltip: self.widget("details-menu-view-resizeguest").set_active(bool(val)) self._sync_resizeguest_with_display() def _sync_resizeguest_with_display(self): if not self._viewer: return val = bool(self.vm.get_console_resizeguest()) self._viewer.console_set_resizeguest(val) self.widget("console-gfx-scroll").queue_resize() def _resizeguest_ui_changed_cb(self, src): if not src.get_sensitive(): return val = int(self.widget("details-menu-view-resizeguest").get_active()) self.vm.set_console_resizeguest(val) self._sync_resizeguest_with_display() def _do_size_to_vm(self, src_ignore): # Resize the console to best fit the VM resolution if not self._viewer: return if not self._viewer.console_get_desktop_resolution(): return self.topwin.unmaximize() self.topwin.resize(1, 1) self._force_resize = True self.widget("console-gfx-scroll").queue_resize() ################ # Scaling APIs # ################ def _refresh_scaling_from_settings(self): scale_type = self.vm.get_console_scaling() self.widget("details-menu-view-scale-always").set_active( scale_type == self.config.CONSOLE_SCALE_ALWAYS) self.widget("details-menu-view-scale-never").set_active( scale_type == self.config.CONSOLE_SCALE_NEVER) self.widget("details-menu-view-scale-fullscreen").set_active( scale_type == self.config.CONSOLE_SCALE_FULLSCREEN) self._sync_scaling_with_display() def _scaling_ui_changed_cb(self, src): # Called from details.py if not src.get_active(): return scale_type = 0 if src == self.widget("details-menu-view-scale-always"): scale_type = self.config.CONSOLE_SCALE_ALWAYS elif src == self.widget("details-menu-view-scale-fullscreen"): scale_type = self.config.CONSOLE_SCALE_FULLSCREEN elif src == self.widget("details-menu-view-scale-never"): scale_type = self.config.CONSOLE_SCALE_NEVER self.vm.set_console_scaling(scale_type) self._sync_scaling_with_display() def _sync_scaling_with_display(self): if not self._viewer: return curscale = self._viewer.console_get_scaling() fs = self.widget("control-fullscreen").get_active() scale_type = self.vm.get_console_scaling() if (scale_type == self.config.CONSOLE_SCALE_NEVER and curscale is True): self._viewer.console_set_scaling(False) elif (scale_type == self.config.CONSOLE_SCALE_ALWAYS and curscale is False): self._viewer.console_set_scaling(True) elif (scale_type == self.config.CONSOLE_SCALE_FULLSCREEN and curscale != fs): self._viewer.console_set_scaling(fs) # Refresh viewer size self.widget("console-gfx-scroll").queue_resize() ################### # Fullscreen APIs # ################### def _refresh_can_fullscreen(self): cpage = self.widget("console-pages").get_current_page() dpage = self.widget("details-pages").get_current_page() allow_fullscreen = bool(dpage == DETAILS_PAGE_CONSOLE and cpage == _CONSOLE_PAGE_VIEWER and self._viewer and self._viewer.console_is_open()) self.widget("control-fullscreen").set_sensitive(allow_fullscreen) self.widget("details-menu-view-fullscreen").set_sensitive( allow_fullscreen) def _leave_fullscreen(self, ignore=None): self._change_fullscreen(False) def _change_fullscreen(self, do_fullscreen): self.widget("control-fullscreen").set_active(do_fullscreen) if do_fullscreen: self.topwin.fullscreen() self._fs_toolbar.show() self._fs_drawer.set_active(True) self.widget("toolbar-box").hide() self.widget("details-menubar").hide() else: self._fs_toolbar.hide() self._fs_drawer.set_active(False) self.topwin.unfullscreen() if self.widget("details-menu-view-toolbar").get_active(): self.widget("toolbar-box").show() self.widget("details-menubar").show() self._sync_scaling_with_display() ########################## # State tracking methods # ########################## def _show_vm_status_unavailable(self): if self.vm.is_crashed(): self._activate_unavailable_page(_("Guest has crashed")) else: self._activate_unavailable_page(_("Guest not running")) def _close_viewer(self): if self._viewer is None: return self._viewer.console_remove_display_from_widget( self.widget("console-gfx-viewport")) self._viewer.cleanup() self._viewer = None self._leave_fullscreen() for serial in self._serial_consoles: serial.close() def _update_vm_widget_states(self): page = self.widget("console-pages").get_current_page() if self.vm.is_runable(): self._show_vm_status_unavailable() elif (page == _CONSOLE_PAGE_UNAVAILABLE or page == _CONSOLE_PAGE_VIEWER): if self._viewer and self._viewer.console_is_open(): self._activate_viewer_page() else: self._init_viewer() # Update other state self._refresh_widget_states() ################### # Page Navigation # ################### def _activate_unavailable_page(self, msg): """ This function is passed to serialcon.py at least, so change with care """ self._close_viewer() self.widget("console-pages").set_current_page( _CONSOLE_PAGE_UNAVAILABLE) self.widget("console-unavailable").set_label("" + msg + "") def _activate_auth_page(self, withPassword, withUsername): (pw, username) = self.config.get_console_password(self.vm) self.widget("console-auth-password").set_visible(withPassword) self.widget("label-auth-password").set_visible(withPassword) self.widget("console-auth-username").set_visible(withUsername) self.widget("label-auth-username").set_visible(withUsername) self.widget("console-auth-username").set_text(username) self.widget("console-auth-password").set_text(pw) self.widget("console-auth-remember").set_sensitive( bool(self.config.has_keyring())) if self.config.has_keyring(): self.widget("console-auth-remember").set_active( bool(pw and username)) self.widget("console-pages").set_current_page( _CONSOLE_PAGE_AUTHENTICATE) if withUsername: self.widget("console-auth-username").grab_focus() else: self.widget("console-auth-password").grab_focus() def _activate_viewer_page(self): self.widget("console-pages").set_current_page(_CONSOLE_PAGE_VIEWER) if self._viewer: self._viewer.console_grab_focus() def _page_changed(self, src, origpage, newpage): ignore = src ignore = origpage # Hide the contents of all other pages, so they don't screw # up window sizing for i in range(self.widget("console-pages").get_n_pages()): self.widget("console-pages").get_nth_page(i).set_visible( i == newpage) self.idle_add(self._refresh_widget_states) def _refresh_widget_states(self): pagenum = self.widget("console-pages").get_current_page() paused = self.vm.is_paused() is_viewer = (pagenum == _CONSOLE_PAGE_VIEWER and self._viewer and self._viewer.console_is_open()) self.widget("details-menu-vm-screenshot").set_sensitive(is_viewer) self.widget("details-menu-usb-redirection").set_sensitive( bool(is_viewer and self._viewer and self._viewer.console_has_usb_redirection() and self.vm.has_spicevmc_type_redirdev())) can_sendkey = (is_viewer and not paused) self._send_key_button.set_sensitive(can_sendkey) for c in self._keycombo_menu.get_children(): c.set_sensitive(can_sendkey) self._refresh_can_fullscreen() ######################### # Viewer login attempts # ######################### def _init_viewer(self): if self._viewer or not self.is_visible(): # Don't try and login for these cases return ginfo = None try: gdevs = self.vm.get_graphics_devices() gdev = gdevs and gdevs[0] or None if gdev: ginfo = ConnectionInfo(self.vm.conn, gdev) except Exception, e: # We can fail here if VM is destroyed: xen is a bit racy # and can't handle domain lookups that soon after logging.exception("Getting graphics console failed: %s", str(e)) return if ginfo is None: logging.debug("No graphics configured for guest") self._activate_unavailable_page( _("Graphical console not configured for guest")) return if ginfo.gtype not in self.config.embeddable_graphics(): logging.debug("Don't know how to show graphics type '%s' " "disabling console page", ginfo.gtype) msg = (_("Cannot display graphical console type '%s'") % ginfo.gtype) self._activate_unavailable_page(msg) return if ginfo.is_bad_localhost(): self._activate_unavailable_page( _("Guest is on a remote host with transport '%s'\n" "but is only configured to listen locally.\n" "Connect using 'ssh' transport or change the\n" "guest's listen address." % ginfo.transport)) return if not ginfo.console_active(): self._activate_unavailable_page( _("Graphical console is not yet active for guest")) return self._activate_unavailable_page( _("Connecting to graphical console for guest")) logging.debug("Starting connect process for %s", ginfo.logstring()) try: if ginfo.gtype == "vnc": viewer_class = VNCViewer elif ginfo.gtype == "spice": viewer_class = SpiceViewer self._viewer = viewer_class(ginfo) self._connect_viewer_signals() self._refresh_enable_accel() self._viewer.console_open() except Exception, e: logging.exception("Error connection to graphical console") self._activate_unavailable_page( _("Error connecting to graphical console") + ":\n%s" % e) def _set_credentials(self, src_ignore=None): passwd = self.widget("console-auth-password") username = self.widget("console-auth-username") if passwd.get_visible(): self._viewer.console_set_password(passwd.get_text()) if username.get_visible(): self._viewer.console_set_username(username.get_text()) if self.widget("console-auth-remember").get_active(): self.config.set_console_password(self.vm, passwd.get_text(), username.get_text()) ########################## # Viewer signal handling # ########################## def _viewer_add_display(self, ignore, display): self.widget("console-gfx-viewport").add(display) # Sync initial settings self._sync_scaling_with_display() self._refresh_resizeguest_from_settings() def _pointer_grabbed(self, ignore): self._pointer_is_grabbed = True self._change_title() def _pointer_ungrabbed(self, ignore): self._pointer_is_grabbed = False self._change_title() def _viewer_allocate_cb(self, src, ignore): self.widget("console-gfx-scroll").queue_resize() def _viewer_focus_changed(self, ignore1=None, ignore2=None): force_accel = self.config.get_console_accels() if force_accel: self._enable_modifiers() elif self._someone_has_focus(): self._disable_modifiers() else: self._enable_modifiers() def _viewer_auth_rejected(self, ignore, errmsg): self._close_viewer() self._activate_unavailable_page(errmsg) def _viewer_auth_error(self, ignore, errmsg, viewer_will_disconnect): errmsg = _("Viewer authentication error: %s") % errmsg self.err.val_err(errmsg) if viewer_will_disconnect: # GtkVNC will disconnect after an auth error, so lets do it for # them and re-init the viewer (which will be triggered by # update_vm_widget_states if needed) self._close_viewer() self._activate_unavailable_page(errmsg) self._update_vm_widget_states() def _viewer_need_auth(self, ignore, withPassword, withUsername): self._activate_auth_page(withPassword, withUsername) def _viewer_agent_connected(self, ignore): self._refresh_resizeguest_from_settings() def _viewer_usb_redirect_error(self, ignore, errstr): self.err.show_err(_("USB redirection error"), text2=str(errstr), modal=True) def _viewer_disconnected(self, ignore): errout = "" if self._viewer: errout = self._viewer.console_get_err_output() self.widget("console-pages").set_current_page( _CONSOLE_PAGE_UNAVAILABLE) self._close_viewer() logging.debug("Viewer disconnected") # Make sure modifiers are set correctly self._viewer_focus_changed() if self.vm.is_runable(): # Exit was probably for legitimate reasons self._show_vm_status_unavailable() else: error = _("Error: viewer connection to hypervisor host got " "refused or disconnected!") if errout: logging.debug("Error output from closed console: %s", errout) error += "\n\nError: %s" % errout self._activate_unavailable_page(error) self._refresh_resizeguest_from_settings() def _viewer_connected(self, ignore): logging.debug("Viewer connected") self._activate_viewer_page() # Make sure modifiers are set correctly self._viewer_focus_changed() def _connect_viewer_signals(self): self._viewer.connect("add-display-widget", self._viewer_add_display) self._viewer.connect("pointer-grab", self._pointer_grabbed) self._viewer.connect("pointer-ungrab", self._pointer_ungrabbed) self._viewer.connect("size-allocate", self._viewer_allocate_cb) self._viewer.connect("focus-in-event", self._viewer_focus_changed) self._viewer.connect("focus-out-event", self._viewer_focus_changed) self._viewer.connect("connected", self._viewer_connected) self._viewer.connect("disconnected", self._viewer_disconnected) self._viewer.connect("auth-error", self._viewer_auth_error) self._viewer.connect("auth-rejected", self._viewer_auth_rejected) self._viewer.connect("need-auth", self._viewer_need_auth) self._viewer.connect("agent-connected", self._viewer_agent_connected) self._viewer.connect("usb-redirect-error", self._viewer_usb_redirect_error) ########################### # Serial console handling # ########################### def _activate_default_console_page(self): """ Find the default graphical or serial console for the VM """ if self.vm.get_graphics_devices() or not self.vm.get_serial_devs(): return # We iterate through the 'console' menu and activate the first # valid entry... it's the easiest thing to do to hit all the right # code paths. self._populate_serial_menu() menu = self.widget("details-menu-view-serial-list").get_submenu() for child in menu.get_children(): if isinstance(child, Gtk.SeparatorMenuItem): break if child.get_sensitive(): child.toggled() break def _console_menu_toggled(self, src, dev): self.widget("details-pages").set_current_page(DETAILS_PAGE_CONSOLE) if dev.virtual_device_type == "graphics": self.widget("console-pages").set_current_page(_CONSOLE_PAGE_VIEWER) return target_port = dev.vmmindex serial = None name = src.get_label() for s in self._serial_consoles: if s.name == name: serial = s break if not serial: serial = vmmSerialConsole(self.vm, target_port, name) serial.terminal.connect("focus-in-event", self._viewer_focus_changed) serial.terminal.connect("focus-out-event", self._viewer_focus_changed) title = Gtk.Label(label=name) self.widget("serial-pages").append_page(serial.box, title) self._serial_consoles.append(serial) serial.open_console() page_idx = self._serial_consoles.index(serial) self.widget("console-pages").set_current_page(_CONSOLE_PAGE_SERIAL) self.widget("serial-pages").set_current_page(page_idx) def _build_serial_menu_items(self, menu_item_cb): devs = self.vm.get_serial_devs() if len(devs) == 0: menu_item_cb(_("No text console available"), radio=False, sensitive=False) return active_label = None if (self.widget("console-pages").get_current_page() == _CONSOLE_PAGE_SERIAL): serial_page = self.widget("serial-pages").get_current_page() if len(self._serial_consoles) > serial_page: active_label = self._serial_consoles[serial_page].name for dev in devs: if dev.virtual_device_type == "console": label = _("Text Console %d") % (dev.vmmindex + 1) else: label = _("Serial %d") % (dev.vmmindex + 1) tooltip = vmmSerialConsole.can_connect(self.vm, dev) sensitive = not bool(tooltip) active = (sensitive and label == active_label) menu_item_cb(label, sensitive=sensitive, active=active, tooltip=tooltip, cb=self._console_menu_toggled, cbdata=dev) def _build_graphical_menu_items(self, menu_item_cb): devs = self.vm.get_graphics_devices() if len(devs) == 0: menu_item_cb(_("No graphical console available"), radio=False, sensitive=False) return active = (self.widget("console-pages").get_current_page() != _CONSOLE_PAGE_SERIAL) for idx, dev in enumerate(devs): label = (_("Graphical Console") + " " + dev.pretty_type_simple(dev.type)) sensitive = True tooltip = None if idx > 0: label += " %s" % (idx + 1) sensitive = False tooltip = _("virt-manager does not support more " "that one graphical console") menu_item_cb(label, active=active, sensitive=sensitive, tooltip=tooltip, cb=self._console_menu_toggled, cbdata=dev) def _populate_serial_menu(self, ignore=None): src = self.widget("details-menu-view-serial-list").get_submenu() for child in src: src.remove(child) def menu_item_cb(label, sensitive=True, active=False, radio=True, tooltip=None, cb=None, cbdata=None): if radio: item = Gtk.RadioMenuItem(menu_item_cb.radio_group) if menu_item_cb.radio_group is None: menu_item_cb.radio_group = item item.set_label(label) else: item = Gtk.MenuItem.new_with_label(label) item.set_sensitive(sensitive) if active: item.set_active(True) if tooltip: item.set_tooltip_text(tooltip) if cb and sensitive: item.connect("toggled", cb, cbdata) src.add(item) menu_item_cb.radio_group = None self._build_serial_menu_items(menu_item_cb) src.add(Gtk.SeparatorMenuItem()) self._build_graphical_menu_items(menu_item_cb) src.show_all() ########################## # API used by vmmDetails # ########################## def details_viewer_is_visible(self): return bool(self._viewer and self._viewer.console_get_visible()) def details_viewer_has_usb_redirection(self): return bool(self._viewer and self._viewer.console_has_usb_redirection()) def details_viewer_get_usb_widget(self): return self._viewer.console_get_usb_widget() def details_viewer_get_pixbuf(self): return self._viewer.console_get_pixbuf() def details_close_viewer(self): return self._close_viewer() def details_activate_default_console_page(self): return self._activate_default_console_page() def details_update_widget_states(self): return self._update_vm_widget_states() def details_refresh_can_fullscreen(self): return self._refresh_can_fullscreen() def details_resizeguest_ui_changed_cb(self, *args, **kwargs): return self._resizeguest_ui_changed_cb(*args, **kwargs) def details_page_changed(self, *args, **kwargs): return self._page_changed(*args, **kwargs) def details_scaling_ui_changed_cb(self, *args, **kwargs): return self._scaling_ui_changed_cb(*args, **kwargs) def details_size_to_vm(self, *args, **kwargs): return self._do_size_to_vm(*args, **kwargs) def details_toggle_fullscreen(self, src): do_fullscreen = src.get_active() self._change_fullscreen(do_fullscreen) def details_auth_login(self, ignore): self._set_credentials()