[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