class vmmConsolePages(vmmGObjectUI): def __init__(self, vm, window): vmmGObjectUI.__init__(self, None, None) self.vm = vm self.windowname = "vmm-details" self.window = window self.topwin = self.widget(self.windowname) self.err = vmmErrorDialog(self.topwin) self.pointer_is_grabbed = False self.change_title() self.vm.connect("config-changed", self.change_title) # 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 self.tunnels = None self.viewerRetriesScheduled = 0 self.viewerRetryDelay = 125 self._viewer_connected = False self.viewer_connecting = False self.scale_type = self.vm.get_console_scaling() # Fullscreen toolbar self.send_key_button = None self.fs_toolbar = None self.fs_drawer = None self.keycombo_menu = uihelpers.build_keycombo_menu(self.send_key) self.init_fs_toolbar() finish_img = gtk.image_new_from_stock(gtk.STOCK_YES, gtk.ICON_SIZE_BUTTON) self.widget("console-auth-login").set_image(finish_img) # Make viewer widget background always be black black = gtk.gdk.Color(0, 0, 0) self.widget("console-vnc-viewport").modify_bg(gtk.STATE_NORMAL, black) # Signals are added by vmmDetails. Don't use connect_signals here # or it changes will be overwritten # Set console scaling self.add_gconf_handle( self.vm.on_console_scaling_changed(self.refresh_scaling)) scroll = self.widget("console-vnc-scroll") scroll.connect("size-allocate", self.scroll_size_allocate) self.add_gconf_handle( self.config.on_console_accels_changed(self.set_enable_accel)) self.add_gconf_handle( self.config.on_keys_combination_changed(self.grab_keys_changed)) self.page_changed() def is_visible(self): if self.topwin.flags() & gtk.VISIBLE: return 1 return 0 def _cleanup(self): self.vm = None if self.viewer: self.viewer.cleanup() self.viewer = None self.keycombo_menu.destroy() self.keycombo_menu = None self.fs_drawer.destroy() self.fs_drawer = None self.fs_toolbar.destroy() self.fs_toolbar = None ########################## # Initialization helpers # ########################## def init_fs_toolbar(self): scroll = self.widget("console-vnc-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.TOOLBAR_BOTH_HORIZ) # Exit fullscreen button button = gtk.ToolButton(gtk.STOCK_LEAVE_FULLSCREEN) util.tooltip_wrapper(button, _("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 x, y = toolbar.window.get_origin() ignore, height = toolbar.window.get_size() return x, y + height, True self.keycombo_menu.popup(None, None, menu_location, 0, gtk.get_current_event_time(), self.fs_toolbar) self.send_key_button = gtk.ToolButton() self.send_key_button.set_icon_name( "preferences-desktop-keyboard-shortcuts") util.tooltip_wrapper(self.send_key_button, _("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.show_all() pages.add(self.fs_drawer) def change_title(self, ignore1=None): title = self.vm.get_name() + " " + _("Virtual Machine") if self.pointer_is_grabbed and self.viewer: keystr = self.viewer.get_grab_keys() keymsg = _("Press %s to release pointer.") % keystr title = keymsg + " " + title self.topwin.set_title(title) def grab_keyboard(self, do_grab): if self.viewer and not self.viewer.need_keygrab: return if (not do_grab or not self.viewer or not self.viewer.display): gtk.gdk.keyboard_ungrab() else: gtk.gdk.keyboard_grab(self.viewer.display.window) def viewer_focus_changed(self, ignore1=None, ignore2=None): has_focus = (self.viewer and self.viewer.display and self.viewer.display.get_property("has-focus")) force_accel = self.config.get_console_accels() if force_accel: self._enable_modifiers() elif has_focus and self.viewer_connected: self._disable_modifiers() else: self._enable_modifiers() self.grab_keyboard(has_focus) def pointer_grabbed(self, src_ignore): self.pointer_is_grabbed = True self.change_title() def pointer_ungrabbed(self, src_ignore): self.pointer_is_grabbed = False self.change_title() 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) if has_property(settings, "gtk-enable-mnemonics"): 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 grab_keys_changed(self, ignore1=None, ignore2=None, ignore3=None, ignore4=None): self.viewer.set_grab_keys() def set_enable_accel(self, ignore=None, ignore1=None, ignore2=None, ignore3=None): # Make sure modifiers are up to date self.viewer_focus_changed() def refresh_scaling(self, ignore1=None, ignore2=None, ignore3=None, ignore4=None): self.scale_type = self.vm.get_console_scaling() self.widget("details-menu-view-scale-always").set_active( self.scale_type == self.config.CONSOLE_SCALE_ALWAYS) self.widget("details-menu-view-scale-never").set_active( self.scale_type == self.config.CONSOLE_SCALE_NEVER) self.widget("details-menu-view-scale-fullscreen").set_active( self.scale_type == self.config.CONSOLE_SCALE_FULLSCREEN) self.update_scaling() def set_scale_type(self, src): if not src.get_active(): return if src == self.widget("details-menu-view-scale-always"): self.scale_type = self.config.CONSOLE_SCALE_ALWAYS elif src == self.widget("details-menu-view-scale-fullscreen"): self.scale_type = self.config.CONSOLE_SCALE_FULLSCREEN elif src == self.widget("details-menu-view-scale-never"): self.scale_type = self.config.CONSOLE_SCALE_NEVER self.vm.set_console_scaling(self.scale_type) self.update_scaling() def update_scaling(self): if not self.viewer: return curscale = self.viewer.get_scaling() fs = self.widget("control-fullscreen").get_active() vnc_scroll = self.widget("console-vnc-scroll") if (self.scale_type == self.config.CONSOLE_SCALE_NEVER and curscale == True): self.viewer.set_scaling(False) elif (self.scale_type == self.config.CONSOLE_SCALE_ALWAYS and curscale == False): self.viewer.set_scaling(True) elif (self.scale_type == self.config.CONSOLE_SCALE_FULLSCREEN and curscale != fs): self.viewer.set_scaling(fs) # Refresh viewer size vnc_scroll.queue_resize() def auth_login(self, ignore): self.set_credentials() self.activate_viewer_page() def toggle_fullscreen(self, src): do_fullscreen = src.get_active() self._change_fullscreen(do_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.update_scaling() def size_to_vm(self, src_ignore): # Resize the console to best fit the VM resolution if not self.viewer: return if not self.viewer.get_desktop_resolution(): return w, h = self.viewer.get_desktop_resolution() self.topwin.unmaximize() self.topwin.resize(1, 1) self.queue_scroll_resize_helper(w, h) def send_key(self, src, keys): ignore = src if keys != None: self.viewer.send_keys(keys) ########################## # State tracking methods # ########################## def view_vm_status(self): status = self.vm.status() if status == libvirt.VIR_DOMAIN_SHUTOFF: self.activate_unavailable_page(_("Guest not running")) else: if status == libvirt.VIR_DOMAIN_CRASHED: self.activate_unavailable_page(_("Guest has crashed")) def close_viewer(self): viewport = self.widget("console-vnc-viewport") if self.viewer is None: return v = self.viewer # close_viewer() can be reentered self.viewer = None w = v.display if w and w in viewport.get_children(): viewport.remove(w) v.close() self.viewer_connected = False self.leave_fullscreen() def update_widget_states(self, vm, status_ignore): runable = vm.is_runable() pages = self.widget("console-pages") page = pages.get_current_page() if runable: if page != PAGE_UNAVAILABLE: pages.set_current_page(PAGE_UNAVAILABLE) self.view_vm_status() return elif page in [PAGE_UNAVAILABLE, PAGE_VIEWER]: if self.viewer and self.viewer.is_open(): self.activate_viewer_page() else: self.viewerRetriesScheduled = 0 self.viewerRetryDelay = 125 self.try_login() return ################### # 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(PAGE_UNAVAILABLE) self.widget("details-menu-vm-screenshot").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) if withPassword: self.widget("console-auth-password").show() self.widget("label-auth-password").show() else: self.widget("console-auth-password").hide() self.widget("label-auth-password").hide() if withUsername: self.widget("console-auth-username").show() self.widget("label-auth-username").show() else: self.widget("console-auth-username").hide() self.widget("label-auth-username").hide() self.widget("console-auth-username").set_text(username) self.widget("console-auth-password").set_text(pw) if self.config.has_keyring(): self.widget("console-auth-remember").set_sensitive(True) if pw != "" or username != "": self.widget("console-auth-remember").set_active(True) else: self.widget("console-auth-remember").set_active(False) else: self.widget("console-auth-remember").set_sensitive(False) self.widget("console-pages").set_current_page(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(PAGE_VIEWER) self.widget("details-menu-vm-screenshot").set_sensitive(True) if self.viewer and self.viewer.display: self.viewer.display.grab_focus() def page_changed(self, ignore1=None, ignore2=None, ignore3=None): self.set_allow_fullscreen() def set_allow_fullscreen(self): cpage = self.widget("console-pages").get_current_page() dpage = self.widget("details-pages").get_current_page() allow_fullscreen = (dpage == 0 and cpage == PAGE_VIEWER and self.viewer_connected) self.widget("control-fullscreen").set_sensitive(allow_fullscreen) self.widget("details-menu-view-fullscreen").set_sensitive(allow_fullscreen) def disconnected(self): errout = "" if self.tunnels is not None: errout = self.tunnels.get_err_output() self.tunnels.close_all() self.tunnels = None self.close_viewer() logging.debug("Viewer disconnected") # Make sure modifiers are set correctly self.viewer_focus_changed() if (self.skip_connect_attempt() or self.guest_not_avail()): # Exit was probably for legitimate reasons self.view_vm_status() return 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) def _set_viewer_connected(self, val): self._viewer_connected = val self.set_allow_fullscreen() def _get_viewer_connected(self): return self._viewer_connected viewer_connected = property(_get_viewer_connected, _set_viewer_connected) def connected(self): self.viewer_connected = True logging.debug("Viewer connected") self.activate_viewer_page() # Had a succesfull connect, so reset counters now self.viewerRetriesScheduled = 0 self.viewerRetryDelay = 125 # Make sure modifiers are set correctly self.viewer_focus_changed() def schedule_retry(self): if self.viewerRetriesScheduled >= 10: logging.error("Too many connection failures, not retrying again") return self.timeout_add(self.viewerRetryDelay, self.try_login) if self.viewerRetryDelay < 2000: self.viewerRetryDelay = self.viewerRetryDelay * 2 def skip_connect_attempt(self): return (self.viewer or not self.is_visible()) def guest_not_avail(self): return (self.vm.is_shutoff() or self.vm.is_crashed()) def try_login(self, src_ignore=None): if self.viewer_connecting: return try: self.viewer_connecting = True self._try_login() finally: self.viewer_connecting = False def _try_login(self): if self.skip_connect_attempt(): # Don't try and login for these cases return if self.guest_not_avail(): # Guest isn't running, schedule another try self.activate_unavailable_page(_("Guest not running")) self.schedule_retry() 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) if ginfo.gtype == "spice": msg += ":\n %s" % self.config.get_spice_error() self.activate_unavailable_page(msg) return if ginfo.console_active(): self.activate_unavailable_page( _("Graphical console is not yet active for guest")) self.schedule_retry() 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": self.viewer = VNCViewer(self) self.widget("console-vnc-viewport").add(self.viewer.display) self.viewer.init_widget() elif ginfo.gtype == "spice": self.viewer = SpiceViewer(self) self.set_enable_accel() if ginfo.need_tunnel(): if self.tunnels: # Tunnel already open, no need to continue return self.tunnels = Tunnels(ginfo) self.viewer.open_fd(self.tunnels.open_new()) else: self.viewer.open_host(ginfo) except Exception, e: logging.exception("Error connection to graphical console") self.activate_unavailable_page( _("Error connecting to graphical console") + ":\n%s" % e)