virt-manager/virtManager/details/serialcon.py

385 lines
12 KiB
Python
Raw Normal View History

# Copyright (C) 2006, 2013 Red Hat, Inc.
# Copyright (C) 2006 Daniel P. Berrange <berrange@redhat.com>
#
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
# pylint: disable=wrong-import-order,ungrouped-imports
2016-04-18 23:42:12 +03:00
import gi
Convert to use GTK3 and GObject Introspection bindings Switch over to use GObject introspection bindings for all python modules related to GObject/GTK3/etc. It is not possible to mix and match old pyggtk/pygobject manual bindings with new introspection based bindings so it must be all changed in one go. Imports like import gtk Change to from gi.repository import Gtk The vmmGObject class is changed to always inherit from GObject.GObject There is no compelling reason to avoid a GObject dep for the virt-manager TUI & it horribly messed up the code. Signal declarations are changed from vmmChooseCD.signal_new(vmmChooseCD, "cdrom-chosen", [object, str]) To __gsignals__ = { "cdrom-chosen": (GObject.SignalFlags.RUN_FIRST, None, [object, str]) } which is required by new GObject bindings Most of the rest of the change is simply dealing with renamed constants / classes. Alot of legacy compat code was removed - ie helpers which check to see if certain GTK2 methods are available are no longer required since we're mandating GTK3 only. The event loop is replaced with LibvirtGLib's event loop. Still todo - Rip out all DBus stuff & make vmmEngine class inherit GtkApplication which provides unique support & DBus method handling - Switch to use LibvirtGConfig & LibvirtGObject for libvirt interaction - Possibly switch to Python 3 too ? - Figure out why GNOME keyring is missing Introspection support My suggestion is that the standalone GIT repo for virt-install only live on as a support branch for legacy platforms. A stable-0.9 branch of virt-manager can be kept for legacy PyGtk2 based virt-manager releases. The virt-manager master branch should exclusively use GObject inspection and ideally Python3 and contain both the virt-manager and virt-install codebases in one since they are intimately related to each other & using separate GIT repos has needlessly complicated life for everyone. crobinso: Some locking fixes Misc cleanups and dropping now-useless code Fix dbus usage Fix graph cell renderer regression Fix a couple tooltip issues
2012-05-14 17:24:56 +04:00
from gi.repository import Gdk
from gi.repository import Gtk
2016-04-18 23:42:12 +03:00
from virtinst import log
2016-04-18 23:42:12 +03:00
# We can use either 2.91 or 2.90. This is just to silence runtime warnings
try:
gi.require_version("Vte", "2.91")
log.debug("Using VTE API 2.91")
except ValueError: # pragma: no cover
2016-04-18 23:42:12 +03:00
gi.require_version("Vte", "2.90")
log.debug("Using VTE API 2.90")
Convert to use GTK3 and GObject Introspection bindings Switch over to use GObject introspection bindings for all python modules related to GObject/GTK3/etc. It is not possible to mix and match old pyggtk/pygobject manual bindings with new introspection based bindings so it must be all changed in one go. Imports like import gtk Change to from gi.repository import Gtk The vmmGObject class is changed to always inherit from GObject.GObject There is no compelling reason to avoid a GObject dep for the virt-manager TUI & it horribly messed up the code. Signal declarations are changed from vmmChooseCD.signal_new(vmmChooseCD, "cdrom-chosen", [object, str]) To __gsignals__ = { "cdrom-chosen": (GObject.SignalFlags.RUN_FIRST, None, [object, str]) } which is required by new GObject bindings Most of the rest of the change is simply dealing with renamed constants / classes. Alot of legacy compat code was removed - ie helpers which check to see if certain GTK2 methods are available are no longer required since we're mandating GTK3 only. The event loop is replaced with LibvirtGLib's event loop. Still todo - Rip out all DBus stuff & make vmmEngine class inherit GtkApplication which provides unique support & DBus method handling - Switch to use LibvirtGConfig & LibvirtGObject for libvirt interaction - Possibly switch to Python 3 too ? - Figure out why GNOME keyring is missing Introspection support My suggestion is that the standalone GIT repo for virt-install only live on as a support branch for legacy platforms. A stable-0.9 branch of virt-manager can be kept for legacy PyGtk2 based virt-manager releases. The virt-manager master branch should exclusively use GObject inspection and ideally Python3 and contain both the virt-manager and virt-install codebases in one since they are intimately related to each other & using separate GIT repos has needlessly complicated life for everyone. crobinso: Some locking fixes Misc cleanups and dropping now-useless code Fix dbus usage Fix graph cell renderer regression Fix a couple tooltip issues
2012-05-14 17:24:56 +04:00
from gi.repository import Vte
2011-04-12 01:48:21 +04:00
import libvirt
from ..baseclass import vmmGObject
2011-04-12 01:48:21 +04:00
class _DataStream(vmmGObject):
"""
Wrapper class for interacting with libvirt console stream
"""
def __init__(self, vm):
vmmGObject.__init__(self)
self.vm = vm
self.conn = vm.conn
self._stream = None
self._streamToTerminal = b""
self._terminalToStream = ""
def _cleanup(self):
self.close()
self.vm = None
self.conn = None
#################
# Internal APIs #
#################
def _display_data(self, terminal):
if not self._streamToTerminal:
return # pragma: no cover
terminal.feed(self._streamToTerminal)
self._streamToTerminal = b""
def _event_on_stream(self, stream, events, opaque):
ignore = stream
terminal = opaque
if (events & libvirt.VIR_EVENT_HANDLE_ERROR or
events & libvirt.VIR_EVENT_HANDLE_HANGUP): # pragma: no cover
log.debug("Received stream ERROR/HANGUP, closing console")
self.close()
return
if events & libvirt.VIR_EVENT_HANDLE_READABLE:
try:
got = self._stream.recv(1024 * 100)
except Exception: # pragma: no cover
log.exception("Error receiving stream data")
self.close()
return
if got == -2: # pragma: no cover
# This is basically EAGAIN
return
if len(got) == 0:
log.debug("Received EOF from stream, closing")
self.close()
return
queued_text = bool(self._streamToTerminal)
self._streamToTerminal += got
if not queued_text:
self.idle_add(self._display_data, terminal)
if (events & libvirt.VIR_EVENT_HANDLE_WRITABLE and
self._terminalToStream):
try:
done = self._stream.send(self._terminalToStream.encode())
except Exception: # pragma: no cover
log.exception("Error sending stream data")
self.close()
return
if done == -2: # pragma: no cover
# This is basically EAGAIN
return
self._terminalToStream = self._terminalToStream[done:]
if not self._terminalToStream:
self._stream.eventUpdateCallback(libvirt.VIR_STREAM_EVENT_READABLE |
libvirt.VIR_STREAM_EVENT_ERROR |
libvirt.VIR_STREAM_EVENT_HANGUP)
##############
# Public API #
##############
def open(self, dev, terminal):
if self._stream:
return
name = dev and dev.alias.name or None
log.debug("Opening console stream for dev=%s alias=%s",
dev, name)
# libxl doesn't set aliases, their open_console just defaults to
# opening the first console device, so don't force prescence of
# an alias
stream = self.conn.get_backend().newStream(libvirt.VIR_STREAM_NONBLOCK)
self.vm.open_console(name, stream)
self._stream = stream
self._stream.eventAddCallback((libvirt.VIR_STREAM_EVENT_READABLE |
libvirt.VIR_STREAM_EVENT_ERROR |
libvirt.VIR_STREAM_EVENT_HANGUP),
self._event_on_stream,
terminal)
def close(self):
if self._stream:
try:
self._stream.eventRemoveCallback()
except Exception: # pragma: no cover
log.exception("Error removing stream callback")
try:
self._stream.finish()
except Exception: # pragma: no cover
log.exception("Error finishing stream")
self._stream = None
def send_data(self, src, text, length, terminal):
"""
Callback when data has been entered into VTE terminal
"""
ignore = src
ignore = length
ignore = terminal
if self._stream is None:
return # pragma: no cover
self._terminalToStream += text
if self._terminalToStream:
self._stream.eventUpdateCallback(libvirt.VIR_STREAM_EVENT_READABLE |
libvirt.VIR_STREAM_EVENT_WRITABLE |
libvirt.VIR_STREAM_EVENT_ERROR |
libvirt.VIR_STREAM_EVENT_HANGUP)
class vmmSerialConsole(vmmGObject):
@staticmethod
def can_connect(_vm, dev):
"""
Check if we think we can actually open passed console/serial dev
"""
usable_types = ["pty"]
ctype = dev.type
err = ""
if ctype not in usable_types:
err = (_("Console for device type '%s' is not supported") % ctype)
return err
@staticmethod
def get_serialcon_devices(vm):
serials = vm.xmlobj.devices.serial
consoles = vm.xmlobj.devices.console
if serials and vm.serial_is_console_dup(serials[0]):
consoles.pop(0)
return serials + consoles
def __init__(self, vm, target_port, name):
2011-04-12 01:48:21 +04:00
vmmGObject.__init__(self)
self.vm = vm
self.target_port = target_port
self.name = name
self.lastpath = None
self._datastream = _DataStream(self.vm)
self._serial_popup = None
self._serial_copy = None
self._serial_paste = None
self._init_popup()
self._vteterminal = None
self._init_terminal()
2011-04-12 01:48:21 +04:00
self._box = None
self._error_label = None
self._init_ui()
self.vm.connect("state-changed", self._vm_status_changed)
def _cleanup(self):
self._datastream.cleanup()
self._datastream = None
self.vm = None
self._vteterminal = None
self._box = None
2011-04-12 01:48:21 +04:00
###########
# UI init #
###########
2011-04-12 01:48:21 +04:00
def _init_terminal(self):
self._vteterminal = Vte.Terminal()
self._vteterminal.set_scrollback_lines(1000)
self._vteterminal.set_audible_bell(False)
self._vteterminal.get_accessible().set_name("Serial Terminal")
self._vteterminal.connect("button-press-event",
self._show_serial_rcpopup)
self._vteterminal.connect("commit",
self._datastream.send_data, self._vteterminal)
self._vteterminal.show()
def _init_popup(self):
self._serial_popup = Gtk.Menu()
self._serial_popup.get_accessible().set_name("serial-popup-menu")
self._serial_copy = Gtk.MenuItem.new_with_mnemonic(_("_Copy"))
self._serial_copy.connect("activate", self._serial_copy_text)
self._serial_popup.add(self._serial_copy)
self._serial_paste = Gtk.MenuItem.new_with_mnemonic(_("_Paste"))
self._serial_paste.connect("activate", self._serial_paste_text)
self._serial_popup.add(self._serial_paste)
def _init_ui(self):
self._box = Gtk.Notebook()
self._box.set_show_tabs(False)
self._box.set_show_border(False)
align = Gtk.Box()
align.set_border_width(2)
Convert to use GTK3 and GObject Introspection bindings Switch over to use GObject introspection bindings for all python modules related to GObject/GTK3/etc. It is not possible to mix and match old pyggtk/pygobject manual bindings with new introspection based bindings so it must be all changed in one go. Imports like import gtk Change to from gi.repository import Gtk The vmmGObject class is changed to always inherit from GObject.GObject There is no compelling reason to avoid a GObject dep for the virt-manager TUI & it horribly messed up the code. Signal declarations are changed from vmmChooseCD.signal_new(vmmChooseCD, "cdrom-chosen", [object, str]) To __gsignals__ = { "cdrom-chosen": (GObject.SignalFlags.RUN_FIRST, None, [object, str]) } which is required by new GObject bindings Most of the rest of the change is simply dealing with renamed constants / classes. Alot of legacy compat code was removed - ie helpers which check to see if certain GTK2 methods are available are no longer required since we're mandating GTK3 only. The event loop is replaced with LibvirtGLib's event loop. Still todo - Rip out all DBus stuff & make vmmEngine class inherit GtkApplication which provides unique support & DBus method handling - Switch to use LibvirtGConfig & LibvirtGObject for libvirt interaction - Possibly switch to Python 3 too ? - Figure out why GNOME keyring is missing Introspection support My suggestion is that the standalone GIT repo for virt-install only live on as a support branch for legacy platforms. A stable-0.9 branch of virt-manager can be kept for legacy PyGtk2 based virt-manager releases. The virt-manager master branch should exclusively use GObject inspection and ideally Python3 and contain both the virt-manager and virt-install codebases in one since they are intimately related to each other & using separate GIT repos has needlessly complicated life for everyone. crobinso: Some locking fixes Misc cleanups and dropping now-useless code Fix dbus usage Fix graph cell renderer regression Fix a couple tooltip issues
2012-05-14 17:24:56 +04:00
evbox = Gtk.EventBox()
evbox.modify_bg(Gtk.StateType.NORMAL, Gdk.Color(0, 0, 0))
terminalbox = Gtk.HBox()
scrollbar = Gtk.VScrollbar()
self._error_label = Gtk.Label()
self._error_label.set_width_chars(40)
self._error_label.set_line_wrap(True)
if self._vteterminal:
scrollbar.set_adjustment(self._vteterminal.get_vadjustment())
align.pack_start(self._vteterminal, True, True, 0)
evbox.add(align)
Convert to use GTK3 and GObject Introspection bindings Switch over to use GObject introspection bindings for all python modules related to GObject/GTK3/etc. It is not possible to mix and match old pyggtk/pygobject manual bindings with new introspection based bindings so it must be all changed in one go. Imports like import gtk Change to from gi.repository import Gtk The vmmGObject class is changed to always inherit from GObject.GObject There is no compelling reason to avoid a GObject dep for the virt-manager TUI & it horribly messed up the code. Signal declarations are changed from vmmChooseCD.signal_new(vmmChooseCD, "cdrom-chosen", [object, str]) To __gsignals__ = { "cdrom-chosen": (GObject.SignalFlags.RUN_FIRST, None, [object, str]) } which is required by new GObject bindings Most of the rest of the change is simply dealing with renamed constants / classes. Alot of legacy compat code was removed - ie helpers which check to see if certain GTK2 methods are available are no longer required since we're mandating GTK3 only. The event loop is replaced with LibvirtGLib's event loop. Still todo - Rip out all DBus stuff & make vmmEngine class inherit GtkApplication which provides unique support & DBus method handling - Switch to use LibvirtGConfig & LibvirtGObject for libvirt interaction - Possibly switch to Python 3 too ? - Figure out why GNOME keyring is missing Introspection support My suggestion is that the standalone GIT repo for virt-install only live on as a support branch for legacy platforms. A stable-0.9 branch of virt-manager can be kept for legacy PyGtk2 based virt-manager releases. The virt-manager master branch should exclusively use GObject inspection and ideally Python3 and contain both the virt-manager and virt-install codebases in one since they are intimately related to each other & using separate GIT repos has needlessly complicated life for everyone. crobinso: Some locking fixes Misc cleanups and dropping now-useless code Fix dbus usage Fix graph cell renderer regression Fix a couple tooltip issues
2012-05-14 17:24:56 +04:00
terminalbox.pack_start(evbox, True, True, 0)
terminalbox.pack_start(scrollbar, False, False, 0)
self._box.append_page(terminalbox, Gtk.Label(""))
self._box.append_page(self._error_label, Gtk.Label(""))
self._box.show_all()
scrollbar.hide()
scrollbar.get_adjustment().connect(
"changed", self._scrollbar_adjustment_changed, scrollbar)
###################
# Private methods #
###################
def _show_error(self, msg):
self._error_label.set_markup("<b>%s</b>" % msg)
self._box.set_current_page(1)
def _lookup_dev(self):
devs = vmmSerialConsole.get_serialcon_devices(self.vm)
found = None
for dev in devs:
port = dev.get_xml_idx()
path = dev.source.path
if port == self.target_port:
if path != self.lastpath:
log.debug("Serial console '%s' path changed to %s",
self.target_port, path)
self.lastpath = path
found = dev
break
if not found: # pragma: no cover
log.debug("No devices found for serial target port '%s'",
self.target_port)
self.lastpath = None
return found
##############
# Public API #
##############
def close(self):
if self._datastream:
self._datastream.close()
def get_box(self):
return self._box
def has_focus(self):
return bool(self._vteterminal and
self._vteterminal.get_property("has-focus"))
def set_focus_callbacks(self, in_cb, out_cb):
self._vteterminal.connect("focus-in-event", in_cb)
self._vteterminal.connect("focus-out-event", out_cb)
def open_console(self):
try:
dev = self._lookup_dev()
self._datastream.open(dev, self._vteterminal)
self._box.set_current_page(0)
return True
except Exception as e:
log.exception("Error opening serial console")
self._show_error(_("Error connecting to text console: %s") % e)
try:
self._datastream.close()
except Exception: # pragma: no cover
pass
return False
################
# UI listeners #
################
def _vm_status_changed(self, vm):
if vm.status() in [libvirt.VIR_DOMAIN_RUNNING]:
self.open_console()
else:
self._datastream.close()
def _scrollbar_adjustment_changed(self, adjustment, scrollbar):
scrollbar.set_visible(
adjustment.get_upper() > adjustment.get_page_size())
def _show_serial_rcpopup(self, src, event):
if event.button != 3:
return
self._serial_popup.show_all()
if src.get_has_selection():
self._serial_copy.set_sensitive(True)
else:
self._serial_copy.set_sensitive(False)
self._serial_popup.popup_at_pointer(event)
def _serial_copy_text(self, src_ignore):
self._vteterminal.copy_clipboard()
def _serial_paste_text(self, src_ignore):
self._vteterminal.paste_clipboard()