mirror of
https://github.com/virt-manager/virt-manager.git
synced 2025-01-11 05:17:59 +03:00
View serial consoles as tabs in the details window. Allow viewing not just the primary console.
This commit is contained in:
parent
b9ed921a7c
commit
4ac507f181
@ -36,6 +36,7 @@ import cairo
|
||||
from virtManager.error import vmmErrorDialog
|
||||
from virtManager.addhardware import vmmAddHardware
|
||||
from virtManager.choosecd import vmmChooseCD
|
||||
from virtManager.serialcon import vmmSerialConsole
|
||||
|
||||
import virtinst
|
||||
import urlgrabber.progress as progress
|
||||
@ -68,14 +69,12 @@ PAGE_VNCVIEWER = 3
|
||||
PAGE_CONSOLE = 0
|
||||
PAGE_OVERVIEW = 1
|
||||
PAGE_DETAILS = 2
|
||||
PAGE_FIRST_CHAR = 3
|
||||
PAGE_DYNAMIC_OFFSET = 3
|
||||
|
||||
class vmmDetails(gobject.GObject):
|
||||
__gsignals__ = {
|
||||
"action-show-console": (gobject.SIGNAL_RUN_FIRST,
|
||||
gobject.TYPE_NONE, (str,str)),
|
||||
"action-show-terminal": (gobject.SIGNAL_RUN_FIRST,
|
||||
gobject.TYPE_NONE, (str,str)),
|
||||
"action-save-domain": (gobject.SIGNAL_RUN_FIRST,
|
||||
gobject.TYPE_NONE, (str,str)),
|
||||
"action-destroy-domain": (gobject.SIGNAL_RUN_FIRST,
|
||||
@ -115,6 +114,7 @@ class vmmDetails(gobject.GObject):
|
||||
topwin.set_title(self.title)
|
||||
|
||||
self.engine = engine
|
||||
self.dynamic_tabs = []
|
||||
|
||||
# Don't allowing changing network/disks for Dom0
|
||||
if self.vm.is_management_domain():
|
||||
@ -156,6 +156,25 @@ class vmmDetails(gobject.GObject):
|
||||
destroy.connect("activate", self.control_vm_destroy)
|
||||
menu.add(destroy)
|
||||
|
||||
smenu = gtk.Menu()
|
||||
smenu.connect("show", self.populate_serial_menu)
|
||||
self.window.get_widget("details-menu-view-serial-list").set_submenu(smenu)
|
||||
|
||||
self.serial_popup = gtk.Menu()
|
||||
|
||||
self.serial_copy = gtk.ImageMenuItem(gtk.STOCK_COPY)
|
||||
self.serial_popup.add(self.serial_copy)
|
||||
|
||||
self.serial_paste = gtk.ImageMenuItem(gtk.STOCK_PASTE)
|
||||
self.serial_popup.add(self.serial_paste)
|
||||
|
||||
self.serial_popup.add(gtk.SeparatorMenuItem())
|
||||
|
||||
self.serial_close = gtk.ImageMenuItem(_("Close tab"))
|
||||
close_image = gtk.Image()
|
||||
close_image.set_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU)
|
||||
self.serial_close.set_image(close_image)
|
||||
self.serial_popup.add(self.serial_close)
|
||||
|
||||
self.window.get_widget("hw-panel").set_show_tabs(False)
|
||||
|
||||
@ -237,7 +256,6 @@ class vmmDetails(gobject.GObject):
|
||||
"on_details_menu_graphics_activate": self.control_vm_console,
|
||||
"on_details_menu_view_toolbar_activate": self.toggle_toolbar,
|
||||
"on_details_menu_view_manager_activate": self.view_manager,
|
||||
"on_details_menu_view_serial_activate": self.control_vm_terminal,
|
||||
|
||||
"on_details_pages_switch_page": self.switch_page,
|
||||
|
||||
@ -431,6 +449,47 @@ class vmmDetails(gobject.GObject):
|
||||
else:
|
||||
self.window.get_widget("details-toolbar").hide()
|
||||
|
||||
def populate_serial_menu(self, src):
|
||||
for ent in src:
|
||||
src.remove(ent)
|
||||
|
||||
devs = self.vm.get_serial_devs()
|
||||
if len(devs) == 0:
|
||||
item = gtk.CheckMenuItem(_("No serial devices found"))
|
||||
item.set_sensitive(False)
|
||||
src.add(item)
|
||||
|
||||
usable_types = [ "pty" ]
|
||||
for dev in devs:
|
||||
sensitive = False
|
||||
msg = ""
|
||||
item = gtk.CheckMenuItem(dev[0])
|
||||
|
||||
if self.vm.get_connection().is_remote():
|
||||
msg = _("Serial console not yet supported over remote "
|
||||
"connection.")
|
||||
elif not self.vm.is_active():
|
||||
msg = _("Serial console not available for inactive guest.")
|
||||
elif not dev[1] in usable_types:
|
||||
msg = _("Console for device type '%s' not yet supported.") % \
|
||||
dev[1]
|
||||
elif dev[2] and not os.access(dev[2], os.R_OK | os.W_OK):
|
||||
msg = _("Can not access console path '%s'.") % str(dev[2])
|
||||
else:
|
||||
sensitive = True
|
||||
|
||||
if not sensitive:
|
||||
item.set_tooltip_text(msg)
|
||||
item.set_sensitive(sensitive)
|
||||
|
||||
if sensitive and self.dynamic_tabs.count(dev[0]):
|
||||
# Tab is already open, make sure marked as such
|
||||
item.set_active(True)
|
||||
item.connect("activate", self.control_serial_tab, dev[0], dev[2])
|
||||
src.add(item)
|
||||
|
||||
src.show_all()
|
||||
|
||||
def show(self):
|
||||
dialog = self.window.get_widget("vmm-details")
|
||||
if self.is_visible():
|
||||
@ -585,9 +644,6 @@ class vmmDetails(gobject.GObject):
|
||||
def control_vm_reboot(self, src):
|
||||
self.emit("action-reboot-domain", self.vm.get_connection().get_uri(), self.vm.get_uuid())
|
||||
|
||||
def control_vm_terminal(self, src):
|
||||
self.emit("action-show-terminal", self.vm.get_connection().get_uri(), self.vm.get_uuid())
|
||||
|
||||
def control_vm_console(self, src):
|
||||
self.emit("action-show-console", self.vm.get_connection().get_uri(), self.vm.get_uuid())
|
||||
|
||||
@ -608,11 +664,6 @@ class vmmDetails(gobject.GObject):
|
||||
def update_widget_states(self, vm, status):
|
||||
self.toggle_toolbar(self.window.get_widget("details-menu-view-toolbar"))
|
||||
|
||||
if vm.is_serial_console_tty_accessible():
|
||||
self.window.get_widget("details-menu-view-serial").set_sensitive(True)
|
||||
else:
|
||||
self.window.get_widget("details-menu-view-serial").set_sensitive(False)
|
||||
|
||||
if status in [ libvirt.VIR_DOMAIN_SHUTDOWN,
|
||||
libvirt.VIR_DOMAIN_SHUTOFF ] or vm.is_read_only():
|
||||
self.window.get_widget("details-menu-destroy").set_sensitive(False)
|
||||
@ -625,7 +676,6 @@ class vmmDetails(gobject.GObject):
|
||||
self.window.get_widget("config-vcpus").set_sensitive(True)
|
||||
self.window.get_widget("config-memory").set_sensitive(True)
|
||||
self.window.get_widget("config-maxmem").set_sensitive(True)
|
||||
self.window.get_widget("details-menu-view-serial").set_sensitive(False)
|
||||
else:
|
||||
self.window.get_widget("control-run").set_sensitive(False)
|
||||
self.window.get_widget("details-menu-run").set_sensitive(False)
|
||||
@ -1222,6 +1272,70 @@ class vmmDetails(gobject.GObject):
|
||||
fcdialog.hide()
|
||||
fcdialog.destroy()
|
||||
|
||||
|
||||
# ------------------------------
|
||||
# Serial Console pieces
|
||||
# ------------------------------
|
||||
|
||||
def control_serial_tab(self, src, name, ttypath):
|
||||
if src.get_active():
|
||||
self._show_serial_tab(name, ttypath)
|
||||
else:
|
||||
self._close_serial_tab(name)
|
||||
|
||||
def show_serial_rcpopup(self, src, event):
|
||||
if event.button != 3:
|
||||
return
|
||||
|
||||
self.serial_popup.show_all()
|
||||
self.serial_copy.connect("activate", self.serial_copy_text, src)
|
||||
self.serial_paste.connect("activate", self.serial_paste_text, src)
|
||||
self.serial_close.connect("activate", self.serial_close_tab,
|
||||
self.window.get_widget("details-pages").get_current_page())
|
||||
|
||||
if src.get_has_selection():
|
||||
self.serial_copy.set_sensitive(True)
|
||||
else:
|
||||
self.serial_copy.set_sensitive(False)
|
||||
self.serial_popup.popup(None, None, None, 0, event.time)
|
||||
|
||||
def serial_close_tab(self, src, pagenum):
|
||||
tab_idx = (pagenum - PAGE_DYNAMIC_OFFSET)
|
||||
if (tab_idx < 0) or (tab_idx > len(self.dynamic_tabs)-1):
|
||||
return
|
||||
return self._close_serial_tab(self.dynamic_tabs[tab_idx])
|
||||
|
||||
def serial_copy_text(self, src, terminal):
|
||||
terminal.copy_clipboard()
|
||||
|
||||
def serial_paste_text(self, src, terminal):
|
||||
terminal.paste_clipboard()
|
||||
|
||||
def _show_serial_tab(self, name, ttypath):
|
||||
if not self.dynamic_tabs.count(name):
|
||||
child = vmmSerialConsole(self.vm, ttypath)
|
||||
child.terminal.connect("button-press-event",
|
||||
self.show_serial_rcpopup)
|
||||
title = gtk.Label(name)
|
||||
child.show_all()
|
||||
self.window.get_widget("details-pages").append_page(child, title)
|
||||
self.dynamic_tabs.append(name)
|
||||
|
||||
page_idx = self.dynamic_tabs.index(name) + PAGE_DYNAMIC_OFFSET
|
||||
self.window.get_widget("details-pages").set_current_page(page_idx)
|
||||
|
||||
def _close_serial_tab(self, name):
|
||||
if not self.dynamic_tabs.count(name):
|
||||
return
|
||||
|
||||
page_idx = self.dynamic_tabs.index(name) + PAGE_DYNAMIC_OFFSET
|
||||
self.window.get_widget("details-pages").remove_page(page_idx)
|
||||
self.dynamic_tabs.remove(name)
|
||||
|
||||
# -----------------------
|
||||
# Hardware Section Pieces
|
||||
# -----------------------
|
||||
|
||||
def config_vcpus_changed(self, src):
|
||||
self.window.get_widget("config-vcpus-apply").set_sensitive(True)
|
||||
|
||||
|
@ -424,17 +424,38 @@ class vmmDomain(gobject.GObject):
|
||||
def run_status_icon(self):
|
||||
return self.config.get_vm_status_icon(self.status())
|
||||
|
||||
def get_serial_console_tty(self):
|
||||
return util.get_xml_path(self.get_xml(), "/domain/devices/console/@tty")
|
||||
|
||||
def is_serial_console_tty_accessible(self):
|
||||
def _is_serial_console_tty_accessible(self, path):
|
||||
# pty serial scheme doesn't work over remote
|
||||
if self.connection.is_remote():
|
||||
return False
|
||||
tty = self.get_serial_console_tty()
|
||||
if tty == None:
|
||||
|
||||
if path == None:
|
||||
return False
|
||||
return os.access(tty, os.R_OK | os.W_OK)
|
||||
return os.access(path, os.R_OK | os.W_OK)
|
||||
|
||||
def get_serial_devs(self):
|
||||
def _parse_serial_consoles(ctx):
|
||||
# [ Name, device type, source path
|
||||
serial_list = []
|
||||
devs = ctx.xpathEval("/domain/devices/serial")
|
||||
for node in devs:
|
||||
name = "Serial "
|
||||
usable = False
|
||||
dev_type = node.prop("type")
|
||||
source_path = None
|
||||
|
||||
for child in node.children:
|
||||
if child.name == "target":
|
||||
target_port = child.prop("port")
|
||||
if target_port:
|
||||
name += str(target_port)
|
||||
if child.name == "source":
|
||||
source_path = child.prop("path")
|
||||
|
||||
serial_list.append([name, dev_type, source_path])
|
||||
|
||||
return serial_list
|
||||
return self._parse_device_xml(_parse_serial_consoles)
|
||||
|
||||
def get_graphics_console(self):
|
||||
self.xml = None
|
||||
|
@ -36,7 +36,6 @@ from virtManager.asyncjob import vmmAsyncJob
|
||||
from virtManager.create import vmmCreate
|
||||
from virtManager.host import vmmHost
|
||||
from virtManager.error import vmmErrorDialog
|
||||
from virtManager.serialcon import vmmSerialConsole
|
||||
|
||||
class vmmEngine(gobject.GObject):
|
||||
__gsignals__ = {
|
||||
@ -112,10 +111,6 @@ class vmmEngine(gobject.GObject):
|
||||
if self.connections[hvuri]["windowDetails"].has_key(vmuuid):
|
||||
self.connections[hvuri]["windowDetails"][vmuuid].close()
|
||||
del self.connections[hvuri]["windowDetails"][vmuuid]
|
||||
if self.connections[hvuri]["windowSerialConsole"].has_key(vmuuid):
|
||||
self.connections[hvuri]["windowSerialConsole"][vmuuid].close()
|
||||
del self.connections[hvuri]["windowSerialConsole"][vmuuid]
|
||||
|
||||
|
||||
def _do_connection_changed(self, connection):
|
||||
if connection.get_state() == connection.STATE_ACTIVE or \
|
||||
@ -126,9 +121,6 @@ class vmmEngine(gobject.GObject):
|
||||
for vmuuid in self.connections[hvuri]["windowDetails"].keys():
|
||||
self.connections[hvuri]["windowDetails"][vmuuid].close()
|
||||
del self.connections[hvuri]["windowDetails"][vmuuid]
|
||||
for vmuuid in self.connections[hvuri]["windowSerialConsole"].keys():
|
||||
self.connections[hvuri]["windowSerialConsole"][vmuuid].close()
|
||||
del self.connections[hvuri]["windowSerialConsole"][vmuuid]
|
||||
if self.connections[hvuri]["windowHost"] is not None:
|
||||
self.connections[hvuri]["windowHost"].close()
|
||||
self.connections[hvuri]["windowHost"] = None
|
||||
@ -192,14 +184,10 @@ class vmmEngine(gobject.GObject):
|
||||
self.show_help(index)
|
||||
def _do_show_console(self, src, uri, uuid):
|
||||
self.show_console(uri, uuid)
|
||||
def _do_show_terminal(self, src, uri, uuid):
|
||||
self.show_serial_console(uri, uuid)
|
||||
def _do_show_manager(self, src):
|
||||
self.show_manager()
|
||||
def _do_refresh_console(self, src, uri, uuid):
|
||||
self.refresh_console(uri, uuid)
|
||||
def _do_refresh_terminal(self, src, uri, uuid):
|
||||
self.refresh_serial_console(uri, uuid)
|
||||
def _do_save_domain(self, src, uri, uuid):
|
||||
self.save_domain(src, uri, uuid)
|
||||
def _do_destroy_domain(self, src, uri, uuid):
|
||||
@ -255,16 +243,6 @@ class vmmEngine(gobject.GObject):
|
||||
win = self.show_details(uri, uuid)
|
||||
win.activate_console_page()
|
||||
|
||||
def show_serial_console(self, uri, uuid):
|
||||
con = self.get_connection(uri)
|
||||
|
||||
if not(self.connections[uri]["windowSerialConsole"].has_key(uuid)):
|
||||
console = vmmSerialConsole(self.get_config(),
|
||||
con.get_vm(uuid))
|
||||
self.connections[uri]["windowSerialConsole"][uuid] = console
|
||||
self.connections[uri]["windowSerialConsole"][uuid].show()
|
||||
|
||||
|
||||
def refresh_console(self, uri, uuid):
|
||||
con = self.get_connection(uri)
|
||||
|
||||
@ -277,18 +255,6 @@ class vmmEngine(gobject.GObject):
|
||||
|
||||
console.show()
|
||||
|
||||
def refresh_serial_console(self, uri, uuid):
|
||||
con = self.get_connection(uri)
|
||||
|
||||
if not(self.connections[uri]["windowSerialConsole"].has_key(uuid)):
|
||||
return
|
||||
|
||||
console = self.connections[uri]["windowSerialConsole"][uuid]
|
||||
if not(console.is_visible()):
|
||||
return
|
||||
|
||||
console.show()
|
||||
|
||||
def show_details_performance(self, uri, uuid):
|
||||
win = self.show_details(uri, uuid)
|
||||
win.activate_performance_page()
|
||||
@ -313,7 +279,6 @@ class vmmEngine(gobject.GObject):
|
||||
details.connect("action-reboot-domain", self._do_reboot_domain)
|
||||
details.connect("action-exit-app", self._do_exit_app)
|
||||
details.connect("action-view-manager", self._do_show_manager)
|
||||
details.connect("action-show-terminal", self._do_show_terminal)
|
||||
|
||||
except Exception, e:
|
||||
self.err.show_err(_("Error bringing up domain details: %s") % str(e),
|
||||
@ -341,7 +306,6 @@ class vmmEngine(gobject.GObject):
|
||||
self.windowManager.connect("action-show-connect", self._do_show_connect)
|
||||
self.windowManager.connect("action-connect", self._do_connect)
|
||||
self.windowManager.connect("action-refresh-console", self._do_refresh_console)
|
||||
self.windowManager.connect("action-refresh-terminal", self._do_refresh_terminal)
|
||||
self.windowManager.connect("action-exit-app", self._do_exit_app)
|
||||
return self.windowManager
|
||||
|
||||
@ -379,7 +343,6 @@ class vmmEngine(gobject.GObject):
|
||||
if self.connections[uri]["windowCreate"] == None:
|
||||
create = vmmCreate(self.get_config(), con)
|
||||
create.connect("action-show-console", self._do_show_console)
|
||||
create.connect("action-show-terminal", self._do_show_terminal)
|
||||
create.connect("action-show-help", self._do_show_help)
|
||||
self.connections[uri]["windowCreate"] = create
|
||||
self.connections[uri]["windowCreate"].show()
|
||||
@ -392,7 +355,6 @@ class vmmEngine(gobject.GObject):
|
||||
"windowCreate": None,
|
||||
"windowDetails": {},
|
||||
"windowConsole": {},
|
||||
"windowSerialConsole": {},
|
||||
}
|
||||
self.connections[uri]["connection"].connect("vm-removed", self._do_vm_removed)
|
||||
self.connections[uri]["connection"].connect("state-changed", self._do_connection_changed)
|
||||
|
@ -434,7 +434,6 @@ class vmmManager(gobject.GObject):
|
||||
self.emit("action-show-terminal", uri, vmuuid)
|
||||
else:
|
||||
self.emit("action-refresh-console", uri, vmuuid)
|
||||
self.emit("action-refresh-terminal", uri, vmuuid)
|
||||
|
||||
def _append_vm(self, model, vm, conn):
|
||||
logging.debug("About to append vm: %s" % vm.get_name())
|
||||
|
@ -25,16 +25,16 @@ import gobject
|
||||
import termios
|
||||
import tty
|
||||
import pty
|
||||
import fcntl
|
||||
|
||||
class vmmSerialConsole:
|
||||
def __init__(self, config, vm):
|
||||
import libvirt
|
||||
|
||||
class vmmSerialConsole(gtk.HBox):
|
||||
def __init__(self, vm, ttypath):
|
||||
gtk.HBox.__init__(self)
|
||||
|
||||
self.vm = vm
|
||||
self.config = config
|
||||
|
||||
self.window = gtk.Window()
|
||||
self.window.hide()
|
||||
self.window.set_title(vm.get_name() + " " + _("serial console"))
|
||||
self.ttypath = ttypath
|
||||
|
||||
self.terminal = vte.Terminal()
|
||||
self.terminal.set_cursor_blinks(True)
|
||||
@ -53,42 +53,38 @@ class vmmSerialConsole:
|
||||
scrollbar = gtk.VScrollbar()
|
||||
scrollbar.set_adjustment(self.terminal.get_adjustment())
|
||||
|
||||
box = gtk.HBox()
|
||||
box.pack_start(self.terminal)
|
||||
box.pack_start(scrollbar)
|
||||
|
||||
self.window.add(box)
|
||||
self.pack_start(self.terminal)
|
||||
self.pack_start(scrollbar, expand=False, fill=False)
|
||||
|
||||
self.ptyio = None
|
||||
self.ptysrc = None
|
||||
self.ptytermios = None
|
||||
|
||||
self.window.connect("delete-event", self.close)
|
||||
self.connect("realize", self.handle_realize)
|
||||
self.connect("unrealize", self.handle_unrealize)
|
||||
self.vm.connect("status-changed", self.vm_status_changed)
|
||||
|
||||
|
||||
def show(self):
|
||||
def handle_realize(self, ignore=None):
|
||||
self.opentty()
|
||||
self.window.show_all()
|
||||
self.window.present()
|
||||
|
||||
def close(self, src=None, ignore=None):
|
||||
def handle_unrealize(self, src=None, ignore=None):
|
||||
self.closetty()
|
||||
self.window.hide()
|
||||
return True
|
||||
|
||||
def is_visible(self):
|
||||
if self.window.flags() & gtk.VISIBLE:
|
||||
return 1
|
||||
return 0
|
||||
def vm_status_changed(self, src, status):
|
||||
if status in [ libvirt.VIR_DOMAIN_RUNNING ]:
|
||||
self.opentty()
|
||||
else:
|
||||
self.closetty()
|
||||
|
||||
def opentty(self):
|
||||
if self.ptyio != None:
|
||||
self.closetty()
|
||||
ipty = self.vm.get_serial_console_tty()
|
||||
ipty = self.ttypath
|
||||
|
||||
if ipty == None:
|
||||
return
|
||||
self.ptyio = pty.slave_open(ipty)
|
||||
fcntl.fcntl(self.ptyio, fcntl.F_SETFL, os.O_NONBLOCK)
|
||||
self.ptysrc = gobject.io_add_watch(self.ptyio, gobject.IO_IN | gobject.IO_ERR | gobject.IO_HUP, self.display_data)
|
||||
|
||||
# Save term settings & set to raw mode
|
||||
|
@ -210,11 +210,11 @@
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="details-menu-view-serial">
|
||||
<widget class="GtkMenuItem" id="details-menu-view-serial-list">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">Serial Console...</property>
|
||||
<property name="label" translatable="yes">Serial Consoles</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="on_details_menu_view_serial_activate" last_modification_time="Thu, 24 Jul 2008 19:14:47 GMT"/>
|
||||
<signal name="activate" handler="on_details_menu_view_serial_list_activate" last_modification_time="Mon, 29 Sep 2008 16:57:27 GMT"/>
|
||||
</widget>
|
||||
</child>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user