2010-12-22 05:34:36 +08:00
|
|
|
# -*- coding: utf-8 -*-
|
2009-10-31 01:25:27 +08:00
|
|
|
#
|
2013-10-28 04:59:46 +08:00
|
|
|
# Copyright (C) 2006-2008, 2013 Red Hat, Inc.
|
2009-10-31 01:25:27 +08:00
|
|
|
# Copyright (C) 2006 Daniel P. Berrange <berrange@redhat.com>
|
2010-12-22 05:34:36 +08:00
|
|
|
# Copyright (C) 2010 Marc-André Lureau <marcandre.lureau@redhat.com>
|
2009-10-31 01:25:27 +08:00
|
|
|
#
|
|
|
|
# This program is free software; you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with this program; if not, write to the Free Software
|
|
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
|
|
# MA 02110-1301 USA.
|
|
|
|
#
|
|
|
|
|
2013-04-12 05:16:33 +08:00
|
|
|
# pylint: disable=E0611
|
2012-05-14 21:24:56 +08:00
|
|
|
from gi.repository import GObject
|
|
|
|
from gi.repository import Gtk
|
|
|
|
from gi.repository import Gdk
|
|
|
|
from gi.repository import GtkVnc
|
|
|
|
from gi.repository import SpiceClientGtk
|
|
|
|
from gi.repository import SpiceClientGLib
|
2013-04-12 05:16:33 +08:00
|
|
|
# pylint: enable=E0611
|
2010-02-11 22:32:05 +08:00
|
|
|
|
2009-10-31 01:25:27 +08:00
|
|
|
import libvirt
|
2011-01-08 03:59:31 +08:00
|
|
|
|
2013-09-07 07:36:09 +08:00
|
|
|
import logging
|
2009-10-31 01:25:27 +08:00
|
|
|
import os
|
2013-09-07 07:36:09 +08:00
|
|
|
import Queue
|
2010-02-11 22:32:05 +08:00
|
|
|
import signal
|
2009-10-31 01:25:27 +08:00
|
|
|
import socket
|
2013-09-07 07:36:09 +08:00
|
|
|
import threading
|
2009-10-31 01:25:27 +08:00
|
|
|
|
2011-05-19 06:54:27 +08:00
|
|
|
import virtManager.uihelpers as uihelpers
|
2011-05-19 05:22:07 +08:00
|
|
|
from virtManager.autodrawer import AutoDrawer
|
2011-05-19 05:36:08 +08:00
|
|
|
from virtManager.baseclass import vmmGObjectUI, vmmGObject
|
2013-09-02 05:40:38 +08:00
|
|
|
from virtManager.serialcon import vmmSerialConsole
|
|
|
|
from virtManager.details import DETAILS_PAGE_CONSOLE
|
2009-10-31 01:25:27 +08:00
|
|
|
|
|
|
|
# Console pages
|
2013-09-02 05:40:38 +08:00
|
|
|
(CONSOLE_PAGE_UNAVAILABLE,
|
|
|
|
CONSOLE_PAGE_AUTHENTICATE,
|
|
|
|
CONSOLE_PAGE_VIEWER,
|
|
|
|
CONSOLE_PAGE_OFFSET) = range(4)
|
2009-10-31 01:25:27 +08:00
|
|
|
|
2013-01-16 10:22:32 +08:00
|
|
|
|
2009-10-31 02:36:17 +08:00
|
|
|
def has_property(obj, setting):
|
|
|
|
try:
|
|
|
|
obj.get_property(setting)
|
|
|
|
except TypeError:
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
2013-01-16 10:22:32 +08:00
|
|
|
|
2012-03-14 03:27:23 +08:00
|
|
|
class ConnectionInfo(object):
|
|
|
|
"""
|
|
|
|
Holds all the bits needed to make a connection to a graphical console
|
|
|
|
"""
|
|
|
|
def __init__(self, conn, gdev):
|
|
|
|
self.gtype = gdev.type
|
|
|
|
self.gport = gdev.port and str(gdev.port) or None
|
|
|
|
self.gsocket = gdev.socket
|
|
|
|
self.gaddr = gdev.listen or "127.0.0.1"
|
2013-09-02 04:08:14 +08:00
|
|
|
self.gtlsport = gdev.tlsPort or None
|
2012-03-14 03:27:23 +08:00
|
|
|
|
|
|
|
self.transport, self.connuser = conn.get_transport()
|
|
|
|
self._connhost = conn.get_uri_hostname() or "127.0.0.1"
|
|
|
|
|
|
|
|
self._connport = None
|
|
|
|
if self._connhost.count(":"):
|
|
|
|
self._connhost, self._connport = self._connhost.split(":", 1)
|
|
|
|
|
|
|
|
def need_tunnel(self):
|
2012-04-05 05:55:09 +08:00
|
|
|
if self.gaddr != "127.0.0.1":
|
2012-03-14 03:27:23 +08:00
|
|
|
return False
|
|
|
|
|
|
|
|
return self.transport in ["ssh", "ext"]
|
|
|
|
|
2013-06-30 23:44:58 +08:00
|
|
|
def is_bad_localhost(self):
|
2013-09-02 04:08:14 +08:00
|
|
|
host = self.get_conn_host()[0]
|
2013-06-30 23:44:58 +08:00
|
|
|
if self.need_tunnel():
|
|
|
|
return False
|
|
|
|
return self.transport and host == "127.0.0.1"
|
|
|
|
|
2012-03-14 03:27:23 +08:00
|
|
|
def get_conn_host(self):
|
|
|
|
host = self._connhost
|
|
|
|
port = self._connport
|
2013-09-02 04:08:14 +08:00
|
|
|
tlsport = None
|
2012-03-14 03:27:23 +08:00
|
|
|
|
|
|
|
if not self.need_tunnel():
|
|
|
|
port = self.gport
|
2013-09-02 04:08:14 +08:00
|
|
|
tlsport = self.gtlsport
|
2012-03-14 03:27:23 +08:00
|
|
|
if self.gaddr != "0.0.0.0":
|
|
|
|
host = self.gaddr
|
|
|
|
|
2013-09-02 04:08:14 +08:00
|
|
|
return host, port, tlsport
|
2012-03-14 03:27:23 +08:00
|
|
|
|
|
|
|
def logstring(self):
|
|
|
|
return ("proto=%s trans=%s connhost=%s connuser=%s "
|
2013-09-02 04:08:14 +08:00
|
|
|
"connport=%s gaddr=%s gport=%s gtlsport=%s gsocket=%s" %
|
2012-03-14 03:27:23 +08:00
|
|
|
(self.gtype, self.transport, self._connhost, self.connuser,
|
2013-09-02 04:08:14 +08:00
|
|
|
self._connport, self.gaddr, self.gport, self.gtlsport,
|
|
|
|
self.gsocket))
|
2012-03-14 03:27:23 +08:00
|
|
|
def console_active(self):
|
|
|
|
if self.gsocket:
|
|
|
|
return True
|
2013-10-07 03:30:01 +08:00
|
|
|
if (self.gport in [None, -1] and self.gtlsport in [None, -1]):
|
2012-03-14 03:27:23 +08:00
|
|
|
return False
|
2013-10-07 03:30:01 +08:00
|
|
|
return True
|
2010-12-22 05:34:36 +08:00
|
|
|
|
2013-01-16 10:22:32 +08:00
|
|
|
|
2013-09-07 07:36:09 +08:00
|
|
|
class _TunnelScheduler(object):
|
|
|
|
"""
|
|
|
|
If the user is using Spice + SSH URI + no SSH keys, we need to
|
|
|
|
serialize connection opening otherwise ssh-askpass gets all angry.
|
|
|
|
This handles the locking and scheduling.
|
|
|
|
|
|
|
|
It's only instantiated once for the whole app, because we serialize
|
|
|
|
independent of connection, vm, etc.
|
|
|
|
"""
|
|
|
|
def __init__(self):
|
|
|
|
self._thread = threading.Thread(name="Tunnel thread",
|
|
|
|
target=self._handle_queue,
|
|
|
|
args=())
|
|
|
|
self._thread.daemon = True
|
|
|
|
self._queue = Queue.Queue()
|
|
|
|
self._lock = threading.Lock()
|
|
|
|
|
|
|
|
def _handle_queue(self):
|
|
|
|
while True:
|
|
|
|
cb, args, = self._queue.get()
|
|
|
|
self.lock()
|
|
|
|
vmmGObject.idle_add(cb, *args)
|
|
|
|
|
|
|
|
def schedule(self, cb, *args):
|
|
|
|
if not self._thread.is_alive():
|
|
|
|
self._thread.start()
|
|
|
|
self._queue.put((cb, args))
|
|
|
|
|
|
|
|
def lock(self):
|
|
|
|
self._lock.acquire()
|
|
|
|
def unlock(self):
|
|
|
|
self._lock.release()
|
|
|
|
|
|
|
|
_tunnel_sched = _TunnelScheduler()
|
|
|
|
|
|
|
|
|
|
|
|
class _Tunnel(object):
|
2010-12-22 05:34:36 +08:00
|
|
|
def __init__(self):
|
|
|
|
self.outfd = None
|
|
|
|
self.errfd = None
|
|
|
|
self.pid = None
|
2013-09-07 07:36:09 +08:00
|
|
|
self._outfds = None
|
|
|
|
self._errfds = None
|
|
|
|
self.closed = False
|
2010-12-22 05:34:36 +08:00
|
|
|
|
2012-03-14 03:27:23 +08:00
|
|
|
def open(self, ginfo):
|
2013-09-07 07:36:09 +08:00
|
|
|
self._outfds = socket.socketpair()
|
|
|
|
self._errfds = socket.socketpair()
|
|
|
|
|
|
|
|
return self._outfds[0].fileno(), self._launch_tunnel, ginfo
|
|
|
|
|
|
|
|
def close(self):
|
|
|
|
if self.closed:
|
|
|
|
return
|
|
|
|
self.closed = True
|
|
|
|
|
|
|
|
logging.debug("Close tunnel PID=%s OUTFD=%s ERRFD=%s",
|
|
|
|
self.pid,
|
|
|
|
self.outfd and self.outfd.fileno() or self._outfds,
|
|
|
|
self.errfd and self.errfd.fileno() or self._errfds)
|
|
|
|
|
|
|
|
if self.outfd:
|
|
|
|
self.outfd.close()
|
|
|
|
elif self._outfds:
|
|
|
|
self._outfds[0].close()
|
|
|
|
self._outfds[1].close()
|
|
|
|
self.outfd = None
|
|
|
|
self._outfds = None
|
|
|
|
|
|
|
|
if self.errfd:
|
|
|
|
self.errfd.close()
|
|
|
|
elif self._errfds:
|
|
|
|
self._errfds[0].close()
|
|
|
|
self._errfds[1].close()
|
|
|
|
self.errfd = None
|
|
|
|
self._errfds = None
|
|
|
|
|
|
|
|
if self.pid:
|
|
|
|
os.kill(self.pid, signal.SIGKILL)
|
|
|
|
os.waitpid(self.pid, 0)
|
|
|
|
self.pid = None
|
|
|
|
|
|
|
|
def get_err_output(self):
|
|
|
|
errout = ""
|
|
|
|
while True:
|
|
|
|
try:
|
|
|
|
new = self.errfd.recv(1024)
|
|
|
|
except:
|
|
|
|
break
|
|
|
|
|
|
|
|
if not new:
|
|
|
|
break
|
|
|
|
|
|
|
|
errout += new
|
|
|
|
|
|
|
|
return errout
|
|
|
|
|
|
|
|
def _launch_tunnel(self, ginfo):
|
|
|
|
if self.closed:
|
2010-12-22 05:34:36 +08:00
|
|
|
return -1
|
|
|
|
|
2013-09-02 04:08:14 +08:00
|
|
|
host, port, ignore = ginfo.get_conn_host()
|
2012-03-14 03:27:23 +08:00
|
|
|
|
2010-12-22 05:34:36 +08:00
|
|
|
# Build SSH cmd
|
|
|
|
argv = ["ssh", "ssh"]
|
2012-03-14 03:27:23 +08:00
|
|
|
if port:
|
|
|
|
argv += ["-p", str(port)]
|
2010-12-22 05:34:36 +08:00
|
|
|
|
2012-03-14 03:27:23 +08:00
|
|
|
if ginfo.connuser:
|
|
|
|
argv += ['-l', ginfo.connuser]
|
2010-12-22 05:34:36 +08:00
|
|
|
|
2012-03-14 03:27:23 +08:00
|
|
|
argv += [host]
|
2010-12-22 05:34:36 +08:00
|
|
|
|
|
|
|
# Build 'nc' command run on the remote host
|
|
|
|
#
|
|
|
|
# This ugly thing is a shell script to detect availability of
|
|
|
|
# the -q option for 'nc': debian and suse based distros need this
|
|
|
|
# flag to ensure the remote nc will exit on EOF, so it will go away
|
|
|
|
# when we close the VNC tunnel. If it doesn't go away, subsequent
|
|
|
|
# VNC connection attempts will hang.
|
|
|
|
#
|
|
|
|
# Fedora's 'nc' doesn't have this option, and apparently defaults
|
|
|
|
# to the desired behavior.
|
|
|
|
#
|
2012-03-14 03:27:23 +08:00
|
|
|
if ginfo.gsocket:
|
|
|
|
nc_params = "-U %s" % ginfo.gsocket
|
2011-01-14 23:20:20 +08:00
|
|
|
else:
|
2012-03-14 03:27:23 +08:00
|
|
|
nc_params = "%s %s" % (ginfo.gaddr, ginfo.gport)
|
2011-01-14 00:26:38 +08:00
|
|
|
|
2010-12-22 05:34:36 +08:00
|
|
|
nc_cmd = (
|
2011-08-09 04:42:18 +08:00
|
|
|
"""nc -q 2>&1 | grep "requires an argument" >/dev/null;"""
|
2010-12-22 05:34:36 +08:00
|
|
|
"""if [ $? -eq 0 ] ; then"""
|
|
|
|
""" CMD="nc -q 0 %(nc_params)s";"""
|
|
|
|
"""else"""
|
|
|
|
""" CMD="nc %(nc_params)s";"""
|
|
|
|
"""fi;"""
|
|
|
|
"""eval "$CMD";""" %
|
|
|
|
{'nc_params': nc_params})
|
|
|
|
|
|
|
|
argv.append("sh -c")
|
|
|
|
argv.append("'%s'" % nc_cmd)
|
|
|
|
|
|
|
|
argv_str = reduce(lambda x, y: x + " " + y, argv[1:])
|
2012-01-17 11:04:40 +08:00
|
|
|
logging.debug("Creating SSH tunnel: %s", argv_str)
|
2010-12-22 05:34:36 +08:00
|
|
|
|
|
|
|
pid = os.fork()
|
|
|
|
if pid == 0:
|
2013-09-07 07:36:09 +08:00
|
|
|
self._outfds[0].close()
|
|
|
|
self._errfds[0].close()
|
2010-12-22 05:34:36 +08:00
|
|
|
|
|
|
|
os.close(0)
|
|
|
|
os.close(1)
|
|
|
|
os.close(2)
|
2013-09-07 07:36:09 +08:00
|
|
|
os.dup(self._outfds[1].fileno())
|
|
|
|
os.dup(self._outfds[1].fileno())
|
|
|
|
os.dup(self._errfds[1].fileno())
|
2010-12-22 05:34:36 +08:00
|
|
|
os.execlp(*argv)
|
2013-04-14 02:34:52 +08:00
|
|
|
os._exit(1) # pylint: disable=W0212
|
2010-12-22 05:34:36 +08:00
|
|
|
else:
|
2013-09-07 07:36:09 +08:00
|
|
|
self._outfds[1].close()
|
|
|
|
self._errfds[1].close()
|
2010-12-22 05:34:36 +08:00
|
|
|
|
2013-09-07 07:36:09 +08:00
|
|
|
logging.debug("Open tunnel PID=%d OUTFD=%d ERRFD=%d",
|
|
|
|
pid, self._outfds[0].fileno(), self._errfds[0].fileno())
|
|
|
|
self._errfds[0].setblocking(0)
|
2010-12-22 05:34:36 +08:00
|
|
|
|
2013-09-07 07:36:09 +08:00
|
|
|
self.outfd = self._outfds[0]
|
|
|
|
self.errfd = self._errfds[0]
|
|
|
|
self._outfds = None
|
|
|
|
self._errfds = None
|
2010-12-22 05:34:36 +08:00
|
|
|
self.pid = pid
|
|
|
|
|
2013-01-16 10:22:32 +08:00
|
|
|
|
2010-12-22 05:34:36 +08:00
|
|
|
class Tunnels(object):
|
2012-03-14 03:27:23 +08:00
|
|
|
def __init__(self, ginfo):
|
|
|
|
self.ginfo = ginfo
|
2010-12-22 05:34:36 +08:00
|
|
|
self._tunnels = []
|
|
|
|
|
|
|
|
def open_new(self):
|
2013-09-07 07:36:09 +08:00
|
|
|
t = _Tunnel()
|
|
|
|
fd, cb, args = t.open(self.ginfo)
|
2010-12-22 05:34:36 +08:00
|
|
|
self._tunnels.append(t)
|
2013-09-07 07:36:09 +08:00
|
|
|
_tunnel_sched.schedule(cb, args)
|
|
|
|
|
2010-12-22 05:34:36 +08:00
|
|
|
return fd
|
|
|
|
|
|
|
|
def close_all(self):
|
|
|
|
for l in self._tunnels:
|
|
|
|
l.close()
|
|
|
|
|
|
|
|
def get_err_output(self):
|
|
|
|
errout = ""
|
|
|
|
for l in self._tunnels:
|
|
|
|
errout += l.get_err_output()
|
|
|
|
return errout
|
|
|
|
|
2013-09-07 07:36:09 +08:00
|
|
|
lock = _tunnel_sched.lock
|
|
|
|
unlock = _tunnel_sched.unlock
|
|
|
|
|
2010-12-22 05:34:36 +08:00
|
|
|
|
2011-05-19 05:36:08 +08:00
|
|
|
class Viewer(vmmGObject):
|
|
|
|
def __init__(self, console):
|
|
|
|
vmmGObject.__init__(self)
|
2010-12-22 09:13:11 +08:00
|
|
|
self.console = console
|
2011-05-19 05:36:08 +08:00
|
|
|
self.display = None
|
|
|
|
|
2011-06-01 22:38:37 +08:00
|
|
|
def close(self):
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
2011-07-24 09:16:54 +08:00
|
|
|
def _cleanup(self):
|
2011-06-01 22:28:59 +08:00
|
|
|
self.close()
|
2011-05-19 05:36:08 +08:00
|
|
|
|
|
|
|
if self.display:
|
|
|
|
self.display.destroy()
|
2010-12-22 09:13:11 +08:00
|
|
|
self.display = None
|
2011-06-01 22:28:59 +08:00
|
|
|
self.console = None
|
2010-12-22 09:13:11 +08:00
|
|
|
|
|
|
|
def get_pixbuf(self):
|
|
|
|
return self.display.get_pixbuf()
|
|
|
|
|
2013-09-07 07:36:09 +08:00
|
|
|
def open_ginfo(self, ginfo):
|
|
|
|
if ginfo.need_tunnel():
|
|
|
|
self.open_fd(self.console.tunnels.open_new())
|
|
|
|
else:
|
|
|
|
self.open_host(ginfo)
|
|
|
|
|
2010-12-22 09:13:11 +08:00
|
|
|
def get_grab_keys(self):
|
2012-05-14 21:24:56 +08:00
|
|
|
raise NotImplementedError()
|
2010-12-22 09:13:11 +08:00
|
|
|
|
2012-05-14 21:24:56 +08:00
|
|
|
def set_grab_keys(self):
|
|
|
|
raise NotImplementedError()
|
2010-12-22 09:13:11 +08:00
|
|
|
|
|
|
|
def send_keys(self, keys):
|
2013-01-16 10:22:32 +08:00
|
|
|
raise NotImplementedError()
|
2010-12-22 09:13:11 +08:00
|
|
|
|
2013-09-07 07:36:09 +08:00
|
|
|
def open_host(self, ginfo):
|
2012-03-14 03:27:23 +08:00
|
|
|
raise NotImplementedError()
|
|
|
|
|
2013-09-07 07:36:09 +08:00
|
|
|
def open_fd(self, fd):
|
2011-01-14 00:26:38 +08:00
|
|
|
raise NotImplementedError()
|
|
|
|
|
2011-04-26 22:51:19 +08:00
|
|
|
def get_desktop_resolution(self):
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
2013-07-02 02:33:59 +08:00
|
|
|
def has_usb_redirection(self):
|
|
|
|
return False
|
|
|
|
|
2013-01-16 10:22:32 +08:00
|
|
|
|
2010-12-22 09:13:11 +08:00
|
|
|
class VNCViewer(Viewer):
|
2011-05-19 05:36:08 +08:00
|
|
|
def __init__(self, console):
|
|
|
|
Viewer.__init__(self, console)
|
2012-05-14 21:24:56 +08:00
|
|
|
self.display = GtkVnc.Display.new()
|
2011-01-14 00:26:38 +08:00
|
|
|
self.sockfd = None
|
2011-05-19 05:36:08 +08:00
|
|
|
|
2011-04-26 22:51:19 +08:00
|
|
|
# Last noticed desktop resolution
|
|
|
|
self.desktop_resolution = None
|
2010-12-22 09:13:11 +08:00
|
|
|
|
2013-09-07 07:36:09 +08:00
|
|
|
self._tunnel_unlocked = False
|
|
|
|
|
2010-12-22 09:13:11 +08:00
|
|
|
def init_widget(self):
|
2012-01-30 10:51:20 +08:00
|
|
|
self.set_grab_keys()
|
2010-12-22 09:13:11 +08:00
|
|
|
|
|
|
|
self.display.realize()
|
|
|
|
|
|
|
|
# Make sure viewer doesn't force resize itself
|
|
|
|
self.display.set_force_size(False)
|
|
|
|
|
|
|
|
self.console.refresh_scaling()
|
|
|
|
|
2012-07-15 22:53:52 +08:00
|
|
|
self.display.set_keyboard_grab(True)
|
2010-12-22 09:13:11 +08:00
|
|
|
self.display.set_pointer_grab(True)
|
|
|
|
|
|
|
|
self.display.connect("vnc-pointer-grab", self.console.pointer_grabbed)
|
2013-09-07 07:36:09 +08:00
|
|
|
self.display.connect("vnc-pointer-ungrab",
|
|
|
|
self.console.pointer_ungrabbed)
|
2010-12-22 09:13:11 +08:00
|
|
|
self.display.connect("vnc-auth-credential", self._auth_credential)
|
2013-09-07 07:36:09 +08:00
|
|
|
self.display.connect("vnc-initialized", self._connected_cb)
|
|
|
|
self.display.connect("vnc-disconnected", self._disconnected_cb)
|
2011-04-26 22:51:19 +08:00
|
|
|
self.display.connect("vnc-desktop-resize", self._desktop_resize)
|
2013-09-07 07:36:09 +08:00
|
|
|
self.display.connect("focus-in-event",
|
|
|
|
self.console.viewer_focus_changed)
|
|
|
|
self.display.connect("focus-out-event",
|
|
|
|
self.console.viewer_focus_changed)
|
2010-12-22 09:13:11 +08:00
|
|
|
|
|
|
|
self.display.show()
|
|
|
|
|
2013-09-07 07:36:09 +08:00
|
|
|
def _unlock_tunnel(self):
|
|
|
|
if self.console.tunnels and not self._tunnel_unlocked:
|
|
|
|
self.console.tunnels.unlock()
|
|
|
|
self._tunnel_unlocked = True
|
|
|
|
|
|
|
|
def _connected_cb(self, ignore):
|
|
|
|
self._unlock_tunnel()
|
|
|
|
self.console.connected()
|
|
|
|
|
|
|
|
def _disconnected_cb(self, ignore):
|
|
|
|
self._unlock_tunnel()
|
|
|
|
self.console.disconnected()
|
|
|
|
|
2012-05-14 21:24:56 +08:00
|
|
|
def get_grab_keys(self):
|
|
|
|
return self.display.get_grab_keys().as_string()
|
|
|
|
|
|
|
|
def set_grab_keys(self):
|
|
|
|
try:
|
|
|
|
keys = self.config.get_keys_combination()
|
|
|
|
if not keys:
|
|
|
|
return
|
|
|
|
|
|
|
|
try:
|
2013-04-12 04:32:00 +08:00
|
|
|
keys = [int(k) for k in keys.split(',')]
|
2012-05-14 21:24:56 +08:00
|
|
|
except:
|
|
|
|
logging.debug("Error in grab_keys configuration in GConf",
|
|
|
|
exc_info=True)
|
|
|
|
return
|
|
|
|
|
|
|
|
seq = GtkVnc.GrabSequence.new(keys)
|
|
|
|
self.display.set_grab_keys(seq)
|
|
|
|
except Exception, e:
|
|
|
|
logging.debug("Error when getting the grab keys combination: %s",
|
|
|
|
str(e))
|
|
|
|
|
2013-01-16 10:22:32 +08:00
|
|
|
def send_keys(self, keys):
|
|
|
|
return self.display.send_keys([Gdk.keyval_from_name(k) for k in keys])
|
|
|
|
|
2011-04-26 22:51:19 +08:00
|
|
|
def _desktop_resize(self, src_ignore, w, h):
|
|
|
|
self.desktop_resolution = (w, h)
|
2013-04-22 00:10:14 +08:00
|
|
|
self.console.widget("console-gfx-scroll").queue_resize()
|
2011-04-26 22:51:19 +08:00
|
|
|
|
|
|
|
def get_desktop_resolution(self):
|
|
|
|
return self.desktop_resolution
|
|
|
|
|
2010-12-22 09:13:11 +08:00
|
|
|
def _auth_credential(self, src_ignore, credList):
|
2012-11-09 23:08:15 +08:00
|
|
|
values = []
|
|
|
|
for idx in range(int(credList.n_values)):
|
|
|
|
values.append(credList.get_nth(idx))
|
|
|
|
|
|
|
|
for cred in values:
|
2012-05-14 21:24:56 +08:00
|
|
|
if cred in [GtkVnc.DisplayCredential.PASSWORD,
|
|
|
|
GtkVnc.DisplayCredential.USERNAME,
|
|
|
|
GtkVnc.DisplayCredential.CLIENTNAME]:
|
2011-04-16 02:12:50 +08:00
|
|
|
continue
|
|
|
|
|
|
|
|
self.console.err.show_err(
|
|
|
|
summary=_("Unable to provide requested credentials to "
|
|
|
|
"the VNC server"),
|
|
|
|
details=(_("The credential type %s is not supported") %
|
|
|
|
(str(cred))),
|
2013-09-07 08:16:37 +08:00
|
|
|
title=_("Unable to authenticate"))
|
2011-04-16 02:12:50 +08:00
|
|
|
|
|
|
|
# schedule_retry will error out
|
|
|
|
self.console.viewerRetriesScheduled = 10
|
|
|
|
self.close()
|
|
|
|
self.console.activate_unavailable_page(
|
|
|
|
_("Unsupported console authentication type"))
|
|
|
|
return
|
2010-12-22 09:13:11 +08:00
|
|
|
|
|
|
|
withUsername = False
|
|
|
|
withPassword = False
|
2012-11-09 23:08:15 +08:00
|
|
|
for cred in values:
|
2012-01-17 11:04:40 +08:00
|
|
|
logging.debug("Got credential request %s", cred)
|
2012-05-14 21:24:56 +08:00
|
|
|
if cred == GtkVnc.DisplayCredential.PASSWORD:
|
2010-12-22 09:13:11 +08:00
|
|
|
withPassword = True
|
2012-05-14 21:24:56 +08:00
|
|
|
elif cred == GtkVnc.DisplayCredential.USERNAME:
|
2010-12-22 09:13:11 +08:00
|
|
|
withUsername = True
|
2012-05-14 21:24:56 +08:00
|
|
|
elif cred == GtkVnc.DisplayCredential.CLIENTNAME:
|
2011-04-16 02:12:50 +08:00
|
|
|
self.display.set_credential(cred, "libvirt-vnc")
|
2010-12-22 09:13:11 +08:00
|
|
|
|
|
|
|
if withUsername or withPassword:
|
|
|
|
self.console.activate_auth_page(withPassword, withUsername)
|
|
|
|
|
|
|
|
def get_scaling(self):
|
|
|
|
return self.display.get_scaling()
|
|
|
|
|
|
|
|
def set_scaling(self, scaling):
|
|
|
|
return self.display.set_scaling(scaling)
|
|
|
|
|
|
|
|
def close(self):
|
|
|
|
self.display.close()
|
2011-01-14 00:26:38 +08:00
|
|
|
if not self.sockfd:
|
|
|
|
return
|
|
|
|
|
|
|
|
self.sockfd.close()
|
|
|
|
self.sockfd = None
|
2010-12-22 09:13:11 +08:00
|
|
|
|
|
|
|
def is_open(self):
|
|
|
|
return self.display.is_open()
|
|
|
|
|
2013-09-07 07:36:09 +08:00
|
|
|
def open_host(self, ginfo):
|
2013-09-02 04:08:14 +08:00
|
|
|
host, port, ignore = ginfo.get_conn_host()
|
2011-01-14 23:20:20 +08:00
|
|
|
|
2012-03-14 03:27:23 +08:00
|
|
|
if not ginfo.gsocket:
|
|
|
|
logging.debug("VNC connection to %s:%s", host, port)
|
2011-01-14 23:20:20 +08:00
|
|
|
self.display.open_host(host, port)
|
|
|
|
return
|
|
|
|
|
2012-03-14 03:27:23 +08:00
|
|
|
logging.debug("VNC connecting to socket=%s", ginfo.gsocket)
|
2011-01-14 23:20:20 +08:00
|
|
|
try:
|
|
|
|
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
2012-03-14 03:27:23 +08:00
|
|
|
sock.connect(ginfo.gsocket)
|
2011-01-14 23:20:20 +08:00
|
|
|
self.sockfd = sock
|
|
|
|
except Exception, e:
|
|
|
|
raise RuntimeError(_("Error opening socket path '%s': %s") %
|
2012-03-14 03:27:23 +08:00
|
|
|
(ginfo.gsocket, e))
|
2011-01-14 23:20:20 +08:00
|
|
|
|
|
|
|
fd = self.sockfd.fileno()
|
|
|
|
if fd < 0:
|
|
|
|
raise RuntimeError((_("Error opening socket path '%s'") %
|
2012-03-14 03:27:23 +08:00
|
|
|
ginfo.gsocket) + " fd=%s" % fd)
|
2011-01-14 23:20:20 +08:00
|
|
|
self.open_fd(fd)
|
2010-12-22 09:13:11 +08:00
|
|
|
|
2013-09-07 07:36:09 +08:00
|
|
|
def open_fd(self, fd):
|
2010-12-22 09:13:11 +08:00
|
|
|
self.display.open_fd(fd)
|
|
|
|
|
|
|
|
def set_credential_username(self, cred):
|
2012-05-14 21:24:56 +08:00
|
|
|
self.display.set_credential(GtkVnc.DisplayCredential.USERNAME, cred)
|
2010-12-22 09:13:11 +08:00
|
|
|
|
|
|
|
def set_credential_password(self, cred):
|
2012-05-14 21:24:56 +08:00
|
|
|
self.display.set_credential(GtkVnc.DisplayCredential.PASSWORD, cred)
|
2010-12-22 09:13:11 +08:00
|
|
|
|
|
|
|
|
2010-12-22 09:13:25 +08:00
|
|
|
class SpiceViewer(Viewer):
|
2011-05-19 05:36:08 +08:00
|
|
|
def __init__(self, console):
|
|
|
|
Viewer.__init__(self, console)
|
2010-12-22 09:13:25 +08:00
|
|
|
self.spice_session = None
|
|
|
|
self.display = None
|
|
|
|
self.audio = None
|
2011-04-26 22:51:19 +08:00
|
|
|
self.display_channel = None
|
2013-07-02 02:33:59 +08:00
|
|
|
self.usbdev_manager = None
|
2010-12-22 09:13:25 +08:00
|
|
|
|
|
|
|
def _init_widget(self):
|
2011-01-08 04:29:33 +08:00
|
|
|
self.set_grab_keys()
|
2010-12-22 09:13:25 +08:00
|
|
|
self.console.refresh_scaling()
|
|
|
|
|
|
|
|
self.display.realize()
|
2013-09-07 07:36:09 +08:00
|
|
|
self.display.connect("mouse-grab",
|
|
|
|
lambda src, g: g and self.console.pointer_grabbed(src))
|
|
|
|
self.display.connect("mouse-grab",
|
|
|
|
lambda src, g: g or self.console.pointer_ungrabbed(src))
|
2011-09-27 04:32:37 +08:00
|
|
|
|
|
|
|
self.display.connect("focus-in-event",
|
|
|
|
self.console.viewer_focus_changed)
|
|
|
|
self.display.connect("focus-out-event",
|
|
|
|
self.console.viewer_focus_changed)
|
|
|
|
|
2010-12-22 09:13:25 +08:00
|
|
|
self.display.show()
|
|
|
|
|
2012-05-14 21:24:56 +08:00
|
|
|
def get_grab_keys(self):
|
|
|
|
return self.display.get_grab_keys().as_string()
|
|
|
|
|
|
|
|
def set_grab_keys(self):
|
|
|
|
try:
|
|
|
|
keys = self.config.get_keys_combination()
|
|
|
|
if not keys:
|
|
|
|
return
|
|
|
|
|
|
|
|
try:
|
2013-04-12 04:32:00 +08:00
|
|
|
keys = [int(k) for k in keys.split(',')]
|
2012-05-14 21:24:56 +08:00
|
|
|
except:
|
|
|
|
logging.debug("Error in grab_keys configuration in GConf",
|
|
|
|
exc_info=True)
|
|
|
|
return
|
|
|
|
|
|
|
|
seq = SpiceClientGtk.GrabSequence.new(keys)
|
|
|
|
self.display.set_grab_keys(seq)
|
|
|
|
except Exception, e:
|
|
|
|
logging.debug("Error when getting the grab keys combination: %s",
|
|
|
|
str(e))
|
|
|
|
|
2013-01-16 10:22:32 +08:00
|
|
|
def send_keys(self, keys):
|
|
|
|
return self.display.send_keys([Gdk.keyval_from_name(k) for k in keys],
|
|
|
|
SpiceClientGtk.DisplayKeyEvent.CLICK)
|
|
|
|
|
2010-12-22 09:13:25 +08:00
|
|
|
def close(self):
|
|
|
|
if self.spice_session is not None:
|
|
|
|
self.spice_session.disconnect()
|
2011-01-15 00:34:51 +08:00
|
|
|
self.spice_session = None
|
|
|
|
self.audio = None
|
2011-06-01 22:38:37 +08:00
|
|
|
if self.display:
|
|
|
|
self.display.destroy()
|
2011-01-15 00:34:51 +08:00
|
|
|
self.display = None
|
2011-04-26 22:51:19 +08:00
|
|
|
self.display_channel = None
|
2013-07-02 02:33:59 +08:00
|
|
|
self.usbdev_manager = None
|
2010-12-22 09:13:25 +08:00
|
|
|
|
|
|
|
def is_open(self):
|
2012-11-08 21:15:02 +08:00
|
|
|
return self.spice_session is not None
|
2010-12-22 09:13:25 +08:00
|
|
|
|
|
|
|
def _main_channel_event_cb(self, channel, event):
|
2013-09-02 01:25:37 +08:00
|
|
|
if not self.console:
|
|
|
|
return
|
|
|
|
|
2012-05-14 21:24:56 +08:00
|
|
|
if event == SpiceClientGLib.ChannelEvent.CLOSED:
|
2013-09-02 01:25:37 +08:00
|
|
|
self.console.disconnected()
|
2012-05-14 21:24:56 +08:00
|
|
|
elif event == SpiceClientGLib.ChannelEvent.ERROR_AUTH:
|
2013-09-02 01:25:37 +08:00
|
|
|
self.console.activate_auth_page()
|
|
|
|
elif event in [SpiceClientGLib.ChannelEvent.ERROR_CONNECT,
|
|
|
|
SpiceClientGLib.ChannelEvent.ERROR_IO,
|
|
|
|
SpiceClientGLib.ChannelEvent.ERROR_LINK,
|
|
|
|
SpiceClientGLib.ChannelEvent.ERROR_TLS]:
|
|
|
|
logging.debug("Spice channel event error: %s", event)
|
|
|
|
self.console.disconnected()
|
2010-12-22 09:13:25 +08:00
|
|
|
|
2013-09-07 07:36:09 +08:00
|
|
|
def _fd_channel_event_cb(self, channel, event):
|
|
|
|
# When we see any event from the channel, release the
|
|
|
|
# associated tunnel lock
|
|
|
|
channel.disconnect_by_func(self._fd_channel_event_cb)
|
|
|
|
self.console.tunnels.unlock()
|
|
|
|
|
2010-12-22 09:13:25 +08:00
|
|
|
def _channel_open_fd_request(self, channel, tls_ignore):
|
|
|
|
if not self.console.tunnels:
|
|
|
|
raise SystemError("Got fd request with no configured tunnel!")
|
|
|
|
|
2012-04-05 23:03:16 +08:00
|
|
|
logging.debug("Opening tunnel for channel: %s", channel)
|
2013-09-07 07:36:09 +08:00
|
|
|
channel.connect_after("channel-event", self._fd_channel_event_cb)
|
|
|
|
|
2010-12-22 09:13:25 +08:00
|
|
|
fd = self.console.tunnels.open_new()
|
|
|
|
channel.open_fd(fd)
|
|
|
|
|
|
|
|
def _channel_new_cb(self, session, channel):
|
2012-05-14 21:24:56 +08:00
|
|
|
GObject.GObject.connect(channel, "open-fd",
|
2010-12-22 09:13:25 +08:00
|
|
|
self._channel_open_fd_request)
|
|
|
|
|
2012-05-14 21:24:56 +08:00
|
|
|
if type(channel) == SpiceClientGLib.MainChannel:
|
2013-09-07 07:36:09 +08:00
|
|
|
if self.console.tunnels:
|
|
|
|
self.console.tunnels.unlock()
|
2010-12-22 09:13:25 +08:00
|
|
|
channel.connect_after("channel-event", self._main_channel_event_cb)
|
|
|
|
return
|
|
|
|
|
2013-07-10 05:50:05 +08:00
|
|
|
if (type(channel) == SpiceClientGLib.DisplayChannel and
|
|
|
|
not self.display):
|
2010-12-22 09:13:25 +08:00
|
|
|
channel_id = channel.get_property("channel-id")
|
2011-04-26 22:51:20 +08:00
|
|
|
|
|
|
|
if channel_id != 0:
|
|
|
|
logging.debug("Spice multi-head unsupported")
|
|
|
|
return
|
|
|
|
|
2011-04-26 22:51:19 +08:00
|
|
|
self.display_channel = channel
|
2013-01-16 07:23:17 +08:00
|
|
|
self.display = SpiceClientGtk.Display.new(self.spice_session,
|
|
|
|
channel_id)
|
2013-04-22 00:10:14 +08:00
|
|
|
self.console.widget("console-gfx-viewport").add(self.display)
|
2010-12-22 09:13:25 +08:00
|
|
|
self._init_widget()
|
2011-01-08 04:15:10 +08:00
|
|
|
self.console.connected()
|
2010-12-22 09:13:25 +08:00
|
|
|
return
|
|
|
|
|
2013-01-16 07:23:17 +08:00
|
|
|
if (type(channel) in [SpiceClientGLib.PlaybackChannel,
|
|
|
|
SpiceClientGLib.RecordChannel] and
|
2011-01-08 04:15:10 +08:00
|
|
|
not self.audio):
|
2013-01-16 07:23:17 +08:00
|
|
|
self.audio = SpiceClientGLib.Audio.get(self.spice_session, None)
|
2010-12-22 09:13:25 +08:00
|
|
|
return
|
|
|
|
|
2011-04-26 22:51:19 +08:00
|
|
|
def get_desktop_resolution(self):
|
2011-06-01 22:20:50 +08:00
|
|
|
if (not self.display_channel or
|
|
|
|
not has_property(self.display_channel, "width")):
|
2011-04-26 22:51:19 +08:00
|
|
|
return None
|
|
|
|
return self.display_channel.get_properties("width", "height")
|
|
|
|
|
2013-07-02 02:33:59 +08:00
|
|
|
def _create_spice_session(self):
|
|
|
|
self.spice_session = SpiceClientGLib.Session()
|
2013-09-02 04:08:14 +08:00
|
|
|
SpiceClientGLib.set_session_option(self.spice_session)
|
2013-07-02 02:33:59 +08:00
|
|
|
gtk_session = SpiceClientGtk.GtkSession.get(self.spice_session)
|
|
|
|
gtk_session.set_property("auto-clipboard", True)
|
|
|
|
|
2013-09-07 07:36:09 +08:00
|
|
|
GObject.GObject.connect(self.spice_session, "channel-new",
|
|
|
|
self._channel_new_cb)
|
|
|
|
|
2013-07-09 07:13:52 +08:00
|
|
|
self.usbdev_manager = SpiceClientGLib.UsbDeviceManager.get(
|
|
|
|
self.spice_session)
|
|
|
|
self.usbdev_manager.connect("auto-connect-failed",
|
|
|
|
self._usbdev_redirect_error)
|
|
|
|
self.usbdev_manager.connect("device-error",
|
|
|
|
self._usbdev_redirect_error)
|
2013-07-02 02:33:59 +08:00
|
|
|
|
|
|
|
autoredir = self.config.get_auto_redirection()
|
|
|
|
if autoredir:
|
|
|
|
gtk_session.set_property("auto-usbredir", True)
|
|
|
|
|
2013-09-07 07:36:09 +08:00
|
|
|
def open_host(self, ginfo):
|
2013-09-02 04:08:14 +08:00
|
|
|
host, port, tlsport = ginfo.get_conn_host()
|
2013-07-02 02:33:59 +08:00
|
|
|
self._create_spice_session()
|
2013-09-07 07:36:09 +08:00
|
|
|
|
2013-09-02 04:08:14 +08:00
|
|
|
self.spice_session.set_property("host", str(host))
|
2013-10-04 03:54:15 +08:00
|
|
|
if port:
|
|
|
|
self.spice_session.set_property("port", str(port))
|
2013-09-02 04:08:14 +08:00
|
|
|
if tlsport:
|
|
|
|
self.spice_session.set_property("tls-port", str(tlsport))
|
2013-09-07 07:36:09 +08:00
|
|
|
|
2010-12-22 09:13:25 +08:00
|
|
|
self.spice_session.connect()
|
|
|
|
|
2013-09-07 07:36:09 +08:00
|
|
|
def open_fd(self, fd):
|
2013-07-02 02:33:59 +08:00
|
|
|
self._create_spice_session()
|
2010-12-22 09:13:25 +08:00
|
|
|
self.spice_session.open_fd(fd)
|
|
|
|
|
|
|
|
def set_credential_password(self, cred):
|
|
|
|
self.spice_session.set_property("password", cred)
|
2011-01-07 22:14:33 +08:00
|
|
|
if self.console.tunnels:
|
|
|
|
fd = self.console.tunnels.open_new()
|
|
|
|
self.spice_session.open_fd(fd)
|
|
|
|
else:
|
|
|
|
self.spice_session.connect()
|
2010-12-22 09:13:25 +08:00
|
|
|
|
|
|
|
def get_scaling(self):
|
2012-01-29 05:38:01 +08:00
|
|
|
if not has_property(self.display, "scaling"):
|
|
|
|
return False
|
|
|
|
return self.display.get_property("scaling")
|
2010-12-22 09:13:25 +08:00
|
|
|
|
|
|
|
def set_scaling(self, scaling):
|
2012-01-29 05:38:01 +08:00
|
|
|
if not has_property(self.display, "scaling"):
|
|
|
|
logging.debug("Spice version doesn't support scaling.")
|
|
|
|
return
|
|
|
|
self.display.set_property("scaling", scaling)
|
2010-12-22 09:13:25 +08:00
|
|
|
|
2013-07-02 02:33:59 +08:00
|
|
|
def _usbdev_redirect_error(self,
|
|
|
|
spice_usbdev_widget, spice_usb_device,
|
|
|
|
errstr):
|
|
|
|
ignore_widget = spice_usbdev_widget
|
|
|
|
ignore_device = spice_usb_device
|
|
|
|
|
|
|
|
error = self.console.err
|
|
|
|
error.show_err(_("USB redirection error"),
|
|
|
|
text2=str(errstr),
|
2013-09-07 08:16:37 +08:00
|
|
|
modal=True)
|
2013-07-02 02:33:59 +08:00
|
|
|
|
|
|
|
def get_usb_widget(self):
|
|
|
|
|
|
|
|
# The @format positional parameters are the following:
|
|
|
|
# 1 '%s' manufacturer
|
|
|
|
# 2 '%s' product
|
|
|
|
# 3 '%s' descriptor (a [vendor_id:product_id] string)
|
|
|
|
# 4 '%d' bus
|
|
|
|
# 5 '%d' address
|
|
|
|
|
|
|
|
usb_device_description_fmt = _("%s %s %s at %d-%d")
|
|
|
|
|
2013-07-09 07:13:52 +08:00
|
|
|
if not self.spice_session:
|
|
|
|
return
|
2013-07-02 02:33:59 +08:00
|
|
|
|
2013-07-09 07:13:52 +08:00
|
|
|
usbwidget = SpiceClientGtk.UsbDeviceWidget.new(
|
|
|
|
self.spice_session,
|
|
|
|
usb_device_description_fmt)
|
|
|
|
usbwidget.connect("connect-failed", self._usbdev_redirect_error)
|
|
|
|
return usbwidget
|
2013-07-02 02:33:59 +08:00
|
|
|
|
|
|
|
def has_usb_redirection(self):
|
2013-07-09 07:13:52 +08:00
|
|
|
if not self.spice_session or not self.usbdev_manager:
|
|
|
|
return False
|
2013-07-02 02:33:59 +08:00
|
|
|
|
2013-07-09 07:13:52 +08:00
|
|
|
for c in self.spice_session.get_channels():
|
|
|
|
if c.__class__ is SpiceClientGLib.UsbredirChannel:
|
2013-07-02 02:33:59 +08:00
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
2010-12-22 09:13:25 +08:00
|
|
|
|
2010-12-09 06:26:19 +08:00
|
|
|
class vmmConsolePages(vmmGObjectUI):
|
2013-02-17 02:31:46 +08:00
|
|
|
def __init__(self, vm, builder, topwin):
|
2013-06-09 07:25:36 +08:00
|
|
|
vmmGObjectUI.__init__(self, None, None, builder=builder, topwin=topwin)
|
2009-10-31 01:25:27 +08:00
|
|
|
|
|
|
|
self.vm = vm
|
2011-03-24 04:56:12 +08:00
|
|
|
self.pointer_is_grabbed = False
|
|
|
|
self.change_title()
|
|
|
|
self.vm.connect("config-changed", self.change_title)
|
2009-10-31 01:25:27 +08:00
|
|
|
|
|
|
|
# State for disabling modifiers when keyboard is grabbed
|
2013-04-17 21:30:16 +08:00
|
|
|
self.accel_groups = Gtk.accel_groups_from_object(self.topwin)
|
2009-10-31 01:25:27 +08:00
|
|
|
self.gtk_settings_accel = None
|
2009-10-31 02:36:17 +08:00
|
|
|
self.gtk_settings_mnemonic = None
|
2009-10-31 01:25:27 +08:00
|
|
|
|
|
|
|
# Initialize display widget
|
2011-04-13 22:45:10 +08:00
|
|
|
self.viewer = None
|
2010-12-22 05:34:36 +08:00
|
|
|
self.tunnels = None
|
2010-12-22 09:13:11 +08:00
|
|
|
self.viewerRetriesScheduled = 0
|
|
|
|
self.viewerRetryDelay = 125
|
2011-06-02 00:22:05 +08:00
|
|
|
self._viewer_connected = False
|
2011-01-15 03:17:35 +08:00
|
|
|
self.viewer_connecting = False
|
2011-04-13 22:45:10 +08:00
|
|
|
self.scale_type = self.vm.get_console_scaling()
|
2009-10-31 01:25:27 +08:00
|
|
|
|
2011-05-19 05:22:07 +08:00
|
|
|
# Fullscreen toolbar
|
2012-01-30 10:56:18 +08:00
|
|
|
self.send_key_button = None
|
2011-05-19 05:22:07 +08:00
|
|
|
self.fs_toolbar = None
|
|
|
|
self.fs_drawer = None
|
2011-05-19 06:54:27 +08:00
|
|
|
self.keycombo_menu = uihelpers.build_keycombo_menu(self.send_key)
|
2011-05-19 05:22:07 +08:00
|
|
|
self.init_fs_toolbar()
|
|
|
|
|
2010-12-22 09:13:11 +08:00
|
|
|
# Make viewer widget background always be black
|
2012-05-14 21:24:56 +08:00
|
|
|
black = Gdk.Color(0, 0, 0)
|
2013-04-22 00:10:14 +08:00
|
|
|
self.widget("console-gfx-viewport").modify_bg(Gtk.StateType.NORMAL,
|
2011-07-15 01:13:13 +08:00
|
|
|
black)
|
2009-11-29 07:48:56 +08:00
|
|
|
|
2013-09-02 05:40:38 +08:00
|
|
|
self.serial_tabs = []
|
|
|
|
self.last_gfx_page = 0
|
|
|
|
self._init_menus()
|
|
|
|
|
2012-02-02 06:26:46 +08:00
|
|
|
# Signals are added by vmmDetails. Don't use connect_signals here
|
2009-10-31 01:25:27 +08:00
|
|
|
# or it changes will be overwritten
|
2013-04-18 05:39:25 +08:00
|
|
|
|
|
|
|
self.refresh_scaling()
|
2011-04-13 22:45:10 +08:00
|
|
|
self.add_gconf_handle(
|
|
|
|
self.vm.on_console_scaling_changed(self.refresh_scaling))
|
2009-10-31 01:25:27 +08:00
|
|
|
|
2013-04-22 00:10:14 +08:00
|
|
|
scroll = self.widget("console-gfx-scroll")
|
2010-12-22 09:13:11 +08:00
|
|
|
scroll.connect("size-allocate", self.scroll_size_allocate)
|
2011-04-13 22:45:10 +08:00
|
|
|
self.add_gconf_handle(
|
|
|
|
self.config.on_console_accels_changed(self.set_enable_accel))
|
2012-01-30 10:51:20 +08:00
|
|
|
self.add_gconf_handle(
|
|
|
|
self.config.on_keys_combination_changed(self.grab_keys_changed))
|
2009-10-31 01:25:27 +08:00
|
|
|
|
2011-06-02 00:22:05 +08:00
|
|
|
self.page_changed()
|
|
|
|
|
2009-10-31 01:25:27 +08:00
|
|
|
def is_visible(self):
|
2013-06-18 11:29:20 +08:00
|
|
|
if self.topwin:
|
|
|
|
return self.topwin.get_visible()
|
|
|
|
else:
|
|
|
|
return False
|
2009-10-31 01:25:27 +08:00
|
|
|
|
2011-07-24 09:16:54 +08:00
|
|
|
def _cleanup(self):
|
2011-04-12 06:35:21 +08:00
|
|
|
self.vm = None
|
2011-05-19 05:36:08 +08:00
|
|
|
|
|
|
|
if self.viewer:
|
|
|
|
self.viewer.cleanup()
|
2011-04-13 22:45:10 +08:00
|
|
|
self.viewer = None
|
2011-04-12 06:35:21 +08:00
|
|
|
|
2011-05-19 06:54:27 +08:00
|
|
|
self.keycombo_menu.destroy()
|
|
|
|
self.keycombo_menu = None
|
2011-05-19 05:22:07 +08:00
|
|
|
self.fs_drawer.destroy()
|
|
|
|
self.fs_drawer = None
|
2011-10-29 00:20:11 +08:00
|
|
|
self.fs_toolbar.destroy()
|
|
|
|
self.fs_toolbar = None
|
2011-05-19 05:22:07 +08:00
|
|
|
|
2013-09-02 05:40:38 +08:00
|
|
|
for serial in self.serial_tabs:
|
|
|
|
serial.cleanup()
|
|
|
|
self.serial_tabs = []
|
|
|
|
|
|
|
|
|
2009-10-31 01:25:27 +08:00
|
|
|
##########################
|
|
|
|
# Initialization helpers #
|
|
|
|
##########################
|
|
|
|
|
2011-05-19 05:22:07 +08:00
|
|
|
def init_fs_toolbar(self):
|
2013-04-22 00:10:14 +08:00
|
|
|
scroll = self.widget("console-gfx-scroll")
|
2011-07-15 01:13:13 +08:00
|
|
|
pages = self.widget("console-pages")
|
2011-05-19 05:22:07 +08:00
|
|
|
pages.remove(scroll)
|
|
|
|
|
2012-05-14 21:24:56 +08:00
|
|
|
self.fs_toolbar = Gtk.Toolbar()
|
2011-05-19 05:22:07 +08:00
|
|
|
self.fs_toolbar.set_show_arrow(False)
|
|
|
|
self.fs_toolbar.set_no_show_all(True)
|
2012-05-14 21:24:56 +08:00
|
|
|
self.fs_toolbar.set_style(Gtk.ToolbarStyle.BOTH_HORIZ)
|
2011-05-19 05:22:07 +08:00
|
|
|
|
|
|
|
# Exit fullscreen button
|
2012-05-14 21:24:56 +08:00
|
|
|
button = Gtk.ToolButton.new_from_stock(Gtk.STOCK_LEAVE_FULLSCREEN)
|
|
|
|
button.set_tooltip_text(_("Leave fullscreen"))
|
2011-05-19 05:22:07 +08:00
|
|
|
button.show()
|
|
|
|
self.fs_toolbar.add(button)
|
|
|
|
button.connect("clicked", self.leave_fullscreen)
|
|
|
|
|
2011-05-19 06:54:27 +08:00
|
|
|
def keycombo_menu_clicked(src):
|
|
|
|
ignore = src
|
|
|
|
def menu_location(menu, toolbar):
|
|
|
|
ignore = menu
|
2013-04-16 00:35:45 +08:00
|
|
|
ignore, x, y = toolbar.get_window().get_origin()
|
|
|
|
height = toolbar.get_window().get_height()
|
2011-05-19 06:54:27 +08:00
|
|
|
|
|
|
|
return x, y + height, True
|
|
|
|
|
2013-04-15 01:16:02 +08:00
|
|
|
self.keycombo_menu.popup(None, None, menu_location,
|
|
|
|
self.fs_toolbar, 0,
|
|
|
|
Gtk.get_current_event_time())
|
2011-05-19 06:54:27 +08:00
|
|
|
|
2012-05-14 21:24:56 +08:00
|
|
|
self.send_key_button = Gtk.ToolButton()
|
2012-01-30 10:56:18 +08:00
|
|
|
self.send_key_button.set_icon_name(
|
|
|
|
"preferences-desktop-keyboard-shortcuts")
|
2012-05-14 21:24:56 +08:00
|
|
|
self.send_key_button.set_tooltip_text(_("Send key combination"))
|
2012-01-30 10:56:18 +08:00
|
|
|
self.send_key_button.show_all()
|
|
|
|
self.send_key_button.connect("clicked", keycombo_menu_clicked)
|
|
|
|
self.fs_toolbar.add(self.send_key_button)
|
2011-05-19 06:54:27 +08:00
|
|
|
|
2011-05-19 05:22:07 +08:00
|
|
|
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)
|
2013-09-26 01:03:03 +08:00
|
|
|
self.fs_drawer.period = 20
|
|
|
|
self.fs_drawer.step = .1
|
|
|
|
|
2011-05-19 05:22:07 +08:00
|
|
|
self.fs_drawer.show_all()
|
|
|
|
|
|
|
|
pages.add(self.fs_drawer)
|
|
|
|
|
2013-09-02 05:40:38 +08:00
|
|
|
def _init_menus(self):
|
|
|
|
# Serial list menu
|
|
|
|
smenu = Gtk.Menu()
|
|
|
|
smenu.connect("show", self.populate_serial_menu)
|
|
|
|
self.widget("details-menu-view-serial-list").set_submenu(smenu)
|
|
|
|
|
2011-03-24 04:56:12 +08:00
|
|
|
def change_title(self, ignore1=None):
|
|
|
|
title = self.vm.get_name() + " " + _("Virtual Machine")
|
|
|
|
|
|
|
|
if self.pointer_is_grabbed and self.viewer:
|
|
|
|
keystr = self.viewer.get_grab_keys()
|
|
|
|
keymsg = _("Press %s to release pointer.") % keystr
|
|
|
|
|
|
|
|
title = keymsg + " " + title
|
|
|
|
|
|
|
|
self.topwin.set_title(title)
|
|
|
|
|
2013-12-19 05:27:33 +08:00
|
|
|
def someone_has_focus(self):
|
|
|
|
if (self.viewer and
|
|
|
|
self.viewer.display and
|
|
|
|
self.viewer.display.get_property("has-focus") and
|
|
|
|
self.viewer_connected):
|
|
|
|
return True
|
|
|
|
|
|
|
|
for serial in self.serial_tabs:
|
|
|
|
if (serial.terminal and
|
|
|
|
serial.terminal.get_property("has-focus")):
|
|
|
|
return True
|
|
|
|
|
2010-12-22 09:13:11 +08:00
|
|
|
def viewer_focus_changed(self, ignore1=None, ignore2=None):
|
2010-12-03 02:41:22 +08:00
|
|
|
force_accel = self.config.get_console_accels()
|
|
|
|
|
|
|
|
if force_accel:
|
|
|
|
self._enable_modifiers()
|
2013-12-19 05:27:33 +08:00
|
|
|
elif self.someone_has_focus():
|
2010-12-03 02:41:22 +08:00
|
|
|
self._disable_modifiers()
|
|
|
|
else:
|
|
|
|
self._enable_modifiers()
|
2010-04-22 00:59:25 +08:00
|
|
|
|
2010-12-22 09:13:11 +08:00
|
|
|
def pointer_grabbed(self, src_ignore):
|
2011-03-24 04:56:12 +08:00
|
|
|
self.pointer_is_grabbed = True
|
|
|
|
self.change_title()
|
2009-10-31 01:25:27 +08:00
|
|
|
|
2010-12-10 00:22:35 +08:00
|
|
|
def pointer_ungrabbed(self, src_ignore):
|
2011-03-24 04:56:12 +08:00
|
|
|
self.pointer_is_grabbed = False
|
|
|
|
self.change_title()
|
2009-10-31 01:25:27 +08:00
|
|
|
|
2010-04-22 00:59:25 +08:00
|
|
|
def _disable_modifiers(self):
|
2009-11-02 05:36:44 +08:00
|
|
|
if self.gtk_settings_accel is not None:
|
|
|
|
return
|
|
|
|
|
2009-10-31 01:25:27 +08:00
|
|
|
for g in self.accel_groups:
|
|
|
|
self.topwin.remove_accel_group(g)
|
2009-10-31 02:36:17 +08:00
|
|
|
|
2012-05-14 21:24:56 +08:00
|
|
|
settings = Gtk.Settings.get_default()
|
2009-10-31 01:25:27 +08:00
|
|
|
self.gtk_settings_accel = settings.get_property('gtk-menu-bar-accel')
|
|
|
|
settings.set_property('gtk-menu-bar-accel', None)
|
|
|
|
|
2009-10-31 02:36:17 +08:00
|
|
|
if has_property(settings, "gtk-enable-mnemonics"):
|
2010-12-03 02:41:22 +08:00
|
|
|
self.gtk_settings_mnemonic = settings.get_property(
|
|
|
|
"gtk-enable-mnemonics")
|
2009-10-31 02:36:17 +08:00
|
|
|
settings.set_property("gtk-enable-mnemonics", False)
|
|
|
|
|
2010-04-22 00:59:25 +08:00
|
|
|
def _enable_modifiers(self):
|
2009-10-31 01:25:27 +08:00
|
|
|
if self.gtk_settings_accel is None:
|
|
|
|
return
|
2009-11-02 05:36:44 +08:00
|
|
|
|
2012-05-14 21:24:56 +08:00
|
|
|
settings = Gtk.Settings.get_default()
|
2009-10-31 01:25:27 +08:00
|
|
|
settings.set_property('gtk-menu-bar-accel', self.gtk_settings_accel)
|
|
|
|
self.gtk_settings_accel = None
|
2009-10-31 02:36:17 +08:00
|
|
|
|
|
|
|
if self.gtk_settings_mnemonic is not None:
|
|
|
|
settings.set_property("gtk-enable-mnemonics",
|
|
|
|
self.gtk_settings_mnemonic)
|
|
|
|
|
2009-10-31 01:25:27 +08:00
|
|
|
for g in self.accel_groups:
|
|
|
|
self.topwin.add_accel_group(g)
|
|
|
|
|
2013-04-18 05:39:25 +08:00
|
|
|
def grab_keys_changed(self):
|
2012-01-30 10:51:20 +08:00
|
|
|
self.viewer.set_grab_keys()
|
|
|
|
|
2013-04-18 05:39:25 +08:00
|
|
|
def set_enable_accel(self):
|
2010-12-03 02:41:22 +08:00
|
|
|
# Make sure modifiers are up to date
|
2010-12-22 09:13:11 +08:00
|
|
|
self.viewer_focus_changed()
|
2009-10-31 01:25:27 +08:00
|
|
|
|
2013-04-18 05:39:25 +08:00
|
|
|
def refresh_scaling(self):
|
2009-10-31 01:25:27 +08:00
|
|
|
self.scale_type = self.vm.get_console_scaling()
|
2011-07-15 01:13:13 +08:00
|
|
|
self.widget("details-menu-view-scale-always").set_active(
|
2010-11-30 03:06:43 +08:00
|
|
|
self.scale_type == self.config.CONSOLE_SCALE_ALWAYS)
|
2011-07-15 01:13:13 +08:00
|
|
|
self.widget("details-menu-view-scale-never").set_active(
|
2010-11-30 03:06:43 +08:00
|
|
|
self.scale_type == self.config.CONSOLE_SCALE_NEVER)
|
2011-07-15 01:13:13 +08:00
|
|
|
self.widget("details-menu-view-scale-fullscreen").set_active(
|
2010-11-30 03:06:43 +08:00
|
|
|
self.scale_type == self.config.CONSOLE_SCALE_FULLSCREEN)
|
2009-10-31 01:25:27 +08:00
|
|
|
|
|
|
|
self.update_scaling()
|
|
|
|
|
|
|
|
def set_scale_type(self, src):
|
|
|
|
if not src.get_active():
|
|
|
|
return
|
|
|
|
|
2011-07-15 01:13:13 +08:00
|
|
|
if src == self.widget("details-menu-view-scale-always"):
|
2009-10-31 01:25:27 +08:00
|
|
|
self.scale_type = self.config.CONSOLE_SCALE_ALWAYS
|
2011-07-15 01:13:13 +08:00
|
|
|
elif src == self.widget("details-menu-view-scale-fullscreen"):
|
2009-10-31 01:25:27 +08:00
|
|
|
self.scale_type = self.config.CONSOLE_SCALE_FULLSCREEN
|
2011-07-15 01:13:13 +08:00
|
|
|
elif src == self.widget("details-menu-view-scale-never"):
|
2009-10-31 01:25:27 +08:00
|
|
|
self.scale_type = self.config.CONSOLE_SCALE_NEVER
|
|
|
|
|
|
|
|
self.vm.set_console_scaling(self.scale_type)
|
|
|
|
self.update_scaling()
|
|
|
|
|
|
|
|
def update_scaling(self):
|
2010-12-22 09:13:11 +08:00
|
|
|
if not self.viewer:
|
|
|
|
return
|
|
|
|
|
|
|
|
curscale = self.viewer.get_scaling()
|
2011-07-15 01:13:13 +08:00
|
|
|
fs = self.widget("control-fullscreen").get_active()
|
2013-04-22 00:10:14 +08:00
|
|
|
vnc_scroll = self.widget("console-gfx-scroll")
|
2009-10-31 01:25:27 +08:00
|
|
|
|
|
|
|
if (self.scale_type == self.config.CONSOLE_SCALE_NEVER
|
2012-11-08 21:15:02 +08:00
|
|
|
and curscale is True):
|
2010-12-22 09:13:11 +08:00
|
|
|
self.viewer.set_scaling(False)
|
2009-10-31 01:25:27 +08:00
|
|
|
elif (self.scale_type == self.config.CONSOLE_SCALE_ALWAYS
|
2012-11-08 21:15:02 +08:00
|
|
|
and curscale is False):
|
2010-12-22 09:13:11 +08:00
|
|
|
self.viewer.set_scaling(True)
|
2009-10-31 01:25:27 +08:00
|
|
|
elif (self.scale_type == self.config.CONSOLE_SCALE_FULLSCREEN
|
|
|
|
and curscale != fs):
|
2010-12-22 09:13:11 +08:00
|
|
|
self.viewer.set_scaling(fs)
|
2009-10-31 01:25:27 +08:00
|
|
|
|
2009-11-29 07:48:56 +08:00
|
|
|
# Refresh viewer size
|
|
|
|
vnc_scroll.queue_resize()
|
|
|
|
|
2009-10-31 01:25:27 +08:00
|
|
|
def auth_login(self, ignore):
|
|
|
|
self.set_credentials()
|
|
|
|
self.activate_viewer_page()
|
|
|
|
|
|
|
|
def toggle_fullscreen(self, src):
|
2009-11-23 05:39:38 +08:00
|
|
|
do_fullscreen = src.get_active()
|
2011-05-19 05:22:07 +08:00
|
|
|
self._change_fullscreen(do_fullscreen)
|
|
|
|
|
2011-06-01 22:57:57 +08:00
|
|
|
def leave_fullscreen(self, ignore=None):
|
2011-05-19 05:22:07 +08:00
|
|
|
self._change_fullscreen(False)
|
2009-11-23 05:39:38 +08:00
|
|
|
|
2011-05-19 05:22:07 +08:00
|
|
|
def _change_fullscreen(self, do_fullscreen):
|
2011-07-15 01:13:13 +08:00
|
|
|
self.widget("control-fullscreen").set_active(do_fullscreen)
|
2009-11-23 05:39:38 +08:00
|
|
|
|
|
|
|
if do_fullscreen:
|
2009-10-31 01:25:27 +08:00
|
|
|
self.topwin.fullscreen()
|
2011-05-19 05:22:07 +08:00
|
|
|
self.fs_toolbar.show()
|
|
|
|
self.fs_drawer.set_active(True)
|
2011-07-15 01:13:13 +08:00
|
|
|
self.widget("toolbar-box").hide()
|
|
|
|
self.widget("details-menubar").hide()
|
2009-10-31 01:25:27 +08:00
|
|
|
else:
|
2011-05-19 05:22:07 +08:00
|
|
|
self.fs_toolbar.hide()
|
|
|
|
self.fs_drawer.set_active(False)
|
2009-10-31 01:25:27 +08:00
|
|
|
self.topwin.unfullscreen()
|
|
|
|
|
2011-07-15 01:13:13 +08:00
|
|
|
if self.widget("details-menu-view-toolbar").get_active():
|
|
|
|
self.widget("toolbar-box").show()
|
|
|
|
self.widget("details-menubar").show()
|
2009-10-31 01:25:27 +08:00
|
|
|
|
|
|
|
self.update_scaling()
|
|
|
|
|
2010-12-10 00:22:35 +08:00
|
|
|
def size_to_vm(self, src_ignore):
|
2009-11-29 09:07:01 +08:00
|
|
|
# Resize the console to best fit the VM resolution
|
2011-04-26 22:51:19 +08:00
|
|
|
if not self.viewer:
|
|
|
|
return
|
|
|
|
if not self.viewer.get_desktop_resolution():
|
2009-11-29 09:07:01 +08:00
|
|
|
return
|
|
|
|
|
2011-04-26 22:51:19 +08:00
|
|
|
w, h = self.viewer.get_desktop_resolution()
|
2009-11-29 09:07:01 +08:00
|
|
|
self.topwin.unmaximize()
|
|
|
|
self.topwin.resize(1, 1)
|
2009-12-15 01:37:08 +08:00
|
|
|
self.queue_scroll_resize_helper(w, h)
|
2009-11-29 09:07:01 +08:00
|
|
|
|
2011-05-19 06:54:27 +08:00
|
|
|
def send_key(self, src, keys):
|
|
|
|
ignore = src
|
2009-10-31 01:25:27 +08:00
|
|
|
|
2012-11-08 21:15:02 +08:00
|
|
|
if keys is not None:
|
2010-12-22 09:13:11 +08:00
|
|
|
self.viewer.send_keys(keys)
|
2009-10-31 01:25:27 +08:00
|
|
|
|
|
|
|
|
|
|
|
##########################
|
|
|
|
# State tracking methods #
|
|
|
|
##########################
|
|
|
|
|
|
|
|
def view_vm_status(self):
|
2013-06-18 11:29:20 +08:00
|
|
|
if not self.vm:
|
|
|
|
# window has been closed and no pages to update are available.
|
|
|
|
return
|
2009-10-31 01:25:27 +08:00
|
|
|
status = self.vm.status()
|
|
|
|
if status == libvirt.VIR_DOMAIN_SHUTOFF:
|
|
|
|
self.activate_unavailable_page(_("Guest not running"))
|
|
|
|
else:
|
|
|
|
if status == libvirt.VIR_DOMAIN_CRASHED:
|
|
|
|
self.activate_unavailable_page(_("Guest has crashed"))
|
|
|
|
|
2010-12-22 09:13:11 +08:00
|
|
|
def close_viewer(self):
|
2011-01-15 00:34:51 +08:00
|
|
|
if self.viewer is None:
|
|
|
|
return
|
|
|
|
|
2013-09-07 07:55:46 +08:00
|
|
|
v = self.viewer
|
2012-02-02 06:26:46 +08:00
|
|
|
w = v.display
|
2013-09-07 07:55:46 +08:00
|
|
|
self.viewer = None
|
2011-01-15 00:34:51 +08:00
|
|
|
|
2013-06-18 11:29:20 +08:00
|
|
|
viewport = self.widget("console-gfx-viewport")
|
2011-01-15 00:34:51 +08:00
|
|
|
if w and w in viewport.get_children():
|
|
|
|
viewport.remove(w)
|
|
|
|
|
|
|
|
v.close()
|
|
|
|
self.viewer_connected = False
|
2011-06-01 22:57:57 +08:00
|
|
|
self.leave_fullscreen()
|
2010-12-22 09:13:11 +08:00
|
|
|
|
2013-11-10 06:54:47 +08:00
|
|
|
for serial in self.serial_tabs:
|
|
|
|
serial.close()
|
|
|
|
|
2010-12-10 00:22:35 +08:00
|
|
|
def update_widget_states(self, vm, status_ignore):
|
2009-10-31 01:25:27 +08:00
|
|
|
runable = vm.is_runable()
|
2011-07-15 01:13:13 +08:00
|
|
|
pages = self.widget("console-pages")
|
2009-10-31 01:25:27 +08:00
|
|
|
page = pages.get_current_page()
|
|
|
|
|
|
|
|
if runable:
|
2013-09-02 05:40:38 +08:00
|
|
|
if page != CONSOLE_PAGE_UNAVAILABLE:
|
|
|
|
pages.set_current_page(CONSOLE_PAGE_UNAVAILABLE)
|
2009-10-31 01:25:27 +08:00
|
|
|
|
|
|
|
self.view_vm_status()
|
|
|
|
|
2013-09-02 05:40:38 +08:00
|
|
|
elif page in [CONSOLE_PAGE_UNAVAILABLE, CONSOLE_PAGE_VIEWER]:
|
2010-12-22 09:13:11 +08:00
|
|
|
if self.viewer and self.viewer.is_open():
|
2009-10-31 01:25:27 +08:00
|
|
|
self.activate_viewer_page()
|
|
|
|
else:
|
2010-12-22 09:13:11 +08:00
|
|
|
self.viewerRetriesScheduled = 0
|
|
|
|
self.viewerRetryDelay = 125
|
2009-10-31 01:25:27 +08:00
|
|
|
self.try_login()
|
|
|
|
|
|
|
|
return
|
|
|
|
|
2013-09-02 05:40:38 +08:00
|
|
|
|
2009-10-31 01:25:27 +08:00
|
|
|
###################
|
|
|
|
# Page Navigation #
|
|
|
|
###################
|
|
|
|
|
|
|
|
def activate_unavailable_page(self, msg):
|
2011-06-21 07:09:31 +08:00
|
|
|
"""
|
|
|
|
This function is passed to serialcon.py at least, so change
|
|
|
|
with care
|
|
|
|
"""
|
2011-01-15 03:17:35 +08:00
|
|
|
self.close_viewer()
|
2013-09-02 05:40:38 +08:00
|
|
|
self.widget("console-pages").set_current_page(CONSOLE_PAGE_UNAVAILABLE)
|
2011-07-15 01:13:13 +08:00
|
|
|
self.widget("details-menu-vm-screenshot").set_sensitive(False)
|
2013-07-02 02:33:59 +08:00
|
|
|
self.widget("details-menu-usb-redirection").set_sensitive(False)
|
2011-07-15 01:13:13 +08:00
|
|
|
self.widget("console-unavailable").set_label("<b>" + msg + "</b>")
|
2009-10-31 01:25:27 +08:00
|
|
|
|
|
|
|
def activate_auth_page(self, withPassword=True, withUsername=False):
|
|
|
|
(pw, username) = self.config.get_console_password(self.vm)
|
2011-07-15 01:13:13 +08:00
|
|
|
self.widget("details-menu-vm-screenshot").set_sensitive(False)
|
2013-07-02 02:33:59 +08:00
|
|
|
self.widget("details-menu-usb-redirection").set_sensitive(False)
|
2009-10-31 01:25:27 +08:00
|
|
|
|
2013-09-02 05:40:38 +08:00
|
|
|
self.widget("console-auth-password").set_visible(withPassword)
|
|
|
|
self.widget("label-auth-password").set_visible(withPassword)
|
|
|
|
|
|
|
|
self.widget("console-auth-username").set_visible(withUsername)
|
|
|
|
self.widget("label-auth-username").set_visible(withUsername)
|
2009-10-31 01:25:27 +08:00
|
|
|
|
|
|
|
if withUsername:
|
2013-09-02 05:40:38 +08:00
|
|
|
self.widget("console-auth-username").grab_focus()
|
2009-10-31 01:25:27 +08:00
|
|
|
else:
|
2013-09-02 05:40:38 +08:00
|
|
|
self.widget("console-auth-password").grab_focus()
|
2009-10-31 01:25:27 +08:00
|
|
|
|
2011-07-15 01:13:13 +08:00
|
|
|
self.widget("console-auth-username").set_text(username)
|
|
|
|
self.widget("console-auth-password").set_text(pw)
|
2009-10-31 01:25:27 +08:00
|
|
|
|
2013-09-02 05:40:38 +08:00
|
|
|
self.widget("console-auth-remember").set_sensitive(
|
|
|
|
bool(self.config.has_keyring()))
|
2009-10-31 01:25:27 +08:00
|
|
|
if self.config.has_keyring():
|
2013-09-02 05:40:38 +08:00
|
|
|
self.widget("console-auth-remember").set_active(bool(pw and
|
|
|
|
username))
|
|
|
|
|
|
|
|
self.widget("console-pages").set_current_page(
|
|
|
|
CONSOLE_PAGE_AUTHENTICATE)
|
2009-10-31 01:25:27 +08:00
|
|
|
|
|
|
|
|
|
|
|
def activate_viewer_page(self):
|
2013-09-02 05:40:38 +08:00
|
|
|
self.widget("console-pages").set_current_page(CONSOLE_PAGE_VIEWER)
|
2011-07-15 01:13:13 +08:00
|
|
|
self.widget("details-menu-vm-screenshot").set_sensitive(True)
|
2012-02-02 06:26:46 +08:00
|
|
|
if self.viewer and self.viewer.display:
|
|
|
|
self.viewer.display.grab_focus()
|
2009-10-31 01:25:27 +08:00
|
|
|
|
2013-09-02 05:40:38 +08:00
|
|
|
if (self.viewer.has_usb_redirection() and
|
|
|
|
self.vm.has_spicevmc_type_redirdev()):
|
2013-07-02 02:33:59 +08:00
|
|
|
self.widget("details-menu-usb-redirection").set_sensitive(True)
|
|
|
|
return
|
|
|
|
|
2011-06-02 00:22:05 +08:00
|
|
|
def page_changed(self, ignore1=None, ignore2=None, ignore3=None):
|
2013-09-02 05:40:38 +08:00
|
|
|
pagenum = self.widget("console-pages").get_current_page()
|
|
|
|
if pagenum < CONSOLE_PAGE_OFFSET:
|
|
|
|
self.last_gfx_page = pagenum
|
2011-06-02 00:22:05 +08:00
|
|
|
self.set_allow_fullscreen()
|
|
|
|
|
|
|
|
def set_allow_fullscreen(self):
|
2011-07-15 01:13:13 +08:00
|
|
|
cpage = self.widget("console-pages").get_current_page()
|
|
|
|
dpage = self.widget("details-pages").get_current_page()
|
2011-06-02 00:22:05 +08:00
|
|
|
|
2013-09-02 05:40:38 +08:00
|
|
|
allow_fullscreen = (dpage == DETAILS_PAGE_CONSOLE and
|
|
|
|
cpage == CONSOLE_PAGE_VIEWER and
|
2011-06-02 00:22:05 +08:00
|
|
|
self.viewer_connected)
|
|
|
|
|
2011-07-15 01:13:13 +08:00
|
|
|
self.widget("control-fullscreen").set_sensitive(allow_fullscreen)
|
|
|
|
self.widget("details-menu-view-fullscreen").set_sensitive(allow_fullscreen)
|
2011-06-02 00:22:05 +08:00
|
|
|
|
2010-12-22 09:13:11 +08:00
|
|
|
def disconnected(self):
|
2010-02-11 23:43:44 +08:00
|
|
|
errout = ""
|
2010-12-22 05:34:36 +08:00
|
|
|
if self.tunnels is not None:
|
|
|
|
errout = self.tunnels.get_err_output()
|
|
|
|
self.tunnels.close_all()
|
|
|
|
self.tunnels = None
|
2010-02-11 23:43:44 +08:00
|
|
|
|
2013-09-07 07:55:46 +08:00
|
|
|
self.widget("console-pages").set_current_page(CONSOLE_PAGE_UNAVAILABLE)
|
2010-12-22 09:13:11 +08:00
|
|
|
self.close_viewer()
|
|
|
|
logging.debug("Viewer disconnected")
|
2010-12-03 02:41:22 +08:00
|
|
|
|
|
|
|
# Make sure modifiers are set correctly
|
2010-12-22 09:13:11 +08:00
|
|
|
self.viewer_focus_changed()
|
2010-02-11 23:43:44 +08:00
|
|
|
|
2013-09-07 07:55:46 +08:00
|
|
|
if self.guest_not_avail():
|
2010-02-11 23:43:44 +08:00
|
|
|
# Exit was probably for legitimate reasons
|
2009-10-31 01:25:27 +08:00
|
|
|
self.view_vm_status()
|
|
|
|
return
|
|
|
|
|
2010-12-22 09:13:11 +08:00
|
|
|
error = _("Error: viewer connection to hypervisor host got refused "
|
2010-02-11 23:43:44 +08:00
|
|
|
"or disconnected!")
|
|
|
|
if errout:
|
2012-01-17 11:04:40 +08:00
|
|
|
logging.debug("Error output from closed console: %s", errout)
|
2010-02-11 23:43:44 +08:00
|
|
|
error += "\n\nError: %s" % errout
|
2009-10-31 01:25:27 +08:00
|
|
|
|
2010-02-11 23:43:44 +08:00
|
|
|
self.activate_unavailable_page(error)
|
2009-10-31 01:25:27 +08:00
|
|
|
|
2011-06-02 00:22:05 +08:00
|
|
|
def _set_viewer_connected(self, val):
|
|
|
|
self._viewer_connected = val
|
|
|
|
self.set_allow_fullscreen()
|
|
|
|
def _get_viewer_connected(self):
|
|
|
|
return self._viewer_connected
|
|
|
|
viewer_connected = property(_get_viewer_connected, _set_viewer_connected)
|
|
|
|
|
2010-12-22 09:13:11 +08:00
|
|
|
def connected(self):
|
|
|
|
self.viewer_connected = True
|
|
|
|
logging.debug("Viewer connected")
|
2009-10-31 01:25:27 +08:00
|
|
|
self.activate_viewer_page()
|
|
|
|
|
|
|
|
# Had a succesfull connect, so reset counters now
|
2010-12-22 09:13:11 +08:00
|
|
|
self.viewerRetriesScheduled = 0
|
|
|
|
self.viewerRetryDelay = 125
|
2009-10-31 01:25:27 +08:00
|
|
|
|
2010-12-03 02:41:22 +08:00
|
|
|
# Make sure modifiers are set correctly
|
2010-12-22 09:13:11 +08:00
|
|
|
self.viewer_focus_changed()
|
2010-12-03 02:41:22 +08:00
|
|
|
|
2009-10-31 01:25:27 +08:00
|
|
|
def schedule_retry(self):
|
2010-12-22 09:13:11 +08:00
|
|
|
if self.viewerRetriesScheduled >= 10:
|
2009-10-31 01:25:27 +08:00
|
|
|
logging.error("Too many connection failures, not retrying again")
|
|
|
|
return
|
|
|
|
|
2012-02-11 03:07:51 +08:00
|
|
|
self.timeout_add(self.viewerRetryDelay, self.try_login)
|
2009-10-31 01:25:27 +08:00
|
|
|
|
2010-12-22 09:13:11 +08:00
|
|
|
if self.viewerRetryDelay < 2000:
|
|
|
|
self.viewerRetryDelay = self.viewerRetryDelay * 2
|
2009-10-31 01:25:27 +08:00
|
|
|
|
2010-02-11 23:43:44 +08:00
|
|
|
def skip_connect_attempt(self):
|
2011-01-15 03:17:35 +08:00
|
|
|
return (self.viewer or
|
2010-02-11 23:43:44 +08:00
|
|
|
not self.is_visible())
|
|
|
|
|
|
|
|
def guest_not_avail(self):
|
2011-04-11 05:41:56 +08:00
|
|
|
return (self.vm.is_shutoff() or self.vm.is_crashed())
|
2010-02-11 23:43:44 +08:00
|
|
|
|
2010-12-10 00:22:35 +08:00
|
|
|
def try_login(self, src_ignore=None):
|
2011-01-15 03:17:35 +08:00
|
|
|
if self.viewer_connecting:
|
|
|
|
return
|
|
|
|
|
|
|
|
try:
|
|
|
|
self.viewer_connecting = True
|
|
|
|
self._try_login()
|
|
|
|
finally:
|
|
|
|
self.viewer_connecting = False
|
|
|
|
|
|
|
|
def _try_login(self):
|
2010-02-11 23:43:44 +08:00
|
|
|
if self.skip_connect_attempt():
|
|
|
|
# Don't try and login for these cases
|
2009-10-31 01:25:27 +08:00
|
|
|
return
|
|
|
|
|
2010-02-11 23:43:44 +08:00
|
|
|
if self.guest_not_avail():
|
|
|
|
# Guest isn't running, schedule another try
|
2009-10-31 01:25:27 +08:00
|
|
|
self.activate_unavailable_page(_("Guest not running"))
|
|
|
|
self.schedule_retry()
|
|
|
|
return
|
|
|
|
|
2012-03-14 03:27:23 +08:00
|
|
|
ginfo = None
|
2010-01-11 23:30:40 +08:00
|
|
|
try:
|
2012-03-14 03:27:23 +08:00
|
|
|
gdevs = self.vm.get_graphics_devices()
|
|
|
|
gdev = gdevs and gdevs[0] or None
|
|
|
|
if gdev:
|
|
|
|
ginfo = ConnectionInfo(self.vm.conn, gdev)
|
2010-01-11 23:30:40 +08:00
|
|
|
except Exception, e:
|
|
|
|
# We can fail here if VM is destroyed: xen is a bit racy
|
|
|
|
# and can't handle domain lookups that soon after
|
2012-01-17 11:04:40 +08:00
|
|
|
logging.exception("Getting graphics console failed: %s", str(e))
|
2010-01-11 23:30:40 +08:00
|
|
|
return
|
|
|
|
|
2012-03-14 03:27:23 +08:00
|
|
|
if ginfo is None:
|
2011-01-08 03:59:31 +08:00
|
|
|
logging.debug("No graphics configured for guest")
|
2010-02-11 23:43:44 +08:00
|
|
|
self.activate_unavailable_page(
|
|
|
|
_("Graphical console not configured for guest"))
|
2009-10-31 01:25:27 +08:00
|
|
|
return
|
|
|
|
|
2012-03-14 03:27:23 +08:00
|
|
|
if ginfo.gtype not in self.config.embeddable_graphics():
|
2011-04-10 11:03:32 +08:00
|
|
|
logging.debug("Don't know how to show graphics type '%s' "
|
2012-03-14 03:27:23 +08:00
|
|
|
"disabling console page", ginfo.gtype)
|
2011-01-08 03:59:31 +08:00
|
|
|
|
|
|
|
msg = (_("Cannot display graphical console type '%s'")
|
2012-03-14 03:27:23 +08:00
|
|
|
% ginfo.gtype)
|
2011-01-08 03:59:31 +08:00
|
|
|
|
|
|
|
self.activate_unavailable_page(msg)
|
2009-10-31 01:25:27 +08:00
|
|
|
return
|
|
|
|
|
2013-06-30 23:44:58 +08:00
|
|
|
if ginfo.is_bad_localhost():
|
|
|
|
self.activate_unavailable_page(
|
|
|
|
_("Guest is on a remote host with transport '%s'\n"
|
|
|
|
"but is only configured to listen on 127.0.0.1.\n"
|
|
|
|
"Connect using 'ssh' transport or change the\n"
|
|
|
|
"guest's listen address." % ginfo.transport))
|
|
|
|
return
|
|
|
|
|
2013-10-07 03:30:01 +08:00
|
|
|
if not ginfo.console_active():
|
2010-02-11 23:43:44 +08:00
|
|
|
self.activate_unavailable_page(
|
|
|
|
_("Graphical console is not yet active for guest"))
|
2009-10-31 01:25:27 +08:00
|
|
|
self.schedule_retry()
|
|
|
|
return
|
|
|
|
|
2010-02-11 23:43:44 +08:00
|
|
|
self.activate_unavailable_page(
|
|
|
|
_("Connecting to graphical console for guest"))
|
|
|
|
|
2012-03-14 03:27:23 +08:00
|
|
|
logging.debug("Starting connect process for %s", ginfo.logstring())
|
2009-10-31 01:25:27 +08:00
|
|
|
try:
|
2012-03-14 03:27:23 +08:00
|
|
|
if ginfo.gtype == "vnc":
|
2011-05-19 05:36:08 +08:00
|
|
|
self.viewer = VNCViewer(self)
|
2013-04-22 00:10:14 +08:00
|
|
|
self.widget("console-gfx-viewport").add(self.viewer.display)
|
2011-01-15 03:17:35 +08:00
|
|
|
self.viewer.init_widget()
|
2012-03-14 03:27:23 +08:00
|
|
|
elif ginfo.gtype == "spice":
|
2011-05-19 05:36:08 +08:00
|
|
|
self.viewer = SpiceViewer(self)
|
2011-01-15 03:17:35 +08:00
|
|
|
|
|
|
|
self.set_enable_accel()
|
|
|
|
|
2012-03-14 03:27:23 +08:00
|
|
|
if ginfo.need_tunnel():
|
|
|
|
self.tunnels = Tunnels(ginfo)
|
2013-09-07 07:36:09 +08:00
|
|
|
self.viewer.open_ginfo(ginfo)
|
2011-01-14 00:26:38 +08:00
|
|
|
except Exception, e:
|
|
|
|
logging.exception("Error connection to graphical console")
|
|
|
|
self.activate_unavailable_page(
|
|
|
|
_("Error connecting to graphical console") + ":\n%s" % e)
|
2009-10-31 01:25:27 +08:00
|
|
|
|
2010-12-10 00:22:35 +08:00
|
|
|
def set_credentials(self, src_ignore=None):
|
2011-07-15 01:13:13 +08:00
|
|
|
passwd = self.widget("console-auth-password")
|
2012-05-14 21:24:56 +08:00
|
|
|
if passwd.get_visible():
|
2010-12-22 09:13:11 +08:00
|
|
|
self.viewer.set_credential_password(passwd.get_text())
|
2011-07-15 01:13:13 +08:00
|
|
|
username = self.widget("console-auth-username")
|
2012-05-14 21:24:56 +08:00
|
|
|
if username.get_visible():
|
2010-12-22 09:13:11 +08:00
|
|
|
self.viewer.set_credential_username(username.get_text())
|
2009-10-31 01:25:27 +08:00
|
|
|
|
2011-07-15 01:13:13 +08:00
|
|
|
if self.widget("console-auth-remember").get_active():
|
2009-10-31 01:25:27 +08:00
|
|
|
self.config.set_console_password(self.vm, passwd.get_text(),
|
|
|
|
username.get_text())
|
|
|
|
|
2009-12-15 01:37:08 +08:00
|
|
|
def queue_scroll_resize_helper(self, w, h):
|
2009-11-29 07:48:56 +08:00
|
|
|
"""
|
|
|
|
Resize the VNC container widget to the requested size. The new size
|
|
|
|
isn't a hard requirment so the user can still shrink the window
|
|
|
|
again, as opposed to set_size_request
|
|
|
|
"""
|
2013-04-22 00:10:14 +08:00
|
|
|
widget = self.widget("console-gfx-scroll")
|
2009-11-29 07:48:56 +08:00
|
|
|
signal_holder = []
|
|
|
|
|
2009-12-15 01:37:08 +08:00
|
|
|
def restore_scroll(src):
|
2010-12-22 09:13:11 +08:00
|
|
|
is_scale = self.viewer.get_scaling()
|
2009-12-15 01:37:08 +08:00
|
|
|
|
|
|
|
if is_scale:
|
2012-05-14 21:24:56 +08:00
|
|
|
w_policy = Gtk.PolicyType.NEVER
|
|
|
|
h_policy = Gtk.PolicyType.NEVER
|
2009-12-15 01:37:08 +08:00
|
|
|
else:
|
2012-05-14 21:24:56 +08:00
|
|
|
w_policy = Gtk.PolicyType.AUTOMATIC
|
|
|
|
h_policy = Gtk.PolicyType.AUTOMATIC
|
2009-12-15 01:37:08 +08:00
|
|
|
|
|
|
|
src.set_policy(w_policy, h_policy)
|
|
|
|
return False
|
|
|
|
|
2009-11-29 07:48:56 +08:00
|
|
|
def unset_cb(src):
|
2009-12-15 01:37:08 +08:00
|
|
|
src.queue_resize_no_redraw()
|
2012-02-11 03:07:51 +08:00
|
|
|
self.idle_add(restore_scroll, src)
|
2009-11-29 07:48:56 +08:00
|
|
|
return False
|
|
|
|
|
|
|
|
def request_cb(src, req):
|
|
|
|
signal_id = signal_holder[0]
|
|
|
|
req.width = w
|
|
|
|
req.height = h
|
|
|
|
|
|
|
|
src.disconnect(signal_id)
|
|
|
|
|
2012-02-11 03:07:51 +08:00
|
|
|
self.idle_add(unset_cb, widget)
|
2009-11-29 07:48:56 +08:00
|
|
|
return False
|
|
|
|
|
2009-12-15 01:37:08 +08:00
|
|
|
# Disable scroll bars while we resize, since resizing to the VM's
|
|
|
|
# dimensions can erroneously show scroll bars when they aren't needed
|
2012-05-14 21:24:56 +08:00
|
|
|
widget.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.NEVER)
|
2009-12-15 01:37:08 +08:00
|
|
|
|
2013-04-15 04:50:25 +08:00
|
|
|
signal_id = widget.connect("size-allocate", request_cb)
|
2009-11-29 07:48:56 +08:00
|
|
|
signal_holder.append(signal_id)
|
|
|
|
|
|
|
|
widget.queue_resize()
|
|
|
|
|
2010-12-10 00:22:35 +08:00
|
|
|
def scroll_size_allocate(self, src_ignore, req):
|
2011-04-26 22:51:19 +08:00
|
|
|
if not self.viewer or not self.viewer.get_desktop_resolution():
|
2009-11-29 07:48:56 +08:00
|
|
|
return
|
|
|
|
|
2013-04-22 00:10:14 +08:00
|
|
|
scroll = self.widget("console-gfx-scroll")
|
2010-12-22 09:13:11 +08:00
|
|
|
is_scale = self.viewer.get_scaling()
|
2009-11-29 07:48:56 +08:00
|
|
|
|
|
|
|
dx = 0
|
|
|
|
dy = 0
|
|
|
|
align_ratio = float(req.width) / float(req.height)
|
|
|
|
|
2011-04-26 22:51:19 +08:00
|
|
|
desktop_w, desktop_h = self.viewer.get_desktop_resolution()
|
|
|
|
if desktop_h == 0:
|
|
|
|
return
|
2010-12-22 09:13:11 +08:00
|
|
|
desktop_ratio = float(desktop_w) / float(desktop_h)
|
2009-11-29 07:48:56 +08:00
|
|
|
|
|
|
|
if not is_scale:
|
|
|
|
# Scaling disabled is easy, just force the VNC widget size. Since
|
|
|
|
# we are inside a scrollwindow, it shouldn't cause issues.
|
2012-05-14 21:24:56 +08:00
|
|
|
scroll.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
|
2012-02-02 06:26:46 +08:00
|
|
|
self.viewer.display.set_size_request(desktop_w, desktop_h)
|
2009-11-29 07:48:56 +08:00
|
|
|
return
|
|
|
|
|
|
|
|
# Make sure we never show scrollbars when scaling
|
2012-05-14 21:24:56 +08:00
|
|
|
scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.NEVER)
|
2009-11-29 07:48:56 +08:00
|
|
|
|
|
|
|
# Make sure there is no hard size requirement so we can scale down
|
2012-02-02 06:26:46 +08:00
|
|
|
self.viewer.display.set_size_request(-1, -1)
|
2009-11-29 07:48:56 +08:00
|
|
|
|
|
|
|
# Make sure desktop aspect ratio is maintained
|
2010-12-22 09:13:11 +08:00
|
|
|
if align_ratio > desktop_ratio:
|
|
|
|
desktop_w = int(req.height * desktop_ratio)
|
|
|
|
desktop_h = req.height
|
|
|
|
dx = (req.width - desktop_w) / 2
|
2009-11-29 07:48:56 +08:00
|
|
|
|
|
|
|
else:
|
2010-12-22 09:13:11 +08:00
|
|
|
desktop_w = req.width
|
|
|
|
desktop_h = int(req.width / desktop_ratio)
|
|
|
|
dy = (req.height - desktop_h) / 2
|
2009-11-29 07:48:56 +08:00
|
|
|
|
2013-04-15 04:39:02 +08:00
|
|
|
viewer_alloc = Gdk.Rectangle()
|
|
|
|
viewer_alloc.x = dx
|
|
|
|
viewer_alloc.y = dy
|
|
|
|
viewer_alloc.width = desktop_w
|
|
|
|
viewer_alloc.height = desktop_h
|
2012-11-08 21:15:02 +08:00
|
|
|
self.viewer.display.size_allocate(viewer_alloc)
|
2013-09-02 05:40:38 +08:00
|
|
|
|
|
|
|
|
|
|
|
###########################
|
|
|
|
# Serial console handling #
|
|
|
|
###########################
|
|
|
|
|
|
|
|
def activate_default_console_page(self):
|
|
|
|
if self.vm.get_graphics_devices() or not self.vm.get_serial_devs():
|
|
|
|
return
|
|
|
|
|
|
|
|
# Show serial console
|
|
|
|
devs = self.build_serial_list()
|
|
|
|
for name, ignore, sensitive, ignore, cb, serialidx in devs:
|
|
|
|
if not sensitive or not cb:
|
|
|
|
continue
|
|
|
|
|
|
|
|
self._show_serial_tab(name, serialidx)
|
|
|
|
break
|
|
|
|
|
|
|
|
def build_serial_list(self):
|
|
|
|
ret = []
|
|
|
|
|
|
|
|
def add_row(text, err, sensitive, do_radio, cb, serialidx):
|
|
|
|
ret.append([text, err, sensitive, do_radio, cb, serialidx])
|
|
|
|
|
|
|
|
devs = self.vm.get_serial_devs()
|
|
|
|
if len(devs) == 0:
|
|
|
|
add_row(_("No text console available"),
|
|
|
|
None, False, False, None, None)
|
|
|
|
|
|
|
|
def build_desc(dev):
|
|
|
|
if dev.virtual_device_type == "console":
|
|
|
|
return "Text Console %d" % (dev.vmmindex + 1)
|
|
|
|
return "Serial %d" % (dev.vmmindex + 1)
|
|
|
|
|
|
|
|
for dev in devs:
|
|
|
|
desc = build_desc(dev)
|
|
|
|
idx = dev.vmmindex
|
|
|
|
|
|
|
|
err = vmmSerialConsole.can_connect(self.vm, dev)
|
|
|
|
sensitive = not bool(err)
|
|
|
|
|
|
|
|
def cb(src):
|
|
|
|
return self.control_serial_tab(src, desc, idx)
|
|
|
|
|
|
|
|
add_row(desc, err, sensitive, True, cb, idx)
|
|
|
|
|
|
|
|
return ret
|
|
|
|
|
|
|
|
def current_serial_dev(self):
|
|
|
|
current_page = self.widget("console-pages").get_current_page()
|
|
|
|
if not current_page >= CONSOLE_PAGE_OFFSET:
|
|
|
|
return
|
|
|
|
|
|
|
|
serial_idx = current_page - CONSOLE_PAGE_OFFSET
|
|
|
|
if len(self.serial_tabs) < serial_idx:
|
|
|
|
return
|
|
|
|
|
|
|
|
return self.serial_tabs[serial_idx]
|
|
|
|
|
|
|
|
def control_serial_tab(self, src_ignore, name, target_port):
|
|
|
|
self.widget("details-pages").set_current_page(DETAILS_PAGE_CONSOLE)
|
|
|
|
if name == "graphics":
|
|
|
|
self.widget("console-pages").set_current_page(self.last_gfx_page)
|
|
|
|
else:
|
|
|
|
self._show_serial_tab(name, target_port)
|
|
|
|
|
|
|
|
def _show_serial_tab(self, name, target_port):
|
|
|
|
serial = None
|
|
|
|
for s in self.serial_tabs:
|
|
|
|
if s.name == name:
|
|
|
|
serial = s
|
|
|
|
break
|
|
|
|
|
|
|
|
if not serial:
|
|
|
|
serial = vmmSerialConsole(self.vm, target_port, name)
|
2013-12-19 05:27:33 +08:00
|
|
|
serial.terminal.connect("focus-in-event",
|
|
|
|
self.viewer_focus_changed)
|
|
|
|
serial.terminal.connect("focus-out-event",
|
|
|
|
self.viewer_focus_changed)
|
2013-09-02 05:40:38 +08:00
|
|
|
|
|
|
|
title = Gtk.Label(label=name)
|
|
|
|
self.widget("console-pages").append_page(serial.box, title)
|
|
|
|
self.serial_tabs.append(serial)
|
|
|
|
|
2013-11-10 06:54:47 +08:00
|
|
|
serial.open_console()
|
2013-09-02 05:40:38 +08:00
|
|
|
page_idx = self.serial_tabs.index(serial) + CONSOLE_PAGE_OFFSET
|
|
|
|
self.widget("console-pages").set_current_page(page_idx)
|
|
|
|
|
|
|
|
def populate_serial_menu(self, src):
|
|
|
|
for ent in src:
|
|
|
|
src.remove(ent)
|
|
|
|
|
|
|
|
serial_page_dev = self.current_serial_dev()
|
|
|
|
showing_graphics = (
|
|
|
|
self.widget("console-pages").get_current_page() ==
|
|
|
|
CONSOLE_PAGE_VIEWER)
|
|
|
|
|
|
|
|
# Populate serial devices
|
|
|
|
group = None
|
|
|
|
itemlist = self.build_serial_list()
|
|
|
|
for msg, err, sensitive, do_radio, cb, ignore in itemlist:
|
|
|
|
if do_radio:
|
|
|
|
item = Gtk.RadioMenuItem(group)
|
|
|
|
item.set_label(msg)
|
|
|
|
if group is None:
|
|
|
|
group = item
|
|
|
|
else:
|
|
|
|
item = Gtk.MenuItem(msg)
|
|
|
|
|
|
|
|
item.set_sensitive(sensitive)
|
|
|
|
|
|
|
|
if err and not sensitive:
|
|
|
|
item.set_tooltip_text(err)
|
|
|
|
|
|
|
|
if cb:
|
|
|
|
item.connect("toggled", cb)
|
|
|
|
|
|
|
|
# Tab is already open, make sure marked as such
|
|
|
|
if (sensitive and
|
|
|
|
serial_page_dev and
|
|
|
|
serial_page_dev.name == msg):
|
|
|
|
item.set_active(True)
|
|
|
|
|
|
|
|
src.add(item)
|
|
|
|
|
|
|
|
src.add(Gtk.SeparatorMenuItem())
|
|
|
|
|
|
|
|
# Populate graphical devices
|
|
|
|
devs = self.vm.get_graphics_devices()
|
|
|
|
if len(devs) == 0:
|
|
|
|
item = Gtk.MenuItem(_("No graphical console available"))
|
|
|
|
item.set_sensitive(False)
|
|
|
|
src.add(item)
|
|
|
|
else:
|
|
|
|
dev = devs[0]
|
|
|
|
item = Gtk.RadioMenuItem(group)
|
|
|
|
item.set_label(_("Graphical Console %s") %
|
|
|
|
dev.pretty_type_simple(dev.type))
|
|
|
|
if group is None:
|
|
|
|
group = item
|
|
|
|
|
|
|
|
if showing_graphics:
|
|
|
|
item.set_active(True)
|
|
|
|
item.connect("toggled", self.control_serial_tab,
|
|
|
|
dev.virtual_device_type, dev.type)
|
|
|
|
src.add(item)
|
|
|
|
|
|
|
|
src.show_all()
|