[virt-tools-list] [PATCH 5 of 6] console: support SPICE
Marc-André Lureau
marcandre.lureau at redhat.com
Fri Dec 17 13:51:26 UTC 2010
# HG changeset patch
# User Marc-André Lureau <marcandre.lureau at redhat.com>
# Date 1291221765 -3600
# Node ID 36976fe992df8ebdbe52fca392feb6a284ae3e1c
# Parent 01667175a00b22b91f3592761dbd148a5bb47d58
console: support SPICE
Not supported yet:
- copy/paste
diff -r 01667175a00b -r 36976fe992df src/virtManager/config.py
--- a/src/virtManager/config.py Fri Dec 17 14:33:35 2010 +0100
+++ b/src/virtManager/config.py Wed Dec 01 17:42:45 2010 +0100
@@ -290,7 +290,7 @@
# Check whether we have GTK-VNC that supports configurable grab keys
# installed on the system
- def grab_keys_supported(self):
+ def vnc_grab_keys_supported(self):
try:
import gtkvnc
return hasattr(gtkvnc.Display, "set_grab_keys")
diff -r 01667175a00b -r 36976fe992df src/virtManager/console.py
--- a/src/virtManager/console.py Fri Dec 17 14:33:35 2010 +0100
+++ b/src/virtManager/console.py Wed Dec 01 17:42:45 2010 +0100
@@ -1,6 +1,7 @@
-#
+# -*- coding: utf-8 -*-
# Copyright (C) 2006-2008 Red Hat, Inc.
# Copyright (C) 2006 Daniel P. Berrange <berrange at redhat.com>
+# Copyright (C) 2010 Marc-André Lureau <marcandre.lureau at redhat.com>
#
# 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
@@ -25,6 +26,7 @@
import libvirt
import dbus
import gtkvnc
+import SpiceClientGtk as spice
import os
import sys
@@ -39,7 +41,7 @@
# Console pages
PAGE_UNAVAILABLE = 0
PAGE_AUTHENTICATE = 1
-PAGE_VNCVIEWER = 2
+PAGE_VIEWER = 2
def has_property(obj, setting):
try:
@@ -48,6 +50,392 @@
return False
return True
+class Tunnel(object):
+ def __init__(self):
+ self.outfd = None
+ self.errfd = None
+ self.pid = None
+
+ def open(self, server, addr, port, username, sshport):
+ if self.outfd is not None:
+ return -1
+
+ # Build SSH cmd
+ argv = ["ssh", "ssh"]
+ if sshport:
+ argv += ["-p", str(sshport)]
+
+ if username:
+ argv += ['-l', username]
+
+ argv += [ server ]
+
+ # 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.
+ #
+ nc_params = "%s %s" % (addr, str(port))
+ 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):
+ def __init__(self, server, addr, port, username, sshport):
+ self.server = server
+ self.addr = addr
+ self.port = port
+ self.username = username
+ self.sshport = sshport
+ self._tunnels = []
+
+ def open_new(self):
+ t = Tunnel()
+ fd = t.open(self.server, self.addr, self.port,
+ self.username, self.sshport)
+ 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
+
+
+class Viewer(object):
+ def __init__(self, console, config):
+ self.console = console
+ self.config = config
+ self.display = None
+
+ def set_credential_username(self, cred):
+ raise NotImplementedError
+
+ def get_pixbuf(self):
+ return self.display.get_pixbuf()
+
+ def set_keyboard_grab(self, grab):
+ self.display.set_keyboard_grab(grab)
+
+ def get_grab_keys_from_config(self):
+ keys = None
+ 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")
+ if keys is None:
+ raise Exception("No grab_keys configuration")
+ 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)
+
+
+class VNCViewer(Viewer):
+ def __init__(self, console, config):
+ Viewer.__init__(self, console, config)
+ self.display = gtkvnc.Display()
+
+ def get_widget(self):
+ return self.display
+
+ def init_widget(self):
+ # Set default grab key combination if found and supported
+ if self.config.vnc_grab_keys_supported():
+ try:
+ keys = self.get_grab_keys_from_config()
+ self.display.set_grab_keys(keys)
+ except Exception, e:
+ logging.debug("Error when getting the grab keys combination: %s" % str(e))
+
+ self.display.realize()
+
+ # Make sure viewer doesn't force resize itself
+ self.display.set_force_size(False)
+
+ self.console.refresh_scaling()
+
+ self.console.set_keyboard_grab()
+ self.display.set_pointer_grab(True)
+
+ self.display.connect("vnc-auth-credential", self._auth_credential)
+ self.display.connect("vnc-initialized", lambda src: self.console.connected())
+ self.display.connect("vnc-disconnected", lambda src: self.console.disconnected())
+ self.display.connect("vnc-desktop-resize", self.console.desktop_resize)
+ self.display.connect("vnc-pointer-grab", self.console.mouse_grabbed)
+ self.display.connect("vnc-pointer-ungrab", self.console.mouse_ungrabbed)
+ self.display.connect("vnc-keyboard-grab", self.console.keyboard_grabbed)
+ self.display.connect("vnc-keyboard-ungrab", self.console.keyboard_ungrabbed)
+ self.display.show()
+
+ def _auth_credential(self, src, credList):
+ for i in range(len(credList)):
+ if credList[i] not in [gtkvnc.CREDENTIAL_PASSWORD,
+ gtkvnc.CREDENTIAL_USERNAME,
+ gtkvnc.CREDENTIAL_CLIENTNAME]:
+ 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)
+ self.console.viewerRetriesScheduled = 10 # schedule_retry will error out
+ self.close()
+ self.console.activate_unavailable_page(_("Unsupported console authentication type"))
+ 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()
+
+ def is_open(self):
+ return self.display.is_open()
+
+ def open_host(self, uri, connhost, port):
+ self.display.open_host(connhost, port)
+
+ 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)
+
+
+class SpiceViewer(Viewer):
+ def __init__(self, console, config):
+ Viewer.__init__(self, console, config)
+ self.spice_session = None
+ self.display = None
+ self.audio = None
+
+ def get_widget(self):
+ return self.display
+
+ def _init_widget(self):
+ try:
+ keys = self.get_grab_keys_from_config()
+ self.display.set_grab_keys(keys)
+ except Exception, e:
+ logging.debug("Error when getting the grab keys combination: %s" % str(e))
+
+ self.console.refresh_scaling()
+
+ self.display.realize()
+ self.display.connect("mouse-grab", lambda src, g: g and self.console.mouse_grabbed(src))
+ self.display.connect("mouse-grab", lambda src, g: g or self.console.mouse_ungrabbed(src))
+ self.display.connect("keyboard-grab", lambda src, g: g and self.console.keyboard_grabbed(src))
+ self.display.connect("keyboard-grab", lambda src, g: g or self.console.keyboard_ungrabbed(src))
+ self.display.show()
+
+ def close(self):
+ if self.spice_session is not None:
+ self.spice_session.disconnect()
+ self.spice_session = None
+
+ def is_open(self):
+ # TODO: currently, connect() is sync, and there is no
+ # is_open()/is_connected() in spice-session
+ # It could be done tracking the main_channel socket?
+ return self.spice_session != None
+
+ def _main_channel_event_cb(self, channel, event):
+ if event == spice.CHANNEL_CLOSED:
+ self.console.disconnected()
+
+ def _channel_open_fd_request(self, channel, tls):
+ 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):
+ self.console.connected()
+ 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()
+ self.console.activate_viewer_page()
+ return
+
+ if type(channel) in [spice.PlaybackChannel, spice.RecordChannel] and \
+ not self.audio:
+ self.audio = spice.Audio(self.spice_session)
+ return
+
+ def open_host(self, uri, connhost, port, password=None):
+ 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)
+
+ def get_scaling(self):
+ return self.display.get_property("resize-guest")
+
+ def set_scaling(self, scaling):
+ self.display.set_property("resize-guest", scaling)
+
+
+
class vmmConsolePages(gobject.GObject):
def __init__(self, config, vm, engine, window):
self.__gobject_init__()
@@ -66,7 +454,7 @@
self.title = vm.get_name() + " " + self.topwin.get_title()
self.topwin.set_title(self.title)
- # State for disabling modifiers when keyboard is grabbed
+ # State for disabling shortcuts when keyboard is grabbed
self.accel_groups = gtk.accel_groups_from_object(self.topwin)
self.gtk_settings_accel = None
self.gtk_settings_mnemonic = None
@@ -76,30 +464,11 @@
# Initialize display widget
self.scale_type = self.vm.get_console_scaling()
- self.vncTunnel = None
- self.vncViewerRetriesScheduled = 0
- self.vncViewerRetryDelay = 125
- self.vnc_connected = False
-
- self.vncViewer = gtkvnc.Display()
- self.window.get_widget("console-vnc-viewport").add(self.vncViewer)
-
- # Set default grab key combination if found and supported
- if self.config.grab_keys_supported():
- try:
- 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(','))
- self.vncViewer.set_grab_keys( keys )
- except:
- logging.debug("Error in grab_keys configuration in GConf")
- except Exception, e:
- logging.debug("Error when getting the grab keys combination: %s" % str(e))
-
- self.init_vnc()
+ self.viewerRetriesScheduled = 0
+ self.viewerRetryDelay = 125
+ self.tunnels = None
+ self.viewer = None
+ self.viewer_connected = False
finish_img = gtk.image_new_from_stock(gtk.STOCK_YES,
gtk.ICON_SIZE_BUTTON)
@@ -109,7 +478,7 @@
self.notifyInterface = None
self.init_dbus()
- # Make VNC widget background always be black
+ # Make viewer widget background always be black
black = gtk.gdk.Color(0, 0, 0)
self.window.get_widget("console-vnc-viewport").modify_bg(
gtk.STATE_NORMAL,
@@ -118,6 +487,13 @@
# Signals are added by vmmDetails. Don't use signal_autoconnect here
# or it changes will be overwritten
+ self.config.on_console_keygrab_changed(self.set_keyboard_grab)
+
+ # Set console scaling
+ self.vm.on_console_scaling_changed(self.refresh_scaling)
+
+ scroll = self.window.get_widget("console-vnc-scroll")
+ scroll.connect("size-allocate", self.scroll_size_allocate)
def is_visible(self):
if self.topwin.flags() & gtk.VISIBLE:
@@ -143,59 +519,17 @@
logging.error("Cannot initialize notification system" + str(e))
- def init_vnc(self):
- self.vncViewer.realize()
-
- # Make sure viewer doesn't force resize itself
- self.vncViewer.set_force_size(False)
-
- # Set VNC console scaling
- self.vm.on_console_scaling_changed(self.refresh_scaling)
- self.refresh_scaling()
-
- self.set_keyboard_grab()
- self.config.on_console_keygrab_changed(self.set_keyboard_grab)
- self.vncViewer.set_pointer_grab(True)
-
- scroll = self.window.get_widget("console-vnc-scroll")
- scroll.connect("size-allocate", self.scroll_size_allocate)
-
- self.vncViewer.connect("vnc-pointer-grab", self.notify_grabbed)
- self.vncViewer.connect("vnc-pointer-ungrab", self.notify_ungrabbed)
- self.vncViewer.connect("vnc-auth-credential", self._vnc_auth_credential)
- self.vncViewer.connect("vnc-initialized", self._vnc_initialized)
- self.vncViewer.connect("vnc-disconnected", self._vnc_disconnected)
- self.vncViewer.connect("vnc-keyboard-grab", self.keyboard_grabbed)
- self.vncViewer.connect("vnc-keyboard-ungrab", self.keyboard_ungrabbed)
- self.vncViewer.connect("vnc-desktop-resize", self.desktop_resize)
- self.vncViewer.show()
-
- #############
- # Listeners #
- #############
+ ####################
+ # Common listeners #
+ ####################
def keyboard_grabbed(self, src):
- self._disable_modifiers()
+ self._disable_keyboard_accels()
def keyboard_ungrabbed(self, src=None):
- self._enable_modifiers()
+ self._enable_keyboard_accels()
- def notify_grabbed(self, src):
- keystr = None
- if self.config.grab_keys_supported():
- try:
- keys = src.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
-
- # 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"
+ def mouse_grabbed(self, src):
+ keystr = self.viewer.get_grab_keys()
self.topwin.set_title(_("Press %s to release pointer.") % keystr +
" " + self.title)
@@ -222,7 +556,7 @@
except Exception, e:
logging.error("Cannot popup notification " + str(e))
- def notify_ungrabbed(self, src):
+ def mouse_ungrabbed(self, src):
self.topwin.set_title(self.title)
def notify_closed(self, i, reason=None):
@@ -237,7 +571,7 @@
self.config.set_console_grab_notify(False)
- def _disable_modifiers(self):
+ def _disable_keyboard_accels(self):
if self.gtk_settings_accel is not None:
return
@@ -253,7 +587,7 @@
settings.set_property("gtk-enable-mnemonics", False)
- def _enable_modifiers(self):
+ def _enable_keyboard_accels(self):
if self.gtk_settings_accel is None:
return
@@ -270,8 +604,10 @@
def set_keyboard_grab(self, ignore=None, ignore1=None,
ignore2=None, ignore3=None):
+ if not self.viewer:
+ return
grab = self.config.get_console_keygrab() == 2
- self.vncViewer.set_keyboard_grab(grab)
+ self.viewer.set_keyboard_grab(grab)
def refresh_scaling(self,ignore1=None, ignore2=None, ignore3=None,
ignore4=None):
@@ -297,19 +633,22 @@
self.update_scaling()
def update_scaling(self):
- curscale = self.vncViewer.get_scaling()
+ if not self.viewer:
+ return
+
+ curscale = self.viewer.get_scaling()
fs = self.window.get_widget("control-fullscreen").get_active()
vnc_scroll = self.window.get_widget("console-vnc-scroll")
if (self.scale_type == self.config.CONSOLE_SCALE_NEVER
and curscale == True):
- self.vncViewer.set_scaling(False)
+ self.viewer.set_scaling(False)
elif (self.scale_type == self.config.CONSOLE_SCALE_ALWAYS
and curscale == False):
- self.vncViewer.set_scaling(True)
+ self.viewer.set_scaling(True)
elif (self.scale_type == self.config.CONSOLE_SCALE_FULLSCREEN
and curscale != fs):
- self.vncViewer.set_scaling(fs)
+ self.viewer.set_scaling(fs)
# Refresh viewer size
vnc_scroll.queue_resize()
@@ -327,13 +666,14 @@
self.topwin.fullscreen()
if self.config.get_console_keygrab() == 1:
- gtk.gdk.keyboard_grab(self.vncViewer.window, False, 0L)
- self._disable_modifiers()
+ if self.viewer and self.viewer.get_widget():
+ gtk.gdk.keyboard_grab(self.viewer.get_widget().window, False, 0L)
+ self._disable_keyboard_accels()
self.window.get_widget("toolbar-box").hide()
else:
if self.config.get_console_keygrab() == 1:
- self._enable_modifiers()
+ self._enable_keyboard_accels()
gtk.gdk.keyboard_ungrab(0L)
self.topwin.unfullscreen()
@@ -387,7 +727,7 @@
keys = ["Print"]
if keys != None:
- self.vncViewer.send_keys(keys)
+ self.viewer.send_keys(keys)
##########################
@@ -402,6 +742,14 @@
if status == libvirt.VIR_DOMAIN_CRASHED:
self.activate_unavailable_page(_("Guest has crashed"))
+ def close_viewer(self):
+ if self.viewer is not None:
+ self.window.get_widget("console-vnc-viewport").remove(self.viewer.get_widget())
+ v = self.viewer # TODO: for some reason spice.close() is not reentrant...
+ self.viewer = None
+ v.close()
+ self.viewer_connected = False
+
def update_widget_states(self, vm, status):
runable = vm.is_runable()
pages = self.window.get_widget("console-pages")
@@ -409,18 +757,17 @@
if runable:
if page != PAGE_UNAVAILABLE:
- self.vncViewer.close()
+ pages = self.window.get_widget("console-pages")
pages.set_current_page(PAGE_UNAVAILABLE)
-
self.view_vm_status()
return
- elif page in [PAGE_UNAVAILABLE, PAGE_VNCVIEWER]:
- if self.vncViewer.is_open():
+ elif page in [PAGE_UNAVAILABLE, PAGE_VIEWER, PAGE_AUTHENTICATE]:
+ if self.viewer and self.viewer.is_open():
self.activate_viewer_page()
else:
- self.vncViewerRetriesScheduled = 0
- self.vncViewerRetryDelay = 125
+ self.viewerRetriesScheduled = 0
+ self.viewerRetryDelay = 125
self.try_login()
return
@@ -471,23 +818,22 @@
def activate_viewer_page(self):
- self.window.get_widget("console-pages").set_current_page(PAGE_VNCVIEWER)
+ self.window.get_widget("console-pages").set_current_page(PAGE_VIEWER)
self.window.get_widget("details-menu-vm-screenshot").set_sensitive(True)
- self.vncViewer.grab_focus()
+ if self.viewer and self.viewer.get_widget():
+ self.viewer.get_widget().grab_focus()
- ########################
- # VNC Specific methods #
- ########################
+ def disconnected(self):
+ errout = ""
+ if self.tunnels is not None:
+ errout = self.tunnels.get_err_output()
+ self.tunnels.close_all()
+ self.tunnels = None
- def _vnc_disconnected(self, src):
- errout = ""
- if self.vncTunnel is not None:
- errout = self.get_tunnel_err_output()
- self.close_tunnel()
+ self.close_viewer()
- self.vnc_connected = False
- logging.debug("VNC disconnected")
+ logging.debug("Viewer disconnected")
self.keyboard_ungrabbed()
if (self.skip_connect_attempt() or
@@ -496,7 +842,7 @@
self.view_vm_status()
return
- error = _("Error: VNC connection to hypervisor host got refused "
+ error = _("Error: viewer connection to hypervisor host got refused "
"or disconnected!")
if errout:
logging.debug("Error output from closed console: %s" % errout)
@@ -504,125 +850,27 @@
self.activate_unavailable_page(error)
- def _vnc_initialized(self, src):
- self.vnc_connected = True
- logging.debug("VNC initialized")
+ def connected(self):
+ self.viewer_connected = True
+ logging.debug("Viewer initialized")
self.activate_viewer_page()
# Had a succesfull connect, so reset counters now
- self.vncViewerRetriesScheduled = 0
- self.vncViewerRetryDelay = 125
+ self.viewerRetriesScheduled = 0
+ self.viewerRetryDelay = 125
def schedule_retry(self):
- if self.vncViewerRetriesScheduled >= 10:
+ if self.viewerRetriesScheduled >= 10:
logging.error("Too many connection failures, not retrying again")
return
- util.safe_timeout_add(self.vncViewerRetryDelay, self.try_login)
+ util.safe_timeout_add(self.viewerRetryDelay, self.try_login)
- if self.vncViewerRetryDelay < 2000:
- self.vncViewerRetryDelay = self.vncViewerRetryDelay * 2
-
- def open_tunnel(self, server, vncaddr, vncport, username, sshport):
- if self.vncTunnel is not None:
- return -1
-
- # Build SSH cmd
- argv = ["ssh", "ssh"]
- if sshport:
- argv += ["-p", str(sshport)]
-
- if username:
- argv += ['-l', username]
-
- argv += [ server ]
-
- # 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.
- #
- nc_params = "%s %s" % (vncaddr, str(vncport))
- 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.vncTunnel = [fds[0], errorfds[0], pid]
-
- return fds[0].fileno()
-
- def close_tunnel(self):
- if self.vncTunnel is None:
- return
-
- logging.debug("Shutting down tunnel PID=%d OUTFD=%d ERRFD=%d" %
- (self.vncTunnel[2], self.vncTunnel[0].fileno(),
- self.vncTunnel[1].fileno()))
- self.vncTunnel[0].close()
- self.vncTunnel[1].close()
-
- os.kill(self.vncTunnel[2], signal.SIGKILL)
- self.vncTunnel = None
-
- def get_tunnel_err_output(self):
- errfd = self.vncTunnel[1]
- errout = ""
- while True:
- try:
- new = errfd.recv(1024)
- except:
- break
-
- if not new:
- break
-
- errout += new
-
- return errout
+ if self.viewerRetryDelay < 2000:
+ self.viewerRetryDelay = self.viewerRetryDelay * 2
def skip_connect_attempt(self):
- return (self.vnc_connected or
+ return (self.viewer_connected or
not self.is_visible())
def guest_not_avail(self):
@@ -644,8 +892,8 @@
try:
(protocol, connhost,
- vncport, trans, username,
- connport, vncuri) = self.vm.get_graphics_console()
+ gport, trans, username,
+ connport, guri) = self.vm.get_graphics_console()
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
@@ -658,36 +906,44 @@
_("Graphical console not configured for guest"))
return
- if protocol != "vnc":
- logging.debug("Not a VNC console, disabling")
+ if protocol not in ["vnc", "spice"]:
+ logging.debug("Not a VNC or SPICE console, disabling")
self.activate_unavailable_page(
_("Graphical console not supported for guest"))
return
- if vncport == -1:
+ if gport == -1:
self.activate_unavailable_page(
_("Graphical console is not yet active for guest"))
self.schedule_retry()
return
+ 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.activate_unavailable_page(
_("Connecting to graphical console for guest"))
logging.debug("Starting connect process for %s: %s %s" %
- (vncuri, connhost, str(vncport)))
+ (guri, connhost, str(gport)))
try:
if trans in ("ssh", "ext"):
- if self.vncTunnel:
+ if self.tunnels:
# Tunnel already open, no need to continue
return
- fd = self.open_tunnel(connhost, "127.0.0.1", vncport,
- username, connport)
+ self.tunnels = Tunnels(connhost, "127.0.0.1", gport,
+ username, connport)
+ fd = self.tunnels.open_new()
if fd >= 0:
- self.vncViewer.open_fd(fd)
+ self.viewer.open_fd(fd)
else:
- self.vncViewer.open_host(connhost, str(vncport))
+ self.viewer.open_host(guri, connhost, str(gport))
except:
(typ, value, stacktrace) = sys.exc_info ()
@@ -700,45 +956,16 @@
def set_credentials(self, src=None):
passwd = self.window.get_widget("console-auth-password")
if passwd.flags() & gtk.VISIBLE:
- self.vncViewer.set_credential(gtkvnc.CREDENTIAL_PASSWORD,
- passwd.get_text())
+ self.viewer.set_credential_password(passwd.get_text())
+
username = self.window.get_widget("console-auth-username")
if username.flags() & gtk.VISIBLE:
- self.vncViewer.set_credential(gtkvnc.CREDENTIAL_USERNAME,
- username.get_text())
+ self.viewer.set_credential_username(username.get_text())
if self.window.get_widget("console-auth-remember").get_active():
self.config.set_console_password(self.vm, passwd.get_text(),
username.get_text())
- def _vnc_auth_credential(self, src, credList):
- for i in range(len(credList)):
- if credList[i] not in [gtkvnc.CREDENTIAL_PASSWORD,
- gtkvnc.CREDENTIAL_USERNAME,
- gtkvnc.CREDENTIAL_CLIENTNAME]:
- self.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)
- self.vncViewerRetriesScheduled = 10
- self.vncViewer.close()
- self.activate_unavailable_page(_("Unsupported console authentication type"))
- 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.vncViewer.set_credential(credList[i], "libvirt-vnc")
-
- if withUsername or withPassword:
- self.activate_auth_page(withPassword, withUsername)
-
def desktop_resize(self, src, w, h):
self.desktop_resolution = (w, h)
self.window.get_widget("console-vnc-scroll").queue_resize()
@@ -753,7 +980,7 @@
signal_holder = []
def restore_scroll(src):
- is_scale = self.vncViewer.get_scaling()
+ is_scale = self.viewer.get_scaling()
if is_scale:
w_policy = gtk.POLICY_NEVER
@@ -790,48 +1017,48 @@
widget.queue_resize()
def scroll_size_allocate(self, src, req):
- if not self.desktop_resolution:
+ if not self.viewer or not self.desktop_resolution:
return
scroll = self.window.get_widget("console-vnc-scroll")
- is_scale = self.vncViewer.get_scaling()
+ is_scale = self.viewer.get_scaling()
dx = 0
dy = 0
align_ratio = float(req.width) / float(req.height)
- vnc_w, vnc_h = self.desktop_resolution
- vnc_ratio = float(vnc_w) / float(vnc_h)
+ desktop_w, desktop_h = self.desktop_resolution
+ desktop_ratio = float(desktop_w) / float(desktop_h)
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)
- self.vncViewer.set_size_request(vnc_w, vnc_h)
+ self.viewer.get_widget().set_size_request(desktop_w, desktop_h)
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
- self.vncViewer.set_size_request(-1, -1)
+ self.viewer.get_widget().set_size_request(-1, -1)
# Make sure desktop aspect ratio is maintained
- if align_ratio > vnc_ratio:
- vnc_w = int(req.height * vnc_ratio)
- vnc_h = req.height
- dx = (req.width - vnc_w) / 2
+ if align_ratio > desktop_ratio:
+ desktop_w = int(req.height * desktop_ratio)
+ desktop_h = req.height
+ dx = (req.width - desktop_w) / 2
else:
- vnc_w = req.width
- vnc_h = int(req.width / vnc_ratio)
- dy = (req.height - vnc_h) / 2
+ desktop_w = req.width
+ desktop_h = int(req.width / desktop_ratio)
+ dy = (req.height - desktop_h) / 2
- vnc_alloc = gtk.gdk.Rectangle(x=dx,
+ viewer_alloc = gtk.gdk.Rectangle(x=dx,
y=dy,
- width=vnc_w,
- height=vnc_h)
+ width=desktop_w,
+ height=desktop_h)
- self.vncViewer.size_allocate(vnc_alloc)
+ self.viewer.get_widget().size_allocate(viewer_alloc)
gobject.type_register(vmmConsolePages)
diff -r 01667175a00b -r 36976fe992df src/virtManager/create.py
--- a/src/virtManager/create.py Fri Dec 17 14:33:35 2010 +0100
+++ b/src/virtManager/create.py Wed Dec 01 17:42:45 2010 +0100
@@ -1118,7 +1118,7 @@
# Set up graphics device
try:
guest._graphics_dev = virtinst.VirtualGraphics(
- type=virtinst.VirtualGraphics.TYPE_VNC,
+ type=virtinst.VirtualGraphics.TYPE_SPICE,
conn=guest.conn)
guest.add_device(virtinst.VirtualVideoDevice(conn=guest.conn))
except Exception, e:
diff -r 01667175a00b -r 36976fe992df src/virtManager/details.py
--- a/src/virtManager/details.py Fri Dec 17 14:33:35 2010 +0100
+++ b/src/virtManager/details.py Wed Dec 01 17:42:45 2010 +0100
@@ -454,11 +454,12 @@
return
self.topwin.hide()
- if self.console.vncViewer.flags() & gtk.VISIBLE:
+ if self.console.viewer and self.console.viewer.get_widget() and \
+ self.console.viewer.get_widget().flags() & gtk.VISIBLE:
try:
- self.console.vncViewer.close()
+ self.console.close_viewer()
except:
- logging.error("Failure when disconnecting from VNC server")
+ logging.error("Failure when disconnecting from virtual desktop server")
self.engine.decrement_window_counter()
self.emit("details-closed")
@@ -1085,10 +1086,12 @@
filename = path
if not(filename.endswith(".png")):
filename += ".png"
- image = self.console.vncViewer.get_pixbuf()
-
- # Save along with a little metadata about us & the domain
- image.save(filename, 'png',
+
+ if self.console.viewer:
+ image = self.console.viewer.get_pixbuf()
+
+ # Save along with a little metadata about us & the domain
+ image.save(filename, 'png',
{ 'tEXt::Hypervisor URI': self.vm.get_connection().get_uri(),
'tEXt::Domain Name': self.vm.get_name(),
'tEXt::Domain UUID': self.vm.get_uuid(),
diff -r 01667175a00b -r 36976fe992df src/virtManager/preferences.py
--- a/src/virtManager/preferences.py Fri Dec 17 14:33:35 2010 +0100
+++ b/src/virtManager/preferences.py Wed Dec 01 17:42:45 2010 +0100
@@ -154,11 +154,10 @@
prefs_button = self.window.get_widget("prefs-keys-grab-changebtn")
self.window.get_widget("prefs-keys-grab-sequence").set_text(val)
- if not self.config.grab_keys_supported():
+ if not self.config.vnc_grab_keys_supported():
util.tooltip_wrapper(prefs_button,
_("Installed version of GTK-VNC doesn't "
"support configurable grab keys"))
- prefs_button.set_sensitive(False)
def refresh_confirm_forcepoweroff(self, ignore1=None, ignore2=None,
ignore3=None, ignore4=None):
More information about the virt-tools-list
mailing list