console: Make API used by vmmDetails explicit

And hide everything else. Similar to what we did with the Viewers, this
should help clarify what the boundaries are here.
This commit is contained in:
Cole Robinson 2015-04-03 21:34:03 -04:00
parent f589f01665
commit 03dbbac806
2 changed files with 355 additions and 287 deletions

View File

@ -34,6 +34,10 @@ from .sshtunnels import ConnectionInfo
from .viewers import SpiceViewer, VNCViewer
class vmmConsolePages(vmmGObjectUI):
"""
Handles all the complex UI handling dictated by the spice/vnc widgets
"""
# Console pages
(CONSOLE_PAGE_UNAVAILABLE,
CONSOLE_PAGE_AUTHENTICATE,
@ -41,10 +45,6 @@ from .viewers import SpiceViewer, VNCViewer
CONSOLE_PAGE_OFFSET) = 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)
@ -52,12 +52,12 @@ class vmmConsolePages(vmmGObjectUI):
self._pointer_is_grabbed = False
self._change_title()
self.vm.connect("config-changed", self._change_title)
self.force_resize = False
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
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
@ -67,40 +67,40 @@ class vmmConsolePages(vmmGObjectUI):
self._viewer_is_connecting = False
# Fullscreen toolbar
self.send_key_button = None
self.fs_toolbar = None
self.fs_drawer = None
self.keycombo_menu = self.build_keycombo_menu(self.send_key)
self.init_fs_toolbar()
self._send_key_button = None
self._fs_toolbar = None
self._fs_drawer = None
self._keycombo_menu = self._build_keycombo_menu(self._do_send_key)
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.serial_tabs = []
self.last_gfx_page = 0
self._serial_tabs = []
self._last_gfx_page = 0
self._init_menus()
# Signals are added by vmmDetails. Don't use connect_signals here
# or it changes will be overwritten
self.refresh_can_fullscreen()
self.refresh_scaling_from_settings()
self._refresh_can_fullscreen()
self._refresh_scaling_from_settings()
self.add_gsettings_handle(
self.vm.on_console_scaling_changed(
self.refresh_scaling_from_settings))
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))
scroll = self.widget("console-gfx-scroll")
scroll.connect("size-allocate", self.scroll_size_allocate)
scroll.connect("size-allocate", self._scroll_size_allocate)
self.add_gsettings_handle(
self.config.on_console_accels_changed(self._refresh_enable_accel))
self.page_changed()
self._page_changed()
def is_visible(self):
@ -116,24 +116,23 @@ class vmmConsolePages(vmmGObjectUI):
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
self._keycombo_menu.destroy()
self._keycombo_menu = None
self._fs_drawer.destroy()
self._fs_drawer = None
self._fs_toolbar.destroy()
self._fs_toolbar = None
for serial in self.serial_tabs:
for serial in self._serial_tabs:
serial.cleanup()
self.serial_tabs = []
self._serial_tabs = []
##########################
# Initialization helpers #
##########################
@staticmethod
def build_keycombo_menu(cb):
def _build_keycombo_menu(self, cb):
# Shared with vmmDetails
menu = Gtk.Menu()
@ -156,22 +155,22 @@ class vmmConsolePages(vmmGObjectUI):
menu.show_all()
return menu
def init_fs_toolbar(self):
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)
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)
self._fs_toolbar.add(button)
button.connect("clicked", self._leave_fullscreen)
def keycombo_menu_clicked(src):
ignore = src
@ -182,32 +181,32 @@ class vmmConsolePages(vmmGObjectUI):
return x, y + height, True
self.keycombo_menu.popup(None, None, menu_location,
self.fs_toolbar, 0,
self._keycombo_menu.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(
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._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 = 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()
self._fs_drawer.show_all()
pages.add(self.fs_drawer)
pages.add(self._fs_drawer)
def _init_menus(self):
# Serial list menu
@ -215,6 +214,11 @@ class vmmConsolePages(vmmGObjectUI):
smenu.connect("show", self._populate_serial_menu)
self.widget("details-menu-view-serial-list").set_submenu(smenu)
#################
# Internal APIs #
#################
def _change_title(self, ignore1=None):
title = self.vm.get_name() + " " + _("Virtual Machine")
@ -226,61 +230,122 @@ class vmmConsolePages(vmmGObjectUI):
self.topwin.set_title(title)
def someone_has_focus(self):
def _someone_has_focus(self):
if (self._viewer and
self._viewer.console_has_focus() and
self._viewer_is_connected):
return True
for serial in self.serial_tabs:
for serial in self._serial_tabs:
if (serial.terminal and
serial.terminal.get_property("has-focus")):
return True
def viewer_is_visible(self):
return bool(self._viewer and self._viewer.console_get_visible())
def viewer_has_usb_redirection(self):
return bool(self._viewer and
self._viewer.console_has_usb_redirection())
def viewer_get_usb_widget(self):
return self._viewer.console_get_usb_widget()
def viewer_get_pixbuf(self):
return self._viewer.console_get_pixbuf()
def _disable_modifiers(self):
if self.gtk_settings_accel is not None:
if self._gtk_settings_accel is not None:
return
for g in self.accel_groups:
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')
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(
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:
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
settings.set_property('gtk-menu-bar-accel', self._gtk_settings_accel)
self._gtk_settings_accel = None
if self.gtk_settings_mnemonic is not None:
if self._gtk_settings_mnemonic is not None:
settings.set_property("gtk-enable-mnemonics",
self.gtk_settings_mnemonic)
self._gtk_settings_mnemonic)
for g in self.accel_groups:
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:
@ -298,18 +363,9 @@ class vmmConsolePages(vmmGObjectUI):
if not tooltip:
self.widget("details-menu-view-resizeguest").set_active(bool(val))
self.sync_resizeguest_with_display()
self._sync_resizeguest_with_display()
def resizeguest_ui_changed_cb(self, src):
# Called from details.py
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 sync_resizeguest_with_display(self):
def _sync_resizeguest_with_display(self):
if not self._viewer:
return
@ -317,7 +373,32 @@ class vmmConsolePages(vmmGObjectUI):
self._viewer.console_set_resizeguest(val)
self.widget("console-gfx-scroll").queue_resize()
def refresh_scaling_from_settings(self):
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)
@ -328,7 +409,7 @@ class vmmConsolePages(vmmGObjectUI):
self._sync_scaling_with_display()
def scaling_ui_changed_cb(self, src):
def _scaling_ui_changed_cb(self, src):
# Called from details.py
if not src.get_active():
return
@ -365,16 +446,24 @@ class vmmConsolePages(vmmGObjectUI):
# Refresh viewer size
self.widget("console-gfx-scroll").queue_resize()
def details_auth_login(self, ignore):
self.widget("console-pages").set_current_page(CONSOLE_PAGE_UNAVAILABLE)
self.set_credentials()
self.activate_viewer_page()
def toggle_fullscreen(self, src):
do_fullscreen = src.get_active()
self._change_fullscreen(do_fullscreen)
###################
# Fullscreen APIs #
###################
def leave_fullscreen(self, ignore=None):
def _refresh_can_fullscreen(self):
cpage = self.widget("console-pages").get_current_page()
dpage = self.widget("details-pages").get_current_page()
allow_fullscreen = (dpage == DETAILS_PAGE_CONSOLE and
cpage == self.CONSOLE_PAGE_VIEWER and
self._viewer_is_connected)
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):
@ -382,13 +471,13 @@ class vmmConsolePages(vmmGObjectUI):
if do_fullscreen:
self.topwin.fullscreen()
self.fs_toolbar.show()
self.fs_drawer.set_active(True)
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._fs_toolbar.hide()
self._fs_drawer.set_active(False)
self.topwin.unfullscreen()
if self.widget("details-menu-view-toolbar").get_active():
@ -397,41 +486,23 @@ class vmmConsolePages(vmmGObjectUI):
self._sync_scaling_with_display()
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.console_get_desktop_resolution():
return
self.topwin.unmaximize()
self.topwin.resize(1, 1)
self.force_resize = True
self.widget("console-gfx-scroll").queue_resize()
def send_key(self, src, keys):
ignore = src
if keys is not None:
self._viewer.console_send_keys(keys)
##########################
# State tracking methods #
##########################
def view_vm_status(self):
def _view_vm_status(self):
if not self.vm:
# window has been closed and no pages to update are available.
return
status = self.vm.status()
if status == libvirt.VIR_DOMAIN_SHUTOFF:
self.activate_unavailable_page(_("Guest not running"))
self._activate_unavailable_page(_("Guest not running"))
else:
if status == libvirt.VIR_DOMAIN_CRASHED:
self.activate_unavailable_page(_("Guest has crashed"))
self._activate_unavailable_page(_("Guest has crashed"))
def close_viewer(self):
def _close_viewer(self):
if self._viewer is None:
return
@ -445,30 +516,33 @@ class vmmConsolePages(vmmGObjectUI):
viewer.close()
self._viewer_is_connected = False
self.refresh_can_fullscreen()
self.leave_fullscreen()
self._refresh_can_fullscreen()
self._leave_fullscreen()
for serial in self.serial_tabs:
for serial in self._serial_tabs:
serial.close()
def update_widget_states(self, vm, status_ignore):
def _update_widget_states(self, vm, status_ignore):
runable = vm.is_runable()
paused = vm.is_paused()
pages = self.widget("console-pages")
page = pages.get_current_page()
self._send_key_button.set_sensitive(not (runable or paused))
if runable:
if page != CONSOLE_PAGE_UNAVAILABLE:
pages.set_current_page(CONSOLE_PAGE_UNAVAILABLE)
if page != self.CONSOLE_PAGE_UNAVAILABLE:
pages.set_current_page(self.CONSOLE_PAGE_UNAVAILABLE)
self.view_vm_status()
self._view_vm_status()
elif page in [CONSOLE_PAGE_UNAVAILABLE, CONSOLE_PAGE_VIEWER]:
elif page in [self.CONSOLE_PAGE_UNAVAILABLE, self.CONSOLE_PAGE_VIEWER]:
if self._viewer and self._viewer.console_is_open():
self.activate_viewer_page()
self._activate_viewer_page()
else:
self._viewerRetriesScheduled = 0
self._viewerRetryDelay = 125
self.try_login()
self._try_login()
return
@ -477,18 +551,19 @@ class vmmConsolePages(vmmGObjectUI):
# Page Navigation #
###################
def activate_unavailable_page(self, msg):
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._close_viewer()
self.widget("console-pages").set_current_page(
self.CONSOLE_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, withUsername):
def _activate_auth_page(self, withPassword, withUsername):
(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)
@ -514,11 +589,10 @@ class vmmConsolePages(vmmGObjectUI):
username))
self.widget("console-pages").set_current_page(
CONSOLE_PAGE_AUTHENTICATE)
self.CONSOLE_PAGE_AUTHENTICATE)
def activate_viewer_page(self):
self.widget("console-pages").set_current_page(CONSOLE_PAGE_VIEWER)
def _activate_viewer_page(self):
self.widget("console-pages").set_current_page(self.CONSOLE_PAGE_VIEWER)
self.widget("details-menu-vm-screenshot").set_sensitive(True)
if self._viewer:
self._viewer.console_grab_focus()
@ -528,7 +602,7 @@ class vmmConsolePages(vmmGObjectUI):
self.widget("details-menu-usb-redirection").set_sensitive(True)
return
def page_changed(self, ignore1=None, ignore2=None, newpage=None):
def _page_changed(self, ignore1=None, ignore2=None, newpage=None):
pagenum = self.widget("console-pages").get_current_page()
if newpage is not None:
@ -536,57 +610,51 @@ class vmmConsolePages(vmmGObjectUI):
w = self.widget("console-pages").get_nth_page(i)
w.set_visible(i == newpage)
if pagenum < CONSOLE_PAGE_OFFSET:
self.last_gfx_page = pagenum
self.refresh_can_fullscreen()
if pagenum < self.CONSOLE_PAGE_OFFSET:
self._last_gfx_page = pagenum
self._refresh_can_fullscreen()
def refresh_can_fullscreen(self):
cpage = self.widget("console-pages").get_current_page()
dpage = self.widget("details-pages").get_current_page()
allow_fullscreen = (dpage == DETAILS_PAGE_CONSOLE and
cpage == CONSOLE_PAGE_VIEWER and
self._viewer_is_connected)
#########################
# Viewer login attempts #
#########################
self.widget("control-fullscreen").set_sensitive(allow_fullscreen)
self.widget("details-menu-view-fullscreen").set_sensitive(allow_fullscreen)
def schedule_retry(self):
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)
self.timeout_add(self._viewerRetryDelay, self._try_login)
if self._viewerRetryDelay < 2000:
self._viewerRetryDelay = self._viewerRetryDelay * 2
def skip_connect_attempt(self):
def _skip_connect_attempt(self):
return (self._viewer or
not self.is_visible())
def guest_not_avail(self):
def _guest_not_avail(self):
return (self.vm.is_shutoff() or self.vm.is_crashed())
def try_login(self, src_ignore=None):
def _try_login(self, src_ignore=None):
if self._viewer_is_connecting:
return
try:
self._viewer_is_connecting = True
self._try_login()
self._do_try_login()
finally:
self._viewer_is_connecting = False
def _try_login(self):
if self.skip_connect_attempt():
def _do_try_login(self):
if self._skip_connect_attempt():
# Don't try and login for these cases
return
if self.guest_not_avail():
if self._guest_not_avail():
# Guest isn't running, schedule another try
self.activate_unavailable_page(_("Guest not running"))
self.schedule_retry()
self._activate_unavailable_page(_("Guest not running"))
self._schedule_retry()
return
ginfo = None
@ -603,7 +671,7 @@ class vmmConsolePages(vmmGObjectUI):
if ginfo is None:
logging.debug("No graphics configured for guest")
self.activate_unavailable_page(
self._activate_unavailable_page(
_("Graphical console not configured for guest"))
return
@ -614,11 +682,11 @@ class vmmConsolePages(vmmGObjectUI):
msg = (_("Cannot display graphical console type '%s'")
% ginfo.gtype)
self.activate_unavailable_page(msg)
self._activate_unavailable_page(msg)
return
if ginfo.is_bad_localhost():
self.activate_unavailable_page(
self._activate_unavailable_page(
_("Guest is on a remote host with transport '%s'\n"
"but is only configured to listen on locally.\n"
"Connect using 'ssh' transport or change the\n"
@ -626,12 +694,12 @@ class vmmConsolePages(vmmGObjectUI):
return
if not ginfo.console_active():
self.activate_unavailable_page(
self._activate_unavailable_page(
_("Graphical console is not yet active for guest"))
self.schedule_retry()
self._schedule_retry()
return
self.activate_unavailable_page(
self._activate_unavailable_page(
_("Connecting to graphical console for guest"))
logging.debug("Starting connect process for %s", ginfo.logstring())
@ -649,10 +717,10 @@ class vmmConsolePages(vmmGObjectUI):
self._viewer.console_open_ginfo(ginfo)
except Exception, e:
logging.exception("Error connection to graphical console")
self.activate_unavailable_page(
self._activate_unavailable_page(
_("Error connecting to graphical console") + ":\n%s" % e)
def set_credentials(self, src_ignore=None):
def _set_credentials(self, src_ignore=None):
passwd = self.widget("console-auth-password")
username = self.widget("console-auth-username")
@ -665,66 +733,6 @@ class vmmConsolePages(vmmGObjectUI):
self.config.set_console_password(self.vm, passwd.get_text(),
username.get_text())
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)
##########################
# Viewer signal handling #
@ -753,17 +761,17 @@ class vmmConsolePages(vmmGObjectUI):
if force_accel:
self._enable_modifiers()
elif self.someone_has_focus():
elif self._someone_has_focus():
self._disable_modifiers()
else:
self._enable_modifiers()
def _viewer_auth_error(self, viewer, errmsg):
viewer.close()
self.activate_unavailable_page(errmsg)
self._activate_unavailable_page(errmsg)
def _viewer_need_auth(self, ignore, withPassword, withUsername):
self.activate_auth_page(withPassword, withUsername)
self._activate_auth_page(withPassword, withUsername)
def _viewer_agent_connected(self, ignore):
self._refresh_resizeguest_from_settings()
@ -777,16 +785,17 @@ class vmmConsolePages(vmmGObjectUI):
if self._viewer:
errout = self._viewer.console_reset_tunnels()
self.widget("console-pages").set_current_page(CONSOLE_PAGE_UNAVAILABLE)
self.close_viewer()
self.widget("console-pages").set_current_page(
self.CONSOLE_PAGE_UNAVAILABLE)
self._close_viewer()
logging.debug("Viewer disconnected")
# Make sure modifiers are set correctly
self._viewer_focus_changed()
if self.guest_not_avail():
if self._guest_not_avail():
# Exit was probably for legitimate reasons
self.view_vm_status()
self._view_vm_status()
return
error = _("Error: viewer connection to hypervisor host got refused "
@ -795,15 +804,15 @@ class vmmConsolePages(vmmGObjectUI):
logging.debug("Error output from closed console: %s", errout)
error += "\n\nError: %s" % errout
self.activate_unavailable_page(error)
self._activate_unavailable_page(error)
self._refresh_resizeguest_from_settings()
def _viewer_connected(self, ignore):
self._viewer_is_connected = True
self.refresh_can_fullscreen()
self._refresh_can_fullscreen()
logging.debug("Viewer connected")
self.activate_viewer_page()
self._activate_viewer_page()
# Had a successful connect, so reset counters now
self._viewerRetriesScheduled = 0
@ -832,7 +841,7 @@ class vmmConsolePages(vmmGObjectUI):
# Serial console handling #
###########################
def activate_default_console_page(self):
def _activate_default_console_page(self):
if self.vm.get_graphics_devices() or not self.vm.get_serial_devs():
return
@ -847,14 +856,14 @@ class vmmConsolePages(vmmGObjectUI):
def _selected_serial_dev(self):
current_page = self.widget("console-pages").get_current_page()
if not current_page >= CONSOLE_PAGE_OFFSET:
if not current_page >= self.CONSOLE_PAGE_OFFSET:
return
serial_idx = current_page - CONSOLE_PAGE_OFFSET
if len(self.serial_tabs) < serial_idx:
serial_idx = current_page - self.CONSOLE_PAGE_OFFSET
if len(self._serial_tabs) < serial_idx:
return
return self.serial_tabs[serial_idx]
return self._serial_tabs[serial_idx]
def _make_serial_menu_label(self, dev):
if dev.virtual_device_type == "console":
@ -866,13 +875,13 @@ class vmmConsolePages(vmmGObjectUI):
self.widget("details-pages").set_current_page(DETAILS_PAGE_CONSOLE)
if dev.virtual_device_type == "graphics":
self.widget("console-pages").set_current_page(self.last_gfx_page)
self.widget("console-pages").set_current_page(self._last_gfx_page)
return
target_port = dev.vmmindex
name = self._make_serial_menu_label(dev)
serial = None
for s in self.serial_tabs:
for s in self._serial_tabs:
if s.name == name:
serial = s
break
@ -886,10 +895,10 @@ class vmmConsolePages(vmmGObjectUI):
title = Gtk.Label(label=name)
self.widget("console-pages").append_page(serial.box, title)
self.serial_tabs.append(serial)
self._serial_tabs.append(serial)
serial.open_console()
page_idx = self.serial_tabs.index(serial) + CONSOLE_PAGE_OFFSET
page_idx = self._serial_tabs.index(serial) + self.CONSOLE_PAGE_OFFSET
self.widget("console-pages").set_current_page(page_idx)
def _build_serial_menu_items(self, menu_item_cb):
@ -916,7 +925,7 @@ class vmmConsolePages(vmmGObjectUI):
def _build_graphical_menu_items(self, menu_item_cb):
showing_graphics = (
self.widget("console-pages").get_current_page() ==
CONSOLE_PAGE_VIEWER)
self.CONSOLE_PAGE_VIEWER)
# Populate graphical devices
devs = self.vm.get_graphics_devices()
@ -972,3 +981,54 @@ class vmmConsolePages(vmmGObjectUI):
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, *args, **kwargs):
return self._update_widget_states(*args, **kwargs)
def details_build_keycombo_menu(self, *args, **kwargs):
return self._build_keycombo_menu(*args, **kwargs)
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_send_key(self, *args, **kwargs):
return self._do_send_key(*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.widget("console-pages").set_current_page(
self.CONSOLE_PAGE_UNAVAILABLE)
self._set_credentials()
self._activate_viewer_page()

View File

@ -520,6 +520,8 @@ class vmmDetails(vmmGObjectUI):
"on_hostdev_rombar_toggled": lambda *x: self.enable_apply(
x, EDIT_HOSTDEV_ROMBAR),
"on_controller_model_combo_changed": (lambda *x:
self.enable_apply(x, EDIT_CONTROLLER_MODEL)),
"on_config_apply_clicked": self.config_apply,
"on_config_cancel_clicked": self.config_cancel,
@ -531,18 +533,25 @@ class vmmDetails(vmmGObjectUI):
"on_hw_list_button_press_event": self.popup_addhw_menu,
# Listeners stored in vmmConsolePages
"on_details_menu_view_fullscreen_activate": self.console.toggle_fullscreen,
"on_details_menu_view_size_to_vm_activate": self.console.size_to_vm,
"on_details_menu_view_scale_always_toggled": self.console.scaling_ui_changed_cb,
"on_details_menu_view_scale_fullscreen_toggled": self.console.scaling_ui_changed_cb,
"on_details_menu_view_scale_never_toggled": self.console.scaling_ui_changed_cb,
"on_details_menu_view_resizeguest_toggled": self.console.resizeguest_ui_changed_cb,
"on_details_menu_view_fullscreen_activate": (
self.console.details_toggle_fullscreen),
"on_details_menu_view_size_to_vm_activate": (
self.console.details_size_to_vm),
"on_details_menu_view_scale_always_toggled": (
self.console.details_scaling_ui_changed_cb),
"on_details_menu_view_scale_fullscreen_toggled": (
self.console.details_scaling_ui_changed_cb),
"on_details_menu_view_scale_never_toggled": (
self.console.details_scaling_ui_changed_cb),
"on_details_menu_view_resizeguest_toggled": (
self.console.details_resizeguest_ui_changed_cb),
"on_console_pages_switch_page": self.console.page_changed,
"on_console_auth_password_activate": self.console.details_auth_login,
"on_console_auth_login_clicked": self.console.details_auth_login,
"on_controller_model_combo_changed": lambda *x: self.enable_apply(x,
EDIT_CONTROLLER_MODEL),
"on_console_pages_switch_page": (
self.console.details_page_changed),
"on_console_auth_password_activate": (
self.console.details_auth_login),
"on_console_auth_login_clicked": (
self.console.details_auth_login),
})
# Deliberately keep all this after signal connection
@ -618,9 +627,9 @@ class vmmDetails(vmmGObjectUI):
return
self.topwin.hide()
if self.console.viewer_is_visible():
if self.console.details_viewer_is_visible():
try:
self.console.close_viewer()
self.console.details_close_viewer()
except:
logging.error("Failure when disconnecting from desktop server")
@ -688,8 +697,8 @@ class vmmDetails(vmmGObjectUI):
self.config.get_details_show_toolbar())
# Keycombo menu (ctrl+alt+del etc.)
self.keycombo_menu = self.console.build_keycombo_menu(
self.console.send_key)
self.keycombo_menu = self.console.details_build_keycombo_menu(
self.console.details_send_key)
self.widget("details-menu-send-key").set_submenu(self.keycombo_menu)
@ -1252,7 +1261,7 @@ class vmmDetails(vmmGObjectUI):
self.page_refresh(newpage)
self.sync_details_console_view(newpage)
self.console.refresh_can_fullscreen()
self.console.details_refresh_can_fullscreen()
def change_run_text(self, can_restore):
if can_restore:
@ -1297,12 +1306,11 @@ class vmmDetails(vmmGObjectUI):
self.widget("config-maxmem").set_sensitive(not ro)
# Disable send key menu entries for offline VM
self.console.send_key_button.set_sensitive(not (run or paused))
send_key = self.widget("details-menu-send-key")
for c in send_key.get_submenu().get_children():
c.set_sensitive(not (run or paused))
self.console.update_widget_states(vm, status)
self.console.details_update_widget_states(vm, status)
if not run:
self.activate_default_console_page()
@ -1346,7 +1354,7 @@ class vmmDetails(vmmGObjectUI):
# when the user runs a VM while they are focused on the details page,
# and we don't want to switch pages out from under them.
origpage = pages.get_current_page()
self.console.activate_default_console_page()
self.console.details_activate_default_console_page()
pages.set_current_page(origpage)
# activate_* are called from engine.py via CLI options
@ -1413,7 +1421,7 @@ class vmmDetails(vmmGObjectUI):
self.vm.get_connkey())
def control_vm_menu(self, src_ignore):
can_usb = bool(self.console.viewer_has_usb_redirection() and
can_usb = bool(self.console.details_viewer_has_usb_redirection() and
self.vm.has_spicevmc_type_redirdev())
self.widget("details-menu-usb-redirection").set_sensitive(can_usb)
@ -1464,7 +1472,7 @@ class vmmDetails(vmmGObjectUI):
ignore = src
spice_usbdev_dialog = self.err
spice_usbdev_widget = self.console.viewer_get_usb_widget()
spice_usbdev_widget = self.console.details_viewer_get_usb_widget()
if not spice_usbdev_widget:
self.err.show_err(_("Error initializing spice USB device widget"))
return
@ -1474,7 +1482,7 @@ class vmmDetails(vmmGObjectUI):
widget=spice_usbdev_widget)
def _take_screenshot(self):
image = self.console.viewer_get_pixbuf()
image = self.console.details_viewer_get_pixbuf()
metadata = {
'tEXt::Hypervisor URI': self.vm.conn.get_uri(),