The attached patch combines the serial console window with the VM details window. Opening the serial console now appends a tab to the details view. In addition, multiple serial consoles are now supported, not just the primary/first defined console, though this still only works for 'pty' devices. The patch does three things: 1) Remove all the previous plumbing needed to keep track of the separate console window 2) Fix the vmmSerialConsole class to extend gtk.HBox, so we can reuse most of the code in a notebook tab. 3) Add all the plumbing in details.py to deal with adding and removing tabs on the fly. Screenshot of a couple tab examples: http://fedorapeople.org/~crobinso/virt-manager/vmm-serial-tab1.png Screenshot of selecting which serial console to open (unsupported console types will have entries, they will just be disabled as shown): http://fedorapeople.org/~crobinso/virt-manager/vmm-serial-tab2.png Screenshot of terminal right click menu. Tabs can be closed via this menu, or the 'View' menu shown in the second screenshot. http://fedorapeople.org/~crobinso/virt-manager/vmm-serial-tab3.png Any comments appreciated. Thanks, Cole
# HG changeset patch # User "Cole Robinson <crobinso@xxxxxxxxxx>" # Date 1222789526 14400 # Node ID 597b7d1430152e10b465775c46e86116bf1ef0ab # Parent c94fe88e5f64d9273a789b0fa4a4c278ff289777 View serial consoles as tabs in the details window. Allow viewing not just the primary console. diff -r c94fe88e5f64 -r 597b7d143015 src/virtManager/details.py --- a/src/virtManager/details.py Tue Sep 30 11:37:52 2008 -0400 +++ b/src/virtManager/details.py Tue Sep 30 11:45:26 2008 -0400 @@ -36,6 +36,7 @@ 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_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 @@ 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 @@ 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 @@ "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,27 @@ 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 consoles available")) + item.set_sensitive(False) + src.add(item) + + for dev in devs: + item = gtk.CheckMenuItem(dev[0]) + item.set_sensitive(dev[1]) + if dev[1] 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 +624,6 @@ 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 +644,6 @@ 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 +656,6 @@ 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) @@ -1221,6 +1251,70 @@ else: 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) diff -r c94fe88e5f64 -r 597b7d143015 src/virtManager/domain.py --- a/src/virtManager/domain.py Tue Sep 30 11:37:52 2008 -0400 +++ b/src/virtManager/domain.py Tue Sep 30 11:45:26 2008 -0400 @@ -424,17 +424,42 @@ 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, Can we connect?, tty ] + serial_list = [] + usable_types = ["pty"] + 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") + + if dev_type in usable_types: + usable = self._is_serial_console_tty_accessible(source_path) + + serial_list.append([name, usable, source_path]) + + return serial_list + return self._parse_device_xml(_parse_serial_consoles) def get_graphics_console(self): self.xml = None diff -r c94fe88e5f64 -r 597b7d143015 src/virtManager/engine.py --- a/src/virtManager/engine.py Tue Sep 30 11:37:52 2008 -0400 +++ b/src/virtManager/engine.py Tue Sep 30 11:45:26 2008 -0400 @@ -36,7 +36,6 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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) @@ -272,18 +250,6 @@ return console = self.connections[uri]["windowConsole"][uuid] - if not(console.is_visible()): - return - - 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 @@ -313,7 +279,6 @@ 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 @@ 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 @@ 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 @@ "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) diff -r c94fe88e5f64 -r 597b7d143015 src/virtManager/manager.py --- a/src/virtManager/manager.py Tue Sep 30 11:37:52 2008 -0400 +++ b/src/virtManager/manager.py Tue Sep 30 11:45:26 2008 -0400 @@ -434,7 +434,6 @@ 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()) diff -r c94fe88e5f64 -r 597b7d143015 src/virtManager/serialcon.py --- a/src/virtManager/serialcon.py Tue Sep 30 11:37:52 2008 -0400 +++ b/src/virtManager/serialcon.py Tue Sep 30 11:45:26 2008 -0400 @@ -25,16 +25,16 @@ 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 @@ 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 handle_realize(self, ignore=None): + self.opentty() - def show(self): - self.opentty() - self.window.show_all() - self.window.present() + def handle_unrealize(self, src=None, ignore=None): + self.closetty() - def close(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 diff -r c94fe88e5f64 -r 597b7d143015 src/vmm-details.glade --- a/src/vmm-details.glade Tue Sep 30 11:37:52 2008 -0400 +++ b/src/vmm-details.glade Tue Sep 30 11:45:26 2008 -0400 @@ -210,11 +210,11 @@ </child> <child> - <widget class="GtkMenuItem" id="details-menu-view-serial"> - <property name="visible">True</property> - <property name="label" translatable="yes">Serial Console...</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"/> + <widget class="GtkMenuItem" id="details-menu-view-serial-list"> + <property name="visible">True</property> + <property name="label" translatable="yes">Serial Consoles</property> + <property name="use_underline">True</property> + <signal name="activate" handler="on_details_menu_view_serial_list_activate" last_modification_time="Mon, 29 Sep 2008 16:57:27 GMT"/> </widget> </child>
_______________________________________________ et-mgmt-tools mailing list et-mgmt-tools@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/et-mgmt-tools