2010-12-21 15:34:36 -06:00
|
|
|
# -*- coding: utf-8 -*-
|
2009-10-30 12:25:27 -05:00
|
|
|
#
|
|
|
|
# Copyright (C) 2006-2008 Red Hat, Inc.
|
|
|
|
# Copyright (C) 2006 Daniel P. Berrange <berrange@redhat.com>
|
2010-12-21 15:34:36 -06:00
|
|
|
# Copyright (C) 2010 Marc-André Lureau <marcandre.lureau@redhat.com>
|
2009-10-30 12:25:27 -05: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.
|
|
|
|
#
|
|
|
|
|
|
|
|
import gtk
|
2010-12-21 19:13:25 -06:00
|
|
|
import gobject
|
2010-02-11 08:32:05 -06:00
|
|
|
|
2009-10-30 12:25:27 -05:00
|
|
|
import libvirt
|
2011-01-07 13:59:31 -06:00
|
|
|
|
2009-10-30 12:25:27 -05:00
|
|
|
import gtkvnc
|
2011-01-07 13:59:31 -06:00
|
|
|
|
|
|
|
try:
|
|
|
|
import SpiceClientGtk as spice
|
2011-01-13 10:12:29 -06:00
|
|
|
except:
|
2011-01-07 13:59:31 -06:00
|
|
|
spice = None
|
2010-02-11 08:32:05 -06:00
|
|
|
|
2009-10-30 12:25:27 -05:00
|
|
|
import os
|
2010-02-11 08:32:05 -06:00
|
|
|
import signal
|
2009-10-30 12:25:27 -05:00
|
|
|
import socket
|
2010-02-11 08:32:05 -06:00
|
|
|
import logging
|
2009-10-30 12:25:27 -05:00
|
|
|
|
2010-02-11 11:32:00 -06:00
|
|
|
from virtManager import util
|
2010-12-08 16:26:19 -06:00
|
|
|
from virtManager.baseclass import vmmGObjectUI
|
2009-10-30 12:25:27 -05:00
|
|
|
from virtManager.error import vmmErrorDialog
|
|
|
|
|
|
|
|
# Console pages
|
|
|
|
PAGE_UNAVAILABLE = 0
|
2009-11-01 20:14:41 -06:00
|
|
|
PAGE_AUTHENTICATE = 1
|
2010-12-21 19:13:11 -06:00
|
|
|
PAGE_VIEWER = 2
|
2009-10-30 12:25:27 -05:00
|
|
|
|
2009-10-30 13:36:17 -05:00
|
|
|
def has_property(obj, setting):
|
|
|
|
try:
|
|
|
|
obj.get_property(setting)
|
|
|
|
except TypeError:
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
2010-12-21 15:34:36 -06:00
|
|
|
|
|
|
|
class Tunnel(object):
|
|
|
|
def __init__(self):
|
|
|
|
self.outfd = None
|
|
|
|
self.errfd = None
|
|
|
|
self.pid = None
|
|
|
|
|
2011-01-14 09:20:20 -06:00
|
|
|
def open(self, connhost, connuser, connport, gaddr, gport, gsocket):
|
2010-12-21 15:34:36 -06:00
|
|
|
if self.outfd is not None:
|
|
|
|
return -1
|
|
|
|
|
|
|
|
# Build SSH cmd
|
|
|
|
argv = ["ssh", "ssh"]
|
2011-01-13 10:26:38 -06:00
|
|
|
if connport:
|
|
|
|
argv += ["-p", str(connport)]
|
2010-12-21 15:34:36 -06:00
|
|
|
|
2011-01-13 10:26:38 -06:00
|
|
|
if connuser:
|
|
|
|
argv += ['-l', connuser]
|
2010-12-21 15:34:36 -06:00
|
|
|
|
2011-01-13 10:26:38 -06:00
|
|
|
argv += [connhost]
|
2010-12-21 15:34:36 -06: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.
|
|
|
|
#
|
2011-01-14 09:20:20 -06:00
|
|
|
if gsocket:
|
|
|
|
nc_params = "-U %s" % gsocket
|
|
|
|
else:
|
|
|
|
nc_params = "%s %s" % (gaddr, gport)
|
2011-01-13 10:26:38 -06:00
|
|
|
|
2010-12-21 15:34:36 -06:00
|
|
|
nc_cmd = (
|
|
|
|
"""nc -q 2>&1 | grep -q "requires an argument";"""
|
|
|
|
"""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:])
|
|
|
|
logging.debug("Creating SSH tunnel: %s" % argv_str)
|
|
|
|
|
|
|
|
fds = socket.socketpair()
|
|
|
|
errorfds = socket.socketpair()
|
|
|
|
|
|
|
|
pid = os.fork()
|
|
|
|
if pid == 0:
|
|
|
|
fds[0].close()
|
|
|
|
errorfds[0].close()
|
|
|
|
|
|
|
|
os.close(0)
|
|
|
|
os.close(1)
|
|
|
|
os.close(2)
|
|
|
|
os.dup(fds[1].fileno())
|
|
|
|
os.dup(fds[1].fileno())
|
|
|
|
os.dup(errorfds[1].fileno())
|
|
|
|
os.execlp(*argv)
|
|
|
|
os._exit(1)
|
|
|
|
else:
|
|
|
|
fds[1].close()
|
|
|
|
errorfds[1].close()
|
|
|
|
|
|
|
|
logging.debug("Tunnel PID=%d OUTFD=%d ERRFD=%d" %
|
|
|
|
(pid, fds[0].fileno(), errorfds[0].fileno()))
|
|
|
|
errorfds[0].setblocking(0)
|
|
|
|
|
|
|
|
self.outfd = fds[0]
|
|
|
|
self.errfd = errorfds[0]
|
|
|
|
self.pid = pid
|
|
|
|
|
|
|
|
fd = fds[0].fileno()
|
|
|
|
if fd < 0:
|
|
|
|
raise SystemError("can't open a new tunnel: fd=%d" % fd)
|
|
|
|
return fd
|
|
|
|
|
|
|
|
def close(self):
|
|
|
|
if self.outfd is None:
|
|
|
|
return
|
|
|
|
|
|
|
|
logging.debug("Shutting down tunnel PID=%d OUTFD=%d ERRFD=%d" %
|
|
|
|
(self.pid, self.outfd.fileno(),
|
|
|
|
self.errfd.fileno()))
|
|
|
|
self.outfd.close()
|
|
|
|
self.outfd = None
|
|
|
|
self.errfd.close()
|
|
|
|
self.errfd = None
|
|
|
|
|
|
|
|
os.kill(self.pid, signal.SIGKILL)
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
class Tunnels(object):
|
2011-01-14 09:20:20 -06:00
|
|
|
def __init__(self, connhost, connuser, connport, gaddr, gport, gsocket):
|
2011-01-13 10:26:38 -06:00
|
|
|
self.connhost = connhost
|
|
|
|
self.connuser = connuser
|
|
|
|
self.connport = connport
|
|
|
|
|
|
|
|
self.gaddr = gaddr
|
|
|
|
self.gport = gport
|
2011-01-14 09:20:20 -06:00
|
|
|
self.gsocket = gsocket
|
2010-12-21 15:34:36 -06:00
|
|
|
self._tunnels = []
|
|
|
|
|
|
|
|
def open_new(self):
|
|
|
|
t = Tunnel()
|
2011-01-13 10:26:38 -06:00
|
|
|
fd = t.open(self.connhost, self.connuser, self.connport,
|
2011-01-14 09:20:20 -06:00
|
|
|
self.gaddr, self.gport, self.gsocket)
|
2010-12-21 15:34:36 -06:00
|
|
|
self._tunnels.append(t)
|
|
|
|
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
|
|
|
|
|
|
|
|
|
2010-12-21 19:13:11 -06:00
|
|
|
class Viewer(object):
|
|
|
|
def __init__(self, console, config):
|
|
|
|
self.console = console
|
|
|
|
self.config = config
|
|
|
|
self.display = None
|
|
|
|
|
2011-01-13 10:26:38 -06:00
|
|
|
def get_widget(self):
|
|
|
|
return self.display
|
|
|
|
|
2010-12-21 19:13:11 -06:00
|
|
|
def get_pixbuf(self):
|
|
|
|
return self.display.get_pixbuf()
|
|
|
|
|
|
|
|
def get_grab_keys_from_config(self):
|
2011-01-07 14:29:33 -06:00
|
|
|
keys = []
|
2010-12-21 19:13:11 -06:00
|
|
|
grab_keys = self.config.get_keys_combination(True)
|
|
|
|
if grab_keys is not None:
|
|
|
|
# If somebody edited this in GConf it would fail so
|
|
|
|
# we encapsulate this into try/except block
|
|
|
|
try:
|
|
|
|
keys = map(int, grab_keys.split(','))
|
|
|
|
except:
|
|
|
|
logging.debug("Error in grab_keys configuration in GConf")
|
2011-01-07 14:29:33 -06:00
|
|
|
|
2010-12-21 19:13:11 -06:00
|
|
|
return keys
|
|
|
|
|
|
|
|
def get_grab_keys(self):
|
|
|
|
keystr = None
|
|
|
|
try:
|
|
|
|
keys = self.display.get_grab_keys()
|
|
|
|
for k in keys:
|
|
|
|
if keystr is None:
|
|
|
|
keystr = gtk.gdk.keyval_name(k)
|
|
|
|
else:
|
|
|
|
keystr = keystr + "+" + gtk.gdk.keyval_name(k)
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
|
|
|
|
return keystr
|
|
|
|
|
|
|
|
def send_keys(self, keys):
|
|
|
|
return self.display.send_keys(keys)
|
|
|
|
|
2011-01-07 14:29:33 -06:00
|
|
|
def set_grab_keys(self):
|
|
|
|
try:
|
|
|
|
keys = self.get_grab_keys_from_config()
|
|
|
|
if keys:
|
|
|
|
self.display.set_grab_keys(keys)
|
|
|
|
except Exception, e:
|
|
|
|
logging.debug("Error when getting the grab keys combination: %s" %
|
|
|
|
str(e))
|
2010-12-21 19:13:11 -06:00
|
|
|
|
2011-01-14 09:20:20 -06:00
|
|
|
def open_host(self, host, user, port, socketpath, password=None):
|
2011-01-13 10:26:38 -06:00
|
|
|
raise NotImplementedError()
|
|
|
|
|
2010-12-21 19:13:11 -06:00
|
|
|
class VNCViewer(Viewer):
|
|
|
|
def __init__(self, console, config):
|
|
|
|
Viewer.__init__(self, console, config)
|
|
|
|
self.display = gtkvnc.Display()
|
2011-01-13 10:26:38 -06:00
|
|
|
self.sockfd = None
|
2010-12-21 19:13:11 -06:00
|
|
|
|
|
|
|
def init_widget(self):
|
|
|
|
# Set default grab key combination if found and supported
|
|
|
|
if self.config.vnc_grab_keys_supported():
|
2011-01-07 14:29:33 -06:00
|
|
|
self.set_grab_keys()
|
2010-12-21 19:13:11 -06:00
|
|
|
|
|
|
|
self.display.realize()
|
|
|
|
|
|
|
|
# Make sure viewer doesn't force resize itself
|
|
|
|
self.display.set_force_size(False)
|
|
|
|
|
|
|
|
self.console.refresh_scaling()
|
|
|
|
|
|
|
|
self.display.set_keyboard_grab(False)
|
|
|
|
self.display.set_pointer_grab(True)
|
|
|
|
|
|
|
|
self.display.connect("vnc-pointer-grab", self.console.pointer_grabbed)
|
|
|
|
self.display.connect("vnc-pointer-ungrab", self.console.pointer_ungrabbed)
|
|
|
|
self.display.connect("vnc-auth-credential", self._auth_credential)
|
2011-01-07 14:15:10 -06:00
|
|
|
self.display.connect("vnc-initialized",
|
|
|
|
lambda src: self.console.connected())
|
|
|
|
self.display.connect("vnc-disconnected",
|
|
|
|
lambda src: self.console.disconnected())
|
2010-12-21 19:13:11 -06:00
|
|
|
self.display.connect("vnc-desktop-resize", self.console.desktop_resize)
|
|
|
|
self.display.connect("focus-in-event", self.console.viewer_focus_changed)
|
|
|
|
self.display.connect("focus-out-event", self.console.viewer_focus_changed)
|
|
|
|
|
|
|
|
self.display.show()
|
|
|
|
|
|
|
|
def _auth_credential(self, src_ignore, credList):
|
|
|
|
for i in range(len(credList)):
|
|
|
|
if credList[i] not in [gtkvnc.CREDENTIAL_PASSWORD,
|
|
|
|
gtkvnc.CREDENTIAL_USERNAME,
|
|
|
|
gtkvnc.CREDENTIAL_CLIENTNAME]:
|
2011-04-06 10:22:03 -05:00
|
|
|
self.console.err.show_err(
|
|
|
|
summary=_("Unable to provide requested credentials to the VNC server"),
|
|
|
|
details=_("The credential type %s is not supported") % (str(credList[i])),
|
|
|
|
title=_("Unable to authenticate"),
|
|
|
|
async=True)
|
|
|
|
|
|
|
|
# schedule_retry will error out
|
|
|
|
self.console.viewerRetriesScheduled = 10
|
2010-12-21 19:13:11 -06:00
|
|
|
self.close()
|
2011-04-06 10:22:03 -05:00
|
|
|
self.console.activate_unavailable_page(
|
|
|
|
_("Unsupported console authentication type"))
|
2010-12-21 19:13:11 -06:00
|
|
|
return
|
|
|
|
|
|
|
|
withUsername = False
|
|
|
|
withPassword = False
|
|
|
|
for i in range(len(credList)):
|
|
|
|
logging.debug("Got credential request %s", str(credList[i]))
|
|
|
|
if credList[i] == gtkvnc.CREDENTIAL_PASSWORD:
|
|
|
|
withPassword = True
|
|
|
|
elif credList[i] == gtkvnc.CREDENTIAL_USERNAME:
|
|
|
|
withUsername = True
|
|
|
|
elif credList[i] == gtkvnc.CREDENTIAL_CLIENTNAME:
|
|
|
|
self.display.set_credential(credList[i], "libvirt-vnc")
|
|
|
|
|
|
|
|
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-13 10:26:38 -06:00
|
|
|
if not self.sockfd:
|
|
|
|
return
|
|
|
|
|
|
|
|
self.sockfd.close()
|
|
|
|
self.sockfd = None
|
2010-12-21 19:13:11 -06:00
|
|
|
|
|
|
|
def is_open(self):
|
|
|
|
return self.display.is_open()
|
|
|
|
|
2011-01-14 09:20:20 -06:00
|
|
|
def open_host(self, host, user, port, socketpath, password=None):
|
2011-01-13 10:26:38 -06:00
|
|
|
ignore = password
|
|
|
|
ignore = user
|
2011-01-14 09:20:20 -06:00
|
|
|
|
|
|
|
if not socketpath:
|
|
|
|
self.display.open_host(host, port)
|
|
|
|
return
|
|
|
|
|
|
|
|
try:
|
|
|
|
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
|
|
sock.connect(socketpath)
|
|
|
|
self.sockfd = sock
|
|
|
|
except Exception, e:
|
|
|
|
raise RuntimeError(_("Error opening socket path '%s': %s") %
|
|
|
|
(socketpath, e))
|
|
|
|
|
|
|
|
fd = self.sockfd.fileno()
|
|
|
|
if fd < 0:
|
|
|
|
raise RuntimeError((_("Error opening socket path '%s'") %
|
|
|
|
socketpath) + " fd=%s" % fd)
|
|
|
|
self.open_fd(fd)
|
2010-12-21 19:13:11 -06:00
|
|
|
|
|
|
|
def open_fd(self, fd):
|
|
|
|
self.display.open_fd(fd)
|
|
|
|
|
|
|
|
def get_grab_keys(self):
|
|
|
|
keystr = None
|
|
|
|
if self.config.vnc_grab_keys_supported():
|
|
|
|
keystr = super(VNCViewer, self).get_grab_keys()
|
|
|
|
|
|
|
|
# If grab keys are set to None then preserve old behaviour since
|
|
|
|
# the GTK-VNC - we're using older version of GTK-VNC
|
|
|
|
if keystr is None:
|
|
|
|
keystr = "Control_L+Alt_L"
|
|
|
|
return keystr
|
|
|
|
|
|
|
|
def set_credential_username(self, cred):
|
|
|
|
self.display.set_credential(gtkvnc.CREDENTIAL_USERNAME, cred)
|
|
|
|
|
|
|
|
def set_credential_password(self, cred):
|
|
|
|
self.display.set_credential(gtkvnc.CREDENTIAL_PASSWORD, cred)
|
|
|
|
|
|
|
|
|
2010-12-21 19:13:25 -06:00
|
|
|
class SpiceViewer(Viewer):
|
|
|
|
def __init__(self, console, config):
|
|
|
|
Viewer.__init__(self, console, config)
|
|
|
|
self.spice_session = None
|
|
|
|
self.display = None
|
|
|
|
self.audio = None
|
|
|
|
|
|
|
|
def _init_widget(self):
|
2011-01-07 14:29:33 -06:00
|
|
|
self.set_grab_keys()
|
2010-12-21 19:13:25 -06:00
|
|
|
self.console.refresh_scaling()
|
|
|
|
|
|
|
|
self.display.realize()
|
|
|
|
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))
|
|
|
|
self.display.show()
|
|
|
|
|
|
|
|
def close(self):
|
|
|
|
if self.spice_session is not None:
|
|
|
|
self.spice_session.disconnect()
|
2011-01-14 10:34:51 -06:00
|
|
|
self.spice_session = None
|
|
|
|
self.audio = None
|
|
|
|
self.display = None
|
2010-12-21 19:13:25 -06:00
|
|
|
|
|
|
|
def is_open(self):
|
|
|
|
return self.spice_session != None
|
|
|
|
|
|
|
|
def _main_channel_event_cb(self, channel, event):
|
|
|
|
if event == spice.CHANNEL_CLOSED:
|
|
|
|
self.console.disconnected()
|
2011-01-07 08:14:33 -06:00
|
|
|
elif event == spice.CHANNEL_ERROR_AUTH:
|
|
|
|
self.console.activate_auth_page()
|
2010-12-21 19:13:25 -06:00
|
|
|
|
|
|
|
def _channel_open_fd_request(self, channel, tls_ignore):
|
|
|
|
if not self.console.tunnels:
|
|
|
|
raise SystemError("Got fd request with no configured tunnel!")
|
|
|
|
|
|
|
|
fd = self.console.tunnels.open_new()
|
|
|
|
channel.open_fd(fd)
|
|
|
|
|
|
|
|
def _channel_new_cb(self, session, channel):
|
|
|
|
gobject.GObject.connect(channel, "open-fd",
|
|
|
|
self._channel_open_fd_request)
|
|
|
|
|
|
|
|
if type(channel) == spice.MainChannel:
|
|
|
|
channel.connect_after("channel-event", self._main_channel_event_cb)
|
|
|
|
return
|
|
|
|
|
|
|
|
if type(channel) == spice.DisplayChannel:
|
|
|
|
channel_id = channel.get_property("channel-id")
|
|
|
|
self.display = spice.Display(self.spice_session, channel_id)
|
|
|
|
self.console.window.get_widget("console-vnc-viewport").add(self.display)
|
|
|
|
self._init_widget()
|
2011-01-07 14:15:10 -06:00
|
|
|
self.console.connected()
|
2010-12-21 19:13:25 -06:00
|
|
|
return
|
|
|
|
|
2011-01-07 14:15:10 -06:00
|
|
|
if (type(channel) in [spice.PlaybackChannel, spice.RecordChannel] and
|
|
|
|
not self.audio):
|
2010-12-21 19:13:25 -06:00
|
|
|
self.audio = spice.Audio(self.spice_session)
|
|
|
|
return
|
|
|
|
|
2011-01-14 09:20:20 -06:00
|
|
|
def open_host(self, host, user, port, socketpath, password=None):
|
|
|
|
ignore = socketpath
|
|
|
|
|
2011-01-13 10:26:38 -06:00
|
|
|
uri = "spice://"
|
|
|
|
uri += (user and str(user) or "")
|
|
|
|
uri += str(host) + "?port=" + str(port)
|
|
|
|
logging.debug("spice uri: %s" % uri)
|
|
|
|
|
2010-12-21 19:13:25 -06:00
|
|
|
self.spice_session = spice.Session()
|
|
|
|
self.spice_session.set_property("uri", uri)
|
|
|
|
if password:
|
|
|
|
self.spice_session.set_property("password", password)
|
|
|
|
gobject.GObject.connect(self.spice_session, "channel-new",
|
|
|
|
self._channel_new_cb)
|
|
|
|
self.spice_session.connect()
|
|
|
|
|
|
|
|
def open_fd(self, fd, password=None):
|
|
|
|
self.spice_session = spice.Session()
|
|
|
|
if password:
|
|
|
|
self.spice_session.set_property("password", password)
|
|
|
|
gobject.GObject.connect(self.spice_session, "channel-new",
|
|
|
|
self._channel_new_cb)
|
|
|
|
self.spice_session.open_fd(fd)
|
|
|
|
|
|
|
|
def set_credential_password(self, cred):
|
|
|
|
self.spice_session.set_property("password", cred)
|
2011-01-07 08:14:33 -06:00
|
|
|
if self.console.tunnels:
|
|
|
|
fd = self.console.tunnels.open_new()
|
|
|
|
self.spice_session.open_fd(fd)
|
|
|
|
else:
|
|
|
|
self.spice_session.connect()
|
2010-12-21 19:13:25 -06:00
|
|
|
|
|
|
|
def get_scaling(self):
|
|
|
|
return self.display.get_property("resize-guest")
|
|
|
|
|
|
|
|
def set_scaling(self, scaling):
|
|
|
|
self.display.set_property("resize-guest", scaling)
|
|
|
|
|
|
|
|
|
2010-12-08 16:26:19 -06:00
|
|
|
class vmmConsolePages(vmmGObjectUI):
|
|
|
|
def __init__(self, vm, window):
|
|
|
|
vmmGObjectUI.__init__(self, None, None)
|
2009-10-30 12:25:27 -05:00
|
|
|
|
|
|
|
self.vm = vm
|
|
|
|
|
2010-12-08 16:26:19 -06:00
|
|
|
self.windowname = "vmm-details"
|
|
|
|
self.window = window
|
|
|
|
self.topwin = self.window.get_widget(self.windowname)
|
2010-11-30 13:33:21 -06:00
|
|
|
self.err = vmmErrorDialog(self.topwin)
|
2009-10-30 12:25:27 -05:00
|
|
|
|
2011-03-23 15:56:12 -05:00
|
|
|
self.pointer_is_grabbed = False
|
|
|
|
self.change_title()
|
|
|
|
self.vm.connect("config-changed", self.change_title)
|
2009-10-30 12:25:27 -05:00
|
|
|
|
|
|
|
# State for disabling modifiers when keyboard is grabbed
|
|
|
|
self.accel_groups = gtk.accel_groups_from_object(self.topwin)
|
|
|
|
self.gtk_settings_accel = None
|
2009-10-30 13:36:17 -05:00
|
|
|
self.gtk_settings_mnemonic = None
|
2009-10-30 12:25:27 -05:00
|
|
|
|
2009-11-28 17:48:56 -06:00
|
|
|
# Last noticed desktop resolution
|
|
|
|
self.desktop_resolution = None
|
2009-11-22 15:39:38 -06:00
|
|
|
|
2009-10-30 12:25:27 -05:00
|
|
|
# Initialize display widget
|
|
|
|
self.scale_type = self.vm.get_console_scaling()
|
2010-12-21 15:34:36 -06:00
|
|
|
self.tunnels = None
|
2010-12-21 19:13:11 -06:00
|
|
|
self.viewerRetriesScheduled = 0
|
|
|
|
self.viewerRetryDelay = 125
|
|
|
|
self.viewer = None
|
|
|
|
self.viewer_connected = False
|
2011-01-14 13:17:35 -06:00
|
|
|
self.viewer_connecting = False
|
2009-10-30 12:25:27 -05:00
|
|
|
|
2009-11-17 14:06:15 -06:00
|
|
|
finish_img = gtk.image_new_from_stock(gtk.STOCK_YES,
|
|
|
|
gtk.ICON_SIZE_BUTTON)
|
|
|
|
self.window.get_widget("console-auth-login").set_image(finish_img)
|
|
|
|
|
2010-12-21 19:13:11 -06:00
|
|
|
# Make viewer widget background always be black
|
2009-12-03 12:36:28 -06:00
|
|
|
black = gtk.gdk.Color(0, 0, 0)
|
2009-11-28 17:48:56 -06:00
|
|
|
self.window.get_widget("console-vnc-viewport").modify_bg(
|
|
|
|
gtk.STATE_NORMAL,
|
|
|
|
black)
|
|
|
|
|
2009-10-30 12:25:27 -05:00
|
|
|
# Signals are added by vmmDetails. Don't use signal_autoconnect here
|
|
|
|
# or it changes will be overwritten
|
2010-12-21 19:13:11 -06:00
|
|
|
# Set console scaling
|
|
|
|
self.vm.on_console_scaling_changed(self.refresh_scaling)
|
2009-10-30 12:25:27 -05:00
|
|
|
|
2010-12-21 19:13:11 -06:00
|
|
|
scroll = self.window.get_widget("console-vnc-scroll")
|
|
|
|
scroll.connect("size-allocate", self.scroll_size_allocate)
|
|
|
|
self.config.on_console_accels_changed(self.set_enable_accel)
|
2009-10-30 12:25:27 -05:00
|
|
|
|
|
|
|
def is_visible(self):
|
|
|
|
if self.topwin.flags() & gtk.VISIBLE:
|
|
|
|
return 1
|
|
|
|
return 0
|
|
|
|
|
2011-04-11 17:35:21 -05:00
|
|
|
def cleanup(self):
|
|
|
|
vmmGObjectUI.cleanup(self)
|
|
|
|
self.vm = None
|
|
|
|
|
2009-10-30 12:25:27 -05:00
|
|
|
##########################
|
|
|
|
# Initialization helpers #
|
|
|
|
##########################
|
|
|
|
|
2011-03-23 15:56:12 -05: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)
|
|
|
|
|
2010-12-21 19:13:11 -06:00
|
|
|
def viewer_focus_changed(self, ignore1=None, ignore2=None):
|
|
|
|
has_focus = self.viewer and self.viewer.get_widget() and \
|
|
|
|
self.viewer.get_widget().get_property("has-focus")
|
2010-12-02 12:41:22 -06:00
|
|
|
force_accel = self.config.get_console_accels()
|
|
|
|
|
|
|
|
if force_accel:
|
|
|
|
self._enable_modifiers()
|
2010-12-21 19:13:11 -06:00
|
|
|
elif has_focus and self.viewer_connected:
|
2010-12-02 12:41:22 -06:00
|
|
|
self._disable_modifiers()
|
|
|
|
else:
|
|
|
|
self._enable_modifiers()
|
2010-04-21 11:59:25 -05:00
|
|
|
|
2010-12-21 19:13:11 -06:00
|
|
|
def pointer_grabbed(self, src_ignore):
|
2011-03-23 15:56:12 -05:00
|
|
|
self.pointer_is_grabbed = True
|
|
|
|
self.change_title()
|
2009-10-30 12:25:27 -05:00
|
|
|
|
2010-12-09 10:22:35 -06:00
|
|
|
def pointer_ungrabbed(self, src_ignore):
|
2011-03-23 15:56:12 -05:00
|
|
|
self.pointer_is_grabbed = False
|
|
|
|
self.change_title()
|
2009-10-30 12:25:27 -05:00
|
|
|
|
2010-04-21 11:59:25 -05:00
|
|
|
def _disable_modifiers(self):
|
2009-11-01 15:36:44 -06:00
|
|
|
if self.gtk_settings_accel is not None:
|
|
|
|
return
|
|
|
|
|
2009-10-30 12:25:27 -05:00
|
|
|
for g in self.accel_groups:
|
|
|
|
self.topwin.remove_accel_group(g)
|
2009-10-30 13:36:17 -05:00
|
|
|
|
2009-10-30 12:25:27 -05:00
|
|
|
settings = gtk.settings_get_default()
|
|
|
|
self.gtk_settings_accel = settings.get_property('gtk-menu-bar-accel')
|
|
|
|
settings.set_property('gtk-menu-bar-accel', None)
|
|
|
|
|
2009-10-30 13:36:17 -05:00
|
|
|
if has_property(settings, "gtk-enable-mnemonics"):
|
2010-12-02 12:41:22 -06:00
|
|
|
self.gtk_settings_mnemonic = settings.get_property(
|
|
|
|
"gtk-enable-mnemonics")
|
2009-10-30 13:36:17 -05:00
|
|
|
settings.set_property("gtk-enable-mnemonics", False)
|
|
|
|
|
2010-04-21 11:59:25 -05:00
|
|
|
def _enable_modifiers(self):
|
2009-10-30 12:25:27 -05:00
|
|
|
if self.gtk_settings_accel is None:
|
|
|
|
return
|
2009-11-01 15:36:44 -06:00
|
|
|
|
2009-10-30 12:25:27 -05:00
|
|
|
settings = gtk.settings_get_default()
|
|
|
|
settings.set_property('gtk-menu-bar-accel', self.gtk_settings_accel)
|
|
|
|
self.gtk_settings_accel = None
|
2009-10-30 13:36:17 -05:00
|
|
|
|
|
|
|
if self.gtk_settings_mnemonic is not None:
|
|
|
|
settings.set_property("gtk-enable-mnemonics",
|
|
|
|
self.gtk_settings_mnemonic)
|
|
|
|
|
2009-10-30 12:25:27 -05:00
|
|
|
for g in self.accel_groups:
|
|
|
|
self.topwin.add_accel_group(g)
|
|
|
|
|
2010-12-02 12:41:22 -06:00
|
|
|
def set_enable_accel(self, ignore=None, ignore1=None,
|
|
|
|
ignore2=None, ignore3=None):
|
|
|
|
# Make sure modifiers are up to date
|
2010-12-21 19:13:11 -06:00
|
|
|
self.viewer_focus_changed()
|
2009-10-30 12:25:27 -05:00
|
|
|
|
2010-11-29 13:06:43 -06:00
|
|
|
def refresh_scaling(self, ignore1=None, ignore2=None, ignore3=None,
|
2009-10-30 12:25:27 -05:00
|
|
|
ignore4=None):
|
|
|
|
self.scale_type = self.vm.get_console_scaling()
|
2010-11-29 13:06:43 -06:00
|
|
|
self.window.get_widget("details-menu-view-scale-always").set_active(
|
|
|
|
self.scale_type == self.config.CONSOLE_SCALE_ALWAYS)
|
|
|
|
self.window.get_widget("details-menu-view-scale-never").set_active(
|
|
|
|
self.scale_type == self.config.CONSOLE_SCALE_NEVER)
|
|
|
|
self.window.get_widget("details-menu-view-scale-fullscreen").set_active(
|
|
|
|
self.scale_type == self.config.CONSOLE_SCALE_FULLSCREEN)
|
2009-10-30 12:25:27 -05:00
|
|
|
|
|
|
|
self.update_scaling()
|
|
|
|
|
|
|
|
def set_scale_type(self, src):
|
|
|
|
if not src.get_active():
|
|
|
|
return
|
|
|
|
|
|
|
|
if src == self.window.get_widget("details-menu-view-scale-always"):
|
|
|
|
self.scale_type = self.config.CONSOLE_SCALE_ALWAYS
|
|
|
|
elif src == self.window.get_widget("details-menu-view-scale-fullscreen"):
|
|
|
|
self.scale_type = self.config.CONSOLE_SCALE_FULLSCREEN
|
|
|
|
elif src == self.window.get_widget("details-menu-view-scale-never"):
|
|
|
|
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-21 19:13:11 -06:00
|
|
|
if not self.viewer:
|
|
|
|
return
|
|
|
|
|
|
|
|
curscale = self.viewer.get_scaling()
|
2009-10-30 12:25:27 -05:00
|
|
|
fs = self.window.get_widget("control-fullscreen").get_active()
|
2009-11-28 17:48:56 -06:00
|
|
|
vnc_scroll = self.window.get_widget("console-vnc-scroll")
|
2009-10-30 12:25:27 -05:00
|
|
|
|
|
|
|
if (self.scale_type == self.config.CONSOLE_SCALE_NEVER
|
|
|
|
and curscale == True):
|
2010-12-21 19:13:11 -06:00
|
|
|
self.viewer.set_scaling(False)
|
2009-10-30 12:25:27 -05:00
|
|
|
elif (self.scale_type == self.config.CONSOLE_SCALE_ALWAYS
|
|
|
|
and curscale == False):
|
2010-12-21 19:13:11 -06:00
|
|
|
self.viewer.set_scaling(True)
|
2009-10-30 12:25:27 -05:00
|
|
|
elif (self.scale_type == self.config.CONSOLE_SCALE_FULLSCREEN
|
|
|
|
and curscale != fs):
|
2010-12-21 19:13:11 -06:00
|
|
|
self.viewer.set_scaling(fs)
|
2009-10-30 12:25:27 -05:00
|
|
|
|
2009-11-28 17:48:56 -06:00
|
|
|
# Refresh viewer size
|
|
|
|
vnc_scroll.queue_resize()
|
|
|
|
|
2009-10-30 12:25:27 -05:00
|
|
|
def auth_login(self, ignore):
|
|
|
|
self.set_credentials()
|
|
|
|
self.activate_viewer_page()
|
|
|
|
|
|
|
|
def toggle_fullscreen(self, src):
|
2009-11-22 15:39:38 -06:00
|
|
|
do_fullscreen = src.get_active()
|
|
|
|
|
|
|
|
self.window.get_widget("control-fullscreen").set_active(do_fullscreen)
|
|
|
|
|
|
|
|
if do_fullscreen:
|
2009-10-30 12:25:27 -05:00
|
|
|
self.topwin.fullscreen()
|
|
|
|
self.window.get_widget("toolbar-box").hide()
|
|
|
|
else:
|
|
|
|
self.topwin.unfullscreen()
|
|
|
|
|
|
|
|
if self.window.get_widget("details-menu-view-toolbar").get_active():
|
|
|
|
self.window.get_widget("toolbar-box").show()
|
|
|
|
|
|
|
|
self.update_scaling()
|
|
|
|
|
2010-12-09 10:22:35 -06:00
|
|
|
def size_to_vm(self, src_ignore):
|
2009-11-28 19:07:01 -06:00
|
|
|
# Resize the console to best fit the VM resolution
|
|
|
|
if not self.desktop_resolution:
|
|
|
|
return
|
|
|
|
|
|
|
|
w, h = self.desktop_resolution
|
|
|
|
self.topwin.unmaximize()
|
|
|
|
self.topwin.resize(1, 1)
|
2009-12-14 11:37:08 -06:00
|
|
|
self.queue_scroll_resize_helper(w, h)
|
2009-11-28 19:07:01 -06:00
|
|
|
|
2009-10-30 12:25:27 -05:00
|
|
|
def send_key(self, src):
|
|
|
|
keys = None
|
|
|
|
if src.get_name() == "details-menu-send-cad":
|
|
|
|
keys = ["Control_L", "Alt_L", "Delete"]
|
|
|
|
elif src.get_name() == "details-menu-send-cab":
|
|
|
|
keys = ["Control_L", "Alt_L", "BackSpace"]
|
|
|
|
elif src.get_name() == "details-menu-send-caf1":
|
|
|
|
keys = ["Control_L", "Alt_L", "F1"]
|
|
|
|
elif src.get_name() == "details-menu-send-caf2":
|
|
|
|
keys = ["Control_L", "Alt_L", "F2"]
|
|
|
|
elif src.get_name() == "details-menu-send-caf3":
|
|
|
|
keys = ["Control_L", "Alt_L", "F3"]
|
|
|
|
elif src.get_name() == "details-menu-send-caf4":
|
|
|
|
keys = ["Control_L", "Alt_L", "F4"]
|
|
|
|
elif src.get_name() == "details-menu-send-caf5":
|
|
|
|
keys = ["Control_L", "Alt_L", "F5"]
|
|
|
|
elif src.get_name() == "details-menu-send-caf6":
|
|
|
|
keys = ["Control_L", "Alt_L", "F6"]
|
|
|
|
elif src.get_name() == "details-menu-send-caf7":
|
|
|
|
keys = ["Control_L", "Alt_L", "F7"]
|
|
|
|
elif src.get_name() == "details-menu-send-caf8":
|
|
|
|
keys = ["Control_L", "Alt_L", "F8"]
|
|
|
|
elif src.get_name() == "details-menu-send-caf9":
|
|
|
|
keys = ["Control_L", "Alt_L", "F9"]
|
|
|
|
elif src.get_name() == "details-menu-send-caf10":
|
|
|
|
keys = ["Control_L", "Alt_L", "F10"]
|
|
|
|
elif src.get_name() == "details-menu-send-caf11":
|
|
|
|
keys = ["Control_L", "Alt_L", "F11"]
|
|
|
|
elif src.get_name() == "details-menu-send-caf12":
|
|
|
|
keys = ["Control_L", "Alt_L", "F12"]
|
|
|
|
elif src.get_name() == "details-menu-send-printscreen":
|
|
|
|
keys = ["Print"]
|
|
|
|
|
|
|
|
if keys != None:
|
2010-12-21 19:13:11 -06:00
|
|
|
self.viewer.send_keys(keys)
|
2009-10-30 12:25:27 -05:00
|
|
|
|
|
|
|
|
|
|
|
##########################
|
|
|
|
# State tracking methods #
|
|
|
|
##########################
|
|
|
|
|
|
|
|
def view_vm_status(self):
|
|
|
|
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-21 19:13:11 -06:00
|
|
|
def close_viewer(self):
|
2011-01-14 10:34:51 -06:00
|
|
|
viewport = self.window.get_widget("console-vnc-viewport")
|
|
|
|
if self.viewer is None:
|
|
|
|
return
|
|
|
|
|
|
|
|
v = self.viewer # close_viewer() can be reentered
|
|
|
|
self.viewer = None
|
|
|
|
w = v.get_widget()
|
|
|
|
|
|
|
|
if w and w in viewport.get_children():
|
|
|
|
viewport.remove(w)
|
|
|
|
|
|
|
|
v.close()
|
|
|
|
self.viewer_connected = False
|
2010-12-21 19:13:11 -06:00
|
|
|
|
2010-12-09 10:22:35 -06:00
|
|
|
def update_widget_states(self, vm, status_ignore):
|
2009-10-30 12:25:27 -05:00
|
|
|
runable = vm.is_runable()
|
|
|
|
pages = self.window.get_widget("console-pages")
|
|
|
|
page = pages.get_current_page()
|
|
|
|
|
|
|
|
if runable:
|
|
|
|
if page != PAGE_UNAVAILABLE:
|
|
|
|
pages.set_current_page(PAGE_UNAVAILABLE)
|
|
|
|
|
|
|
|
self.view_vm_status()
|
|
|
|
return
|
|
|
|
|
2010-12-21 19:13:11 -06:00
|
|
|
elif page in [PAGE_UNAVAILABLE, PAGE_VIEWER]:
|
|
|
|
if self.viewer and self.viewer.is_open():
|
2009-10-30 12:25:27 -05:00
|
|
|
self.activate_viewer_page()
|
|
|
|
else:
|
2010-12-21 19:13:11 -06:00
|
|
|
self.viewerRetriesScheduled = 0
|
|
|
|
self.viewerRetryDelay = 125
|
2009-10-30 12:25:27 -05:00
|
|
|
self.try_login()
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
###################
|
|
|
|
# Page Navigation #
|
|
|
|
###################
|
|
|
|
|
|
|
|
def activate_unavailable_page(self, msg):
|
2011-01-14 13:17:35 -06:00
|
|
|
self.close_viewer()
|
2009-10-30 12:25:27 -05:00
|
|
|
self.window.get_widget("console-pages").set_current_page(PAGE_UNAVAILABLE)
|
|
|
|
self.window.get_widget("details-menu-vm-screenshot").set_sensitive(False)
|
|
|
|
self.window.get_widget("console-unavailable").set_label("<b>" + msg + "</b>")
|
|
|
|
|
|
|
|
def activate_auth_page(self, withPassword=True, withUsername=False):
|
|
|
|
(pw, username) = self.config.get_console_password(self.vm)
|
|
|
|
self.window.get_widget("details-menu-vm-screenshot").set_sensitive(False)
|
|
|
|
|
|
|
|
if withPassword:
|
|
|
|
self.window.get_widget("console-auth-password").show()
|
|
|
|
self.window.get_widget("label-auth-password").show()
|
|
|
|
else:
|
|
|
|
self.window.get_widget("console-auth-password").hide()
|
|
|
|
self.window.get_widget("label-auth-password").hide()
|
|
|
|
|
|
|
|
if withUsername:
|
|
|
|
self.window.get_widget("console-auth-username").show()
|
|
|
|
self.window.get_widget("label-auth-username").show()
|
|
|
|
else:
|
|
|
|
self.window.get_widget("console-auth-username").hide()
|
|
|
|
self.window.get_widget("label-auth-username").hide()
|
|
|
|
|
|
|
|
self.window.get_widget("console-auth-username").set_text(username)
|
|
|
|
self.window.get_widget("console-auth-password").set_text(pw)
|
|
|
|
|
|
|
|
if self.config.has_keyring():
|
|
|
|
self.window.get_widget("console-auth-remember").set_sensitive(True)
|
|
|
|
if pw != "" or username != "":
|
|
|
|
self.window.get_widget("console-auth-remember").set_active(True)
|
|
|
|
else:
|
|
|
|
self.window.get_widget("console-auth-remember").set_active(False)
|
|
|
|
else:
|
|
|
|
self.window.get_widget("console-auth-remember").set_sensitive(False)
|
|
|
|
self.window.get_widget("console-pages").set_current_page(PAGE_AUTHENTICATE)
|
|
|
|
if withUsername:
|
|
|
|
self.window.get_widget("console-auth-username").grab_focus()
|
|
|
|
else:
|
|
|
|
self.window.get_widget("console-auth-password").grab_focus()
|
|
|
|
|
|
|
|
|
|
|
|
def activate_viewer_page(self):
|
2010-12-21 19:13:11 -06:00
|
|
|
self.window.get_widget("console-pages").set_current_page(PAGE_VIEWER)
|
2009-10-30 12:25:27 -05:00
|
|
|
self.window.get_widget("details-menu-vm-screenshot").set_sensitive(True)
|
2010-12-21 19:13:11 -06:00
|
|
|
if self.viewer and self.viewer.get_widget():
|
|
|
|
self.viewer.get_widget().grab_focus()
|
2009-10-30 12:25:27 -05:00
|
|
|
|
2010-12-21 19:13:11 -06:00
|
|
|
def disconnected(self):
|
2010-02-11 09:43:44 -06:00
|
|
|
errout = ""
|
2010-12-21 15:34:36 -06:00
|
|
|
if self.tunnels is not None:
|
|
|
|
errout = self.tunnels.get_err_output()
|
|
|
|
self.tunnels.close_all()
|
|
|
|
self.tunnels = None
|
2010-02-11 09:43:44 -06:00
|
|
|
|
2010-12-21 19:13:11 -06:00
|
|
|
self.close_viewer()
|
|
|
|
logging.debug("Viewer disconnected")
|
2010-12-02 12:41:22 -06:00
|
|
|
|
|
|
|
# Make sure modifiers are set correctly
|
2010-12-21 19:13:11 -06:00
|
|
|
self.viewer_focus_changed()
|
2010-02-11 09:43:44 -06:00
|
|
|
|
|
|
|
if (self.skip_connect_attempt() or
|
|
|
|
self.guest_not_avail()):
|
|
|
|
# Exit was probably for legitimate reasons
|
2009-10-30 12:25:27 -05:00
|
|
|
self.view_vm_status()
|
|
|
|
return
|
|
|
|
|
2010-12-21 19:13:11 -06:00
|
|
|
error = _("Error: viewer connection to hypervisor host got refused "
|
2010-02-11 09:43:44 -06:00
|
|
|
"or disconnected!")
|
|
|
|
if errout:
|
|
|
|
logging.debug("Error output from closed console: %s" % errout)
|
|
|
|
error += "\n\nError: %s" % errout
|
2009-10-30 12:25:27 -05:00
|
|
|
|
2010-02-11 09:43:44 -06:00
|
|
|
self.activate_unavailable_page(error)
|
2009-10-30 12:25:27 -05:00
|
|
|
|
2010-12-21 19:13:11 -06:00
|
|
|
def connected(self):
|
|
|
|
self.viewer_connected = True
|
|
|
|
logging.debug("Viewer connected")
|
2009-10-30 12:25:27 -05:00
|
|
|
self.activate_viewer_page()
|
|
|
|
|
|
|
|
# Had a succesfull connect, so reset counters now
|
2010-12-21 19:13:11 -06:00
|
|
|
self.viewerRetriesScheduled = 0
|
|
|
|
self.viewerRetryDelay = 125
|
2009-10-30 12:25:27 -05:00
|
|
|
|
2010-12-02 12:41:22 -06:00
|
|
|
# Make sure modifiers are set correctly
|
2010-12-21 19:13:11 -06:00
|
|
|
self.viewer_focus_changed()
|
2010-12-02 12:41:22 -06:00
|
|
|
|
2009-10-30 12:25:27 -05:00
|
|
|
def schedule_retry(self):
|
2010-12-21 19:13:11 -06:00
|
|
|
if self.viewerRetriesScheduled >= 10:
|
2009-10-30 12:25:27 -05:00
|
|
|
logging.error("Too many connection failures, not retrying again")
|
|
|
|
return
|
|
|
|
|
2010-12-21 19:13:11 -06:00
|
|
|
util.safe_timeout_add(self.viewerRetryDelay, self.try_login)
|
2009-10-30 12:25:27 -05:00
|
|
|
|
2010-12-21 19:13:11 -06:00
|
|
|
if self.viewerRetryDelay < 2000:
|
|
|
|
self.viewerRetryDelay = self.viewerRetryDelay * 2
|
2009-10-30 12:25:27 -05:00
|
|
|
|
2010-02-11 09:43:44 -06:00
|
|
|
def skip_connect_attempt(self):
|
2011-01-14 13:17:35 -06:00
|
|
|
return (self.viewer or
|
2010-02-11 09:43:44 -06:00
|
|
|
not self.is_visible())
|
|
|
|
|
|
|
|
def guest_not_avail(self):
|
2011-04-10 16:41:56 -05:00
|
|
|
return (self.vm.is_shutoff() or self.vm.is_crashed())
|
2010-02-11 09:43:44 -06:00
|
|
|
|
2010-12-09 10:22:35 -06:00
|
|
|
def try_login(self, src_ignore=None):
|
2011-01-14 13:17:35 -06: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 09:43:44 -06:00
|
|
|
if self.skip_connect_attempt():
|
|
|
|
# Don't try and login for these cases
|
2009-10-30 12:25:27 -05:00
|
|
|
return
|
|
|
|
|
2010-02-11 09:43:44 -06:00
|
|
|
if self.guest_not_avail():
|
|
|
|
# Guest isn't running, schedule another try
|
2009-10-30 12:25:27 -05:00
|
|
|
self.activate_unavailable_page(_("Guest not running"))
|
|
|
|
self.schedule_retry()
|
|
|
|
return
|
|
|
|
|
2010-01-11 09:30:40 -06:00
|
|
|
try:
|
2011-01-13 10:26:38 -06:00
|
|
|
(protocol, transport,
|
|
|
|
connhost, connuser, connport,
|
2011-01-14 09:20:20 -06:00
|
|
|
gaddr, gport, gsocket) = self.vm.get_graphics_console()
|
2010-01-11 09:30:40 -06: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
|
2010-12-16 11:41:47 -06:00
|
|
|
logging.exception("Getting graphics console failed: %s" % str(e))
|
2010-01-11 09:30:40 -06:00
|
|
|
return
|
|
|
|
|
2009-10-30 12:25:27 -05:00
|
|
|
if protocol is None:
|
2011-01-07 13:59:31 -06:00
|
|
|
logging.debug("No graphics configured for guest")
|
2010-02-11 09:43:44 -06:00
|
|
|
self.activate_unavailable_page(
|
|
|
|
_("Graphical console not configured for guest"))
|
2009-10-30 12:25:27 -05:00
|
|
|
return
|
|
|
|
|
2011-01-07 13:59:31 -06:00
|
|
|
if protocol not in self.config.embeddable_graphics():
|
2011-04-09 22:03:32 -05:00
|
|
|
logging.debug("Don't know how to show graphics type '%s' "
|
2011-01-07 13:59:31 -06:00
|
|
|
"disabling console page" % protocol)
|
|
|
|
|
|
|
|
msg = (_("Cannot display graphical console type '%s'")
|
|
|
|
% protocol)
|
|
|
|
if protocol == "spice":
|
|
|
|
msg += ":\n %s" % self.config.get_spice_error()
|
|
|
|
|
|
|
|
self.activate_unavailable_page(msg)
|
2009-10-30 12:25:27 -05:00
|
|
|
return
|
|
|
|
|
2011-02-24 10:25:58 -06:00
|
|
|
if (gport == -1 and not gsocket):
|
2010-02-11 09:43:44 -06:00
|
|
|
self.activate_unavailable_page(
|
|
|
|
_("Graphical console is not yet active for guest"))
|
2009-10-30 12:25:27 -05:00
|
|
|
self.schedule_retry()
|
|
|
|
return
|
|
|
|
|
2010-02-11 09:43:44 -06:00
|
|
|
self.activate_unavailable_page(
|
|
|
|
_("Connecting to graphical console for guest"))
|
|
|
|
|
2011-01-13 10:26:38 -06:00
|
|
|
logging.debug("Starting connect process for "
|
|
|
|
"proto=%s trans=%s connhost=%s connuser=%s "
|
2011-01-14 09:20:20 -06:00
|
|
|
"connport=%s gaddr=%s gport=%s gsocket=%s" %
|
2011-01-13 10:26:38 -06:00
|
|
|
(protocol, transport, connhost, connuser, connport,
|
2011-01-14 09:20:20 -06:00
|
|
|
gaddr, gport, gsocket))
|
2009-10-30 12:25:27 -05:00
|
|
|
try:
|
2011-01-14 13:17:35 -06:00
|
|
|
if protocol == "vnc":
|
|
|
|
self.viewer = VNCViewer(self, self.config)
|
|
|
|
self.window.get_widget("console-vnc-viewport").add(
|
|
|
|
self.viewer.get_widget())
|
|
|
|
self.viewer.init_widget()
|
|
|
|
elif protocol == "spice":
|
|
|
|
self.viewer = SpiceViewer(self, self.config)
|
|
|
|
|
|
|
|
self.set_enable_accel()
|
|
|
|
|
2011-01-13 10:26:38 -06:00
|
|
|
if transport in ("ssh", "ext"):
|
2010-12-21 15:34:36 -06:00
|
|
|
if self.tunnels:
|
2010-02-12 15:33:43 -06:00
|
|
|
# Tunnel already open, no need to continue
|
2009-10-30 12:25:27 -05:00
|
|
|
return
|
|
|
|
|
2011-01-13 10:26:38 -06:00
|
|
|
self.tunnels = Tunnels(connhost, connuser, connport,
|
2011-01-14 09:20:20 -06:00
|
|
|
gaddr, gport, gsocket)
|
2010-12-21 15:34:36 -06:00
|
|
|
fd = self.tunnels.open_new()
|
2009-10-30 12:25:27 -05:00
|
|
|
if fd >= 0:
|
2010-12-21 19:13:11 -06:00
|
|
|
self.viewer.open_fd(fd)
|
2010-02-12 15:33:43 -06:00
|
|
|
|
2009-10-30 12:25:27 -05:00
|
|
|
else:
|
2011-01-13 10:26:38 -06:00
|
|
|
self.viewer.open_host(connhost, connuser,
|
2011-01-14 09:20:20 -06:00
|
|
|
str(gport), gsocket)
|
2010-02-12 15:33:43 -06:00
|
|
|
|
2011-01-13 10:26:38 -06: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-30 12:25:27 -05:00
|
|
|
|
2010-12-09 10:22:35 -06:00
|
|
|
def set_credentials(self, src_ignore=None):
|
2009-10-30 12:25:27 -05:00
|
|
|
passwd = self.window.get_widget("console-auth-password")
|
|
|
|
if passwd.flags() & gtk.VISIBLE:
|
2010-12-21 19:13:11 -06:00
|
|
|
self.viewer.set_credential_password(passwd.get_text())
|
2009-10-30 12:25:27 -05:00
|
|
|
username = self.window.get_widget("console-auth-username")
|
|
|
|
if username.flags() & gtk.VISIBLE:
|
2010-12-21 19:13:11 -06:00
|
|
|
self.viewer.set_credential_username(username.get_text())
|
2009-10-30 12:25:27 -05:00
|
|
|
|
|
|
|
if self.window.get_widget("console-auth-remember").get_active():
|
|
|
|
self.config.set_console_password(self.vm, passwd.get_text(),
|
|
|
|
username.get_text())
|
|
|
|
|
2010-12-09 10:22:35 -06:00
|
|
|
def desktop_resize(self, src_ignore, w, h):
|
2009-11-28 17:48:56 -06:00
|
|
|
self.desktop_resolution = (w, h)
|
2009-11-28 19:07:01 -06:00
|
|
|
self.window.get_widget("console-vnc-scroll").queue_resize()
|
2009-11-28 17:48:56 -06:00
|
|
|
|
2009-12-14 11:37:08 -06:00
|
|
|
def queue_scroll_resize_helper(self, w, h):
|
2009-11-28 17:48:56 -06: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
|
|
|
|
"""
|
2009-12-14 11:37:08 -06:00
|
|
|
widget = self.window.get_widget("console-vnc-scroll")
|
2009-11-28 17:48:56 -06:00
|
|
|
signal_holder = []
|
|
|
|
|
2009-12-14 11:37:08 -06:00
|
|
|
def restore_scroll(src):
|
2010-12-21 19:13:11 -06:00
|
|
|
is_scale = self.viewer.get_scaling()
|
2009-12-14 11:37:08 -06:00
|
|
|
|
|
|
|
if is_scale:
|
|
|
|
w_policy = gtk.POLICY_NEVER
|
|
|
|
h_policy = gtk.POLICY_NEVER
|
|
|
|
else:
|
|
|
|
w_policy = gtk.POLICY_AUTOMATIC
|
|
|
|
h_policy = gtk.POLICY_AUTOMATIC
|
|
|
|
|
|
|
|
src.set_policy(w_policy, h_policy)
|
|
|
|
return False
|
|
|
|
|
2009-11-28 17:48:56 -06:00
|
|
|
def unset_cb(src):
|
2009-12-14 11:37:08 -06:00
|
|
|
src.queue_resize_no_redraw()
|
2010-02-11 11:32:00 -06:00
|
|
|
util.safe_idle_add(restore_scroll, src)
|
2009-11-28 17:48:56 -06:00
|
|
|
return False
|
|
|
|
|
|
|
|
def request_cb(src, req):
|
|
|
|
signal_id = signal_holder[0]
|
|
|
|
req.width = w
|
|
|
|
req.height = h
|
|
|
|
|
|
|
|
src.disconnect(signal_id)
|
|
|
|
|
2010-02-11 11:32:00 -06:00
|
|
|
util.safe_idle_add(unset_cb, widget)
|
2009-11-28 17:48:56 -06:00
|
|
|
return False
|
|
|
|
|
2009-12-14 11:37:08 -06:00
|
|
|
# Disable scroll bars while we resize, since resizing to the VM's
|
|
|
|
# dimensions can erroneously show scroll bars when they aren't needed
|
|
|
|
widget.set_policy(gtk.POLICY_NEVER, gtk.POLICY_NEVER)
|
|
|
|
|
2009-11-28 17:48:56 -06:00
|
|
|
signal_id = widget.connect("size-request", request_cb)
|
|
|
|
signal_holder.append(signal_id)
|
|
|
|
|
|
|
|
widget.queue_resize()
|
|
|
|
|
2010-12-09 10:22:35 -06:00
|
|
|
def scroll_size_allocate(self, src_ignore, req):
|
2010-12-21 19:13:11 -06:00
|
|
|
if not self.viewer or not self.desktop_resolution:
|
2009-11-28 17:48:56 -06:00
|
|
|
return
|
|
|
|
|
|
|
|
scroll = self.window.get_widget("console-vnc-scroll")
|
2010-12-21 19:13:11 -06:00
|
|
|
is_scale = self.viewer.get_scaling()
|
2009-11-28 17:48:56 -06:00
|
|
|
|
|
|
|
dx = 0
|
|
|
|
dy = 0
|
|
|
|
align_ratio = float(req.width) / float(req.height)
|
|
|
|
|
2010-12-21 19:13:11 -06:00
|
|
|
desktop_w, desktop_h = self.desktop_resolution
|
|
|
|
desktop_ratio = float(desktop_w) / float(desktop_h)
|
2009-11-28 17:48:56 -06: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.
|
|
|
|
scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
|
2010-12-21 19:13:11 -06:00
|
|
|
self.viewer.get_widget().set_size_request(desktop_w, desktop_h)
|
2009-11-28 17:48:56 -06:00
|
|
|
return
|
|
|
|
|
|
|
|
# Make sure we never show scrollbars when scaling
|
|
|
|
scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_NEVER)
|
|
|
|
|
|
|
|
# Make sure there is no hard size requirement so we can scale down
|
2010-12-21 19:13:11 -06:00
|
|
|
self.viewer.get_widget().set_size_request(-1, -1)
|
2009-11-28 17:48:56 -06:00
|
|
|
|
|
|
|
# Make sure desktop aspect ratio is maintained
|
2010-12-21 19:13:11 -06: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-28 17:48:56 -06:00
|
|
|
|
|
|
|
else:
|
2010-12-21 19:13:11 -06:00
|
|
|
desktop_w = req.width
|
|
|
|
desktop_h = int(req.width / desktop_ratio)
|
|
|
|
dy = (req.height - desktop_h) / 2
|
2009-11-28 17:48:56 -06:00
|
|
|
|
2010-12-21 19:13:11 -06:00
|
|
|
viewer_alloc = gtk.gdk.Rectangle(x=dx,
|
|
|
|
y=dy,
|
|
|
|
width=desktop_w,
|
|
|
|
height=desktop_h)
|
2009-11-28 17:48:56 -06:00
|
|
|
|
2010-12-21 19:13:11 -06:00
|
|
|
self.viewer.get_widget().size_allocate(viewer_alloc)
|
2009-10-30 12:25:27 -05:00
|
|
|
|
2010-12-08 16:26:19 -06:00
|
|
|
vmmGObjectUI.type_register(vmmConsolePages)
|