2013-06-11 11:32:13 +04:00
# Copyright (C) 2006, 2013 Red Hat, Inc.
2006-08-11 00:47:14 +04:00
# Copyright (C) 2006 Daniel P. Berrange <berrange@redhat.com>
#
2018-04-04 16:35:41 +03:00
# This work is licensed under the GNU GPLv2 or later.
2018-03-20 22:00:02 +03:00
# See the COPYING file in the top-level directory.
2006-08-11 00:47:14 +04:00
2019-06-17 04:12:39 +03:00
# pylint: disable=wrong-import-order,ungrouped-imports
2016-04-18 23:42:12 +03:00
import gi
2012-05-14 17:24:56 +04:00
from gi . repository import Gdk
2013-02-16 23:03:30 +04:00
from gi . repository import Gtk
2016-04-18 23:42:12 +03:00
2019-06-17 04:12:39 +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 " )
2019-06-17 04:12:39 +03:00
log . debug ( " Using VTE API 2.91 " )
2020-08-28 17:25:38 +03:00
except ValueError : # pragma: no cover
2016-04-18 23:42:12 +03:00
gi . require_version ( " Vte " , " 2.90 " )
2019-06-17 04:12:39 +03:00
log . debug ( " Using VTE API 2.90 " )
2012-05-14 17:24:56 +04:00
from gi . repository import Vte
2011-04-12 01:48:21 +04:00
2008-10-03 00:17:25 +04:00
import libvirt
2006-08-11 00:47:14 +04:00
2019-06-17 04:56:34 +03:00
from . . baseclass import vmmGObject
2011-04-12 01:48:21 +04:00
2013-02-07 00:32:30 +04:00
2020-08-28 18:14:10 +03:00
class _DataStream ( vmmGObject ) :
"""
Wrapper class for interacting with libvirt console stream
"""
2011-06-20 02:53:15 +04:00
def __init__ ( self , vm ) :
vmmGObject . __init__ ( self )
self . vm = vm
2011-07-23 00:43:26 +04:00
self . conn = vm . conn
2011-06-20 02:53:15 +04:00
2020-08-28 18:14:10 +03:00
self . _stream = None
2018-06-12 21:23:39 +03:00
2020-08-28 18:14:10 +03:00
self . _streamToTerminal = b " "
self . _terminalToStream = " "
2018-06-12 21:23:39 +03:00
2011-07-24 05:16:54 +04:00
def _cleanup ( self ) :
2011-06-20 02:53:15 +04:00
self . close ( )
self . vm = None
self . conn = None
2020-08-28 18:14:10 +03:00
#################
# Internal APIs #
#################
def _display_data ( self , terminal ) :
if not self . _streamToTerminal :
return # pragma: no cover
terminal . feed ( self . _streamToTerminal )
self . _streamToTerminal = b " "
2011-06-21 19:04:48 +04:00
def _event_on_stream ( self , stream , events , opaque ) :
ignore = stream
terminal = opaque
if ( events & libvirt . VIR_EVENT_HANDLE_ERROR or
2020-08-28 17:25:38 +03:00
events & libvirt . VIR_EVENT_HANDLE_HANGUP ) : # pragma: no cover
2019-06-17 04:12:39 +03:00
log . debug ( " Received stream ERROR/HANGUP, closing console " )
2011-06-21 19:04:48 +04:00
self . close ( )
2013-06-13 05:10:52 +04:00
return
2011-06-20 02:53:15 +04:00
2011-06-21 19:04:48 +04:00
if events & libvirt . VIR_EVENT_HANDLE_READABLE :
try :
2020-08-28 18:14:10 +03:00
got = self . _stream . recv ( 1024 * 100 )
2020-08-28 17:25:38 +03:00
except Exception : # pragma: no cover
2019-06-17 04:12:39 +03:00
log . exception ( " Error receiving stream data " )
2011-06-21 19:04:48 +04:00
self . close ( )
return
2011-06-20 02:53:15 +04:00
2020-08-28 17:25:38 +03:00
if got == - 2 : # pragma: no cover
2013-06-13 05:10:52 +04:00
# This is basically EAGAIN
2011-06-21 19:04:48 +04:00
return
2013-06-11 11:32:13 +04:00
if len ( got ) == 0 :
2019-06-17 04:12:39 +03:00
log . debug ( " Received EOF from stream, closing " )
2013-06-11 11:32:13 +04:00
self . close ( )
2013-06-13 05:10:52 +04:00
return
2011-06-21 19:04:48 +04:00
2020-08-28 18:14:10 +03:00
queued_text = bool ( self . _streamToTerminal )
self . _streamToTerminal + = got
2012-01-30 03:46:53 +04:00
if not queued_text :
2020-08-28 18:14:10 +03:00
self . idle_add ( self . _display_data , terminal )
2011-06-21 19:04:48 +04:00
if ( events & libvirt . VIR_EVENT_HANDLE_WRITABLE and
2020-08-28 18:14:10 +03:00
self . _terminalToStream ) :
2011-06-21 19:04:48 +04:00
try :
2020-08-28 18:14:10 +03:00
done = self . _stream . send ( self . _terminalToStream . encode ( ) )
2020-08-28 17:25:38 +03:00
except Exception : # pragma: no cover
2019-06-17 04:12:39 +03:00
log . exception ( " Error sending stream data " )
2011-06-21 19:04:48 +04:00
self . close ( )
return
2020-08-28 17:25:38 +03:00
if done == - 2 : # pragma: no cover
2013-06-13 05:10:52 +04:00
# This is basically EAGAIN
2011-06-21 19:04:48 +04:00
return
2020-08-28 18:14:10 +03:00
self . _terminalToStream = self . _terminalToStream [ done : ]
2011-06-21 19:04:48 +04:00
2020-08-28 18:14:10 +03:00
if not self . _terminalToStream :
self . _stream . eventUpdateCallback ( libvirt . VIR_STREAM_EVENT_READABLE |
2011-06-21 19:04:48 +04:00
libvirt . VIR_STREAM_EVENT_ERROR |
libvirt . VIR_STREAM_EVENT_HANGUP )
2020-08-28 18:14:10 +03:00
##############
# Public API #
##############
2011-06-21 19:04:48 +04:00
def open ( self , dev , terminal ) :
2020-08-28 18:14:10 +03:00
if self . _stream :
2020-08-28 17:25:38 +03:00
return
2011-06-21 19:04:48 +04:00
name = dev and dev . alias . name or None
2019-06-17 04:12:39 +03:00
log . debug ( " Opening console stream for dev= %s alias= %s " ,
2012-01-17 07:04:40 +04:00
dev , name )
2015-04-26 18:50:01 +03:00
# libxl doesn't set aliases, their open_console just defaults to
# opening the first console device, so don't force prescence of
# an alias
2012-01-29 22:02:13 +04:00
2013-11-10 02:54:47 +04:00
stream = self . conn . get_backend ( ) . newStream ( libvirt . VIR_STREAM_NONBLOCK )
self . vm . open_console ( name , stream )
2020-08-28 18:14:10 +03:00
self . _stream = stream
2011-06-21 19:04:48 +04:00
2020-08-28 18:14:10 +03:00
self . _stream . eventAddCallback ( ( libvirt . VIR_STREAM_EVENT_READABLE |
2011-06-21 19:04:48 +04:00
libvirt . VIR_STREAM_EVENT_ERROR |
libvirt . VIR_STREAM_EVENT_HANGUP ) ,
self . _event_on_stream ,
terminal )
def close ( self ) :
2020-08-28 18:14:10 +03:00
if self . _stream :
2011-06-21 19:04:48 +04:00
try :
2020-08-28 18:14:10 +03:00
self . _stream . eventRemoveCallback ( )
2020-08-28 17:25:38 +03:00
except Exception : # pragma: no cover
2019-06-17 04:12:39 +03:00
log . exception ( " Error removing stream callback " )
2011-06-21 19:04:48 +04:00
try :
2020-08-28 18:14:10 +03:00
self . _stream . finish ( )
2020-08-28 17:25:38 +03:00
except Exception : # pragma: no cover
2019-06-17 04:12:39 +03:00
log . exception ( " Error finishing stream " )
2011-06-21 19:04:48 +04:00
2020-08-28 18:14:10 +03:00
self . _stream = None
2011-06-21 19:04:48 +04:00
def send_data ( self , src , text , length , terminal ) :
2018-06-12 21:23:39 +03:00
"""
Callback when data has been entered into VTE terminal
"""
2011-06-21 19:04:48 +04:00
ignore = src
ignore = length
ignore = terminal
2020-08-28 18:14:10 +03:00
if self . _stream is None :
2020-08-28 17:25:38 +03:00
return # pragma: no cover
2011-06-21 19:04:48 +04:00
2020-08-28 18:14:10 +03:00
self . _terminalToStream + = text
if self . _terminalToStream :
self . _stream . eventUpdateCallback ( libvirt . VIR_STREAM_EVENT_READABLE |
2011-06-21 19:04:48 +04:00
libvirt . VIR_STREAM_EVENT_WRITABLE |
libvirt . VIR_STREAM_EVENT_ERROR |
libvirt . VIR_STREAM_EVENT_HANGUP )
2011-06-21 00:49:11 +04:00
2013-02-07 00:32:30 +04:00
class vmmSerialConsole ( vmmGObject ) :
2011-06-21 00:49:11 +04:00
@staticmethod
2024-09-07 20:27:05 +03:00
def can_connect ( _vm , dev ) :
2011-06-21 00:49:11 +04:00
"""
Check if we think we can actually open passed console / serial dev
"""
usable_types = [ " pty " ]
2013-07-16 17:14:37 +04:00
ctype = dev . type
2011-06-21 00:49:11 +04:00
err = " "
2024-09-07 20:27:05 +03:00
if ctype not in usable_types :
2018-06-12 21:23:39 +03:00
err = ( _ ( " Console for device type ' %s ' is not supported " ) % ctype )
2011-06-21 00:49:11 +04:00
return err
2020-09-09 14:44:17 +03:00
@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
2011-06-21 02:10:04 +04:00
def __init__ ( self , vm , target_port , name ) :
2011-04-12 01:48:21 +04:00
vmmGObject . __init__ ( self )
2006-08-11 00:47:14 +04:00
2008-10-03 00:17:25 +04:00
self . vm = vm
2009-07-15 03:42:16 +04:00
self . target_port = target_port
2011-06-21 02:10:04 +04:00
self . name = name
2011-06-20 04:49:41 +04:00
self . lastpath = None
2006-08-11 00:47:14 +04:00
2020-08-28 18:14:10 +03:00
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
2020-08-28 18:14:10 +03:00
self . _box = None
self . _error_label = None
self . _init_ui ( )
2011-06-21 02:10:04 +04:00
2020-08-28 18:14:10 +03:00
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
2020-08-28 18:14:10 +03:00
###########
# UI init #
###########
2011-04-12 01:48:21 +04:00
2020-08-28 18:14:10 +03: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 " )
2006-08-11 00:47:14 +04:00
2020-08-28 18:14:10 +03:00
self . _vteterminal . connect ( " button-press-event " ,
self . _show_serial_rcpopup )
self . _vteterminal . connect ( " commit " ,
self . _datastream . send_data , self . _vteterminal )
self . _vteterminal . show ( )
2006-08-11 00:47:14 +04:00
2020-08-28 18:14:10 +03:00
def _init_popup ( self ) :
self . _serial_popup = Gtk . Menu ( )
self . _serial_popup . get_accessible ( ) . set_name ( " serial-popup-menu " )
2011-06-21 02:10:04 +04:00
2022-02-19 20:01:55 +03:00
self . _serial_copy = Gtk . MenuItem . new_with_mnemonic ( _ ( " _Copy " ) )
2020-08-28 18:14:10 +03:00
self . _serial_copy . connect ( " activate " , self . _serial_copy_text )
self . _serial_popup . add ( self . _serial_copy )
2011-06-21 02:10:04 +04:00
2022-02-19 20:01:55 +03:00
self . _serial_paste = Gtk . MenuItem . new_with_mnemonic ( _ ( " _Paste " ) )
2020-08-28 18:14:10 +03:00
self . _serial_paste . connect ( " activate " , self . _serial_paste_text )
self . _serial_popup . add ( self . _serial_paste )
2011-06-21 02:10:04 +04:00
2020-08-28 18:14:10 +03:00
def _init_ui ( self ) :
self . _box = Gtk . Notebook ( )
self . _box . set_show_tabs ( False )
self . _box . set_show_border ( False )
2011-06-21 03:09:31 +04:00
2022-02-19 22:23:32 +03:00
align = Gtk . Box ( )
align . set_border_width ( 2 )
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 ( )
2020-08-28 18:14:10 +03:00
self . _error_label = Gtk . Label ( )
self . _error_label . set_width_chars ( 40 )
self . _error_label . set_line_wrap ( True )
2006-08-11 00:47:14 +04:00
2020-08-28 18:14:10 +03:00
if self . _vteterminal :
scrollbar . set_adjustment ( self . _vteterminal . get_vadjustment ( ) )
2024-03-24 00:17:14 +03:00
align . pack_start ( self . _vteterminal , True , True , 0 )
2012-01-30 04:00:45 +04:00
2011-06-21 21:37:10 +04:00
evbox . add ( align )
2012-05-14 17:24:56 +04:00
terminalbox . pack_start ( evbox , True , True , 0 )
2013-02-07 00:32:30 +04:00
terminalbox . pack_start ( scrollbar , False , False , 0 )
2011-06-21 03:09:31 +04:00
2020-08-28 18:14:10 +03:00
self . _box . append_page ( terminalbox , Gtk . Label ( " " ) )
self . _box . append_page ( self . _error_label , Gtk . Label ( " " ) )
self . _box . show_all ( )
2006-08-11 00:47:14 +04:00
2014-04-26 03:13:44 +04:00
scrollbar . hide ( )
scrollbar . get_adjustment ( ) . connect (
" changed " , self . _scrollbar_adjustment_changed , scrollbar )
2011-06-21 03:09:31 +04:00
2020-08-28 18:14:10 +03:00
###################
# Private methods #
###################
2006-08-11 00:47:14 +04:00
2020-08-28 18:14:10 +03:00
def _show_error ( self , msg ) :
self . _error_label . set_markup ( " <b> %s </b> " % msg )
self . _box . set_current_page ( 1 )
2006-11-03 18:02:02 +03:00
2020-08-28 18:14:10 +03:00
def _lookup_dev ( self ) :
2020-09-09 14:44:17 +03:00
devs = vmmSerialConsole . get_serialcon_devices ( self . vm )
2020-08-28 17:25:38 +03:00
found = None
2011-06-20 04:49:41 +04:00
for dev in devs :
2018-03-21 21:42:50 +03:00
port = dev . get_xml_idx ( )
2019-05-13 21:23:27 +03:00
path = dev . source . path
2011-06-20 04:49:41 +04:00
2009-07-15 03:42:16 +04:00
if port == self . target_port :
2011-06-20 04:49:41 +04:00
if path != self . lastpath :
2019-06-17 04:12:39 +03:00
log . debug ( " Serial console ' %s ' path changed to %s " ,
2012-01-17 07:04:40 +04:00
self . target_port , path )
2011-06-20 04:49:41 +04:00
self . lastpath = path
2020-08-28 17:25:38 +03:00
found = dev
break
2009-07-28 17:32:18 +04:00
2020-08-28 17:25:38 +03:00
if not found : # pragma: no cover
log . debug ( " No devices found for serial target port ' %s ' " ,
2009-07-15 03:42:16 +04:00
self . target_port )
2020-08-28 17:25:38 +03:00
self . lastpath = None
return found
2011-06-21 02:10:04 +04:00
2020-08-28 18:14:10 +03:00
##############
# 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 ( ) )
2011-06-21 02:10:04 +04:00
2020-08-28 18:14:10 +03:00
def _show_serial_rcpopup ( self , src , event ) :
2011-06-21 02:10:04 +04:00
if event . button != 3 :
return
2020-08-28 18:14:10 +03:00
self . _serial_popup . show_all ( )
2011-06-21 02:10:04 +04:00
if src . get_has_selection ( ) :
2020-08-28 18:14:10 +03:00
self . _serial_copy . set_sensitive ( True )
2011-06-21 02:10:04 +04:00
else :
2020-08-28 18:14:10 +03:00
self . _serial_copy . set_sensitive ( False )
self . _serial_popup . popup_at_pointer ( event )
2011-06-21 02:10:04 +04:00
2020-08-28 18:14:10 +03:00
def _serial_copy_text ( self , src_ignore ) :
self . _vteterminal . copy_clipboard ( )
2011-06-21 02:10:04 +04:00
2020-08-28 18:14:10 +03:00
def _serial_paste_text ( self , src_ignore ) :
self . _vteterminal . paste_clipboard ( )