2015-04-03 20:54:38 -04:00
#
# Copyright (C) 2006-2008, 2015 Red Hat, Inc.
# Copyright (C) 2006 Daniel P. Berrange <berrange@redhat.com>
# Copyright (C) 2010 Marc-Andre Lureau <marcandre.lureau@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
# 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.
#
from gi . repository import GObject
from gi . repository import Gdk
import gi
gi . require_version ( ' GtkVnc ' , ' 2.0 ' )
from gi . repository import GtkVnc
gi . require_version ( ' SpiceClientGtk ' , ' 3.0 ' )
from gi . repository import SpiceClientGtk
from gi . repository import SpiceClientGLib
import logging
import socket
from . baseclass import vmmGObject
from . sshtunnels import SSHTunnels
##################################
# VNC/Spice abstraction handling #
##################################
class Viewer ( vmmGObject ) :
"""
Base class for viewer abstraction
"""
__gsignals__ = {
" add-display-widget " : ( GObject . SignalFlags . RUN_FIRST , None , [ object ] ) ,
" size-allocate " : ( GObject . SignalFlags . RUN_FIRST , None , [ object ] ) ,
" focus-in-event " : ( GObject . SignalFlags . RUN_FIRST , None , [ object ] ) ,
" focus-out-event " : ( GObject . SignalFlags . RUN_FIRST , None , [ object ] ) ,
" pointer-grab " : ( GObject . SignalFlags . RUN_FIRST , None , [ ] ) ,
" pointer-ungrab " : ( GObject . SignalFlags . RUN_FIRST , None , [ ] ) ,
" connected " : ( GObject . SignalFlags . RUN_FIRST , None , [ ] ) ,
" disconnected " : ( GObject . SignalFlags . RUN_FIRST , None , [ ] ) ,
2015-04-12 13:04:32 -04:00
" auth-error " : ( GObject . SignalFlags . RUN_FIRST , None , [ str , bool ] ) ,
" auth-rejected " : ( GObject . SignalFlags . RUN_FIRST , None , [ str ] ) ,
2015-04-03 20:54:38 -04:00
" need-auth " : ( GObject . SignalFlags . RUN_FIRST , None , [ bool , bool ] ) ,
" agent-connected " : ( GObject . SignalFlags . RUN_FIRST , None , [ ] ) ,
" usb-redirect-error " : ( GObject . SignalFlags . RUN_FIRST , None , [ str ] ) ,
}
2015-04-11 21:39:24 -04:00
def __init__ ( self , ginfo ) :
2015-04-03 20:54:38 -04:00
vmmGObject . __init__ ( self )
self . _display = None
2015-04-11 21:39:24 -04:00
self . _ginfo = ginfo
self . _tunnels = SSHTunnels ( self . _ginfo )
2015-04-03 20:54:38 -04:00
self . add_gsettings_handle (
self . config . on_keys_combination_changed ( self . _refresh_grab_keys ) )
self . add_gsettings_handle (
self . config . on_keyboard_grab_default_changed (
self . _refresh_keyboard_grab_default ) )
self . connect ( " add-display-widget " , self . _common_init )
def _cleanup ( self ) :
self . close ( )
if self . _display :
self . _display . destroy ( )
self . _display = None
2015-04-12 13:04:32 -04:00
self . _tunnels . close_all ( )
2015-04-03 20:54:38 -04:00
########################
# Internal helper APIs #
########################
def _make_signal_proxy ( self , new_signal ) :
"""
Helper for redirecting a signal from self . _display out
through the viewer
"""
def _proxy_signal ( src , * args , * * kwargs ) :
ignore = src
self . emit ( new_signal , * args , * * kwargs )
return _proxy_signal
def _common_init ( self , ignore1 , ignore2 ) :
self . _refresh_grab_keys ( )
self . _refresh_keyboard_grab_default ( )
self . _display . connect ( " size-allocate " ,
self . _make_signal_proxy ( " size-allocate " ) )
self . _display . connect ( " focus-in-event " ,
self . _make_signal_proxy ( " focus-in-event " ) )
self . _display . connect ( " focus-out-event " ,
self . _make_signal_proxy ( " focus-out-event " ) )
#########################
# Generic internal APIs #
#########################
def _grab_focus ( self ) :
if self . _display :
self . _display . grab_focus ( )
def _has_focus ( self ) :
return self . _display and self . _display . get_property ( " has-focus " )
def _set_size_request ( self , * args , * * kwargs ) :
return self . _display . set_size_request ( * args , * * kwargs )
def _size_allocate ( self , * args , * * kwargs ) :
return self . _display . size_allocate ( * args , * * kwargs )
def _get_visible ( self ) :
return self . _display and self . _display . get_visible ( )
def _get_pixbuf ( self ) :
return self . _display . get_pixbuf ( )
2015-04-11 21:39:24 -04:00
def _open ( self ) :
if self . _ginfo . need_tunnel ( ) :
2015-04-03 20:54:38 -04:00
self . _open_fd ( self . _tunnels . open_new ( ) )
else :
2015-04-11 21:39:24 -04:00
self . _open_host ( )
2015-04-03 20:54:38 -04:00
def _get_grab_keys ( self ) :
return self . _display . get_grab_keys ( ) . as_string ( )
#######################################################
# Internal API that will be overwritten by subclasses #
#######################################################
def close ( self ) :
raise NotImplementedError ( )
def _is_open ( self ) :
raise NotImplementedError ( )
def _set_username ( self , cred ) :
raise NotImplementedError ( )
def _set_password ( self , cred ) :
raise NotImplementedError ( )
def _send_keys ( self , keys ) :
raise NotImplementedError ( )
def _refresh_grab_keys ( self ) :
raise NotImplementedError ( )
def _refresh_keyboard_grab_default ( self ) :
raise NotImplementedError ( )
2015-04-11 21:39:24 -04:00
def _open_host ( self ) :
2015-04-03 20:54:38 -04:00
raise NotImplementedError ( )
def _open_fd ( self , fd ) :
raise NotImplementedError ( )
def _get_desktop_resolution ( self ) :
raise NotImplementedError ( )
def _get_scaling ( self ) :
raise NotImplementedError ( )
def _set_scaling ( self , val ) :
raise NotImplementedError ( )
def _set_resizeguest ( self , val ) :
raise NotImplementedError ( )
def _get_resizeguest ( self ) :
raise NotImplementedError ( )
def _get_usb_widget ( self ) :
raise NotImplementedError ( )
def _has_usb_redirection ( self ) :
raise NotImplementedError ( )
def _has_agent ( self ) :
raise NotImplementedError ( )
####################################
# APIs accessed by vmmConsolePages #
####################################
def console_is_open ( self ) :
return self . _is_open ( )
def console_grab_focus ( self ) :
return self . _grab_focus ( )
def console_has_focus ( self ) :
return self . _has_focus ( )
def console_set_size_request ( self , * args , * * kwargs ) :
return self . _set_size_request ( * args , * * kwargs )
def console_size_allocate ( self , * args , * * kwargs ) :
return self . _size_allocate ( * args , * * kwargs )
def console_get_visible ( self ) :
return self . _get_visible ( )
def console_get_pixbuf ( self ) :
return self . _get_pixbuf ( )
2015-04-11 21:39:24 -04:00
def console_open ( self ) :
return self . _open ( )
2015-04-03 20:54:38 -04:00
def console_set_password ( self , val ) :
return self . _set_password ( val )
def console_set_username ( self , val ) :
return self . _set_username ( val )
def console_send_keys ( self , keys ) :
return self . _send_keys ( keys )
2015-04-11 22:30:07 -04:00
def console_get_err_output ( self ) :
return self . _tunnels . get_err_output ( )
2015-04-03 20:54:38 -04:00
def console_get_grab_keys ( self ) :
return self . _get_grab_keys ( )
def console_get_desktop_resolution ( self ) :
return self . _get_desktop_resolution ( )
def console_get_scaling ( self ) :
return self . _get_scaling ( )
def console_set_scaling ( self , val ) :
return self . _set_scaling ( val )
def console_get_resizeguest ( self ) :
return self . _get_resizeguest ( )
def console_set_resizeguest ( self , val ) :
return self . _set_resizeguest ( val )
def console_get_usb_widget ( self ) :
return self . _get_usb_widget ( )
def console_has_usb_redirection ( self ) :
return self . _has_usb_redirection ( )
def console_has_agent ( self ) :
return self . _has_agent ( )
2015-04-12 10:33:41 -04:00
def console_remove_display_from_widget ( self , widget ) :
if self . _display and self . _display in widget . get_children ( ) :
widget . remove ( self . _display )
2015-04-03 20:54:38 -04:00
####################
# VNC viewer class #
####################
class VNCViewer ( Viewer ) :
viewer_type = " vnc "
def __init__ ( self , * args , * * kwargs ) :
Viewer . __init__ ( self , * args , * * kwargs )
self . _display = None
self . _sockfd = None
self . _desktop_resolution = None
###################
# Private helpers #
###################
def _init_widget ( self ) :
self . _display = GtkVnc . Display . new ( )
# Make sure viewer doesn't force resize itself
self . _display . set_force_size ( False )
self . _display . set_pointer_grab ( True )
self . emit ( " add-display-widget " , self . _display )
self . _display . realize ( )
self . _display . connect ( " vnc-pointer-grab " ,
self . _make_signal_proxy ( " pointer-grab " ) )
self . _display . connect ( " vnc-pointer-ungrab " ,
self . _make_signal_proxy ( " pointer-ungrab " ) )
self . _display . connect ( " vnc-auth-credential " , self . _auth_credential )
2015-04-12 13:04:32 -04:00
self . _display . connect ( " vnc-auth-failure " , self . _auth_failure_cb )
2015-04-03 20:54:38 -04:00
self . _display . connect ( " vnc-initialized " , self . _connected_cb )
self . _display . connect ( " vnc-disconnected " , self . _disconnected_cb )
self . _display . connect ( " vnc-desktop-resize " , self . _desktop_resize )
self . _display . show ( )
def _connected_cb ( self , ignore ) :
2015-04-11 21:39:24 -04:00
self . _tunnels . unlock ( )
2015-04-03 20:54:38 -04:00
self . emit ( " connected " )
def _disconnected_cb ( self , ignore ) :
2015-04-11 21:39:24 -04:00
self . _tunnels . unlock ( )
2015-04-03 20:54:38 -04:00
self . emit ( " disconnected " )
def _desktop_resize ( self , src_ignore , w , h ) :
self . _desktop_resolution = ( w , h )
# Queue a resize
self . emit ( " size-allocate " , None )
2015-04-12 13:04:32 -04:00
def _auth_failure_cb ( self , ignore , msg ) :
logging . debug ( " VNC auth failure. msg= %s " , msg )
self . emit ( " auth-error " , msg , True )
2015-04-03 20:54:38 -04:00
def _auth_credential ( self , src_ignore , credList ) :
values = [ ]
for idx in range ( int ( credList . n_values ) ) :
values . append ( credList . get_nth ( idx ) )
for cred in values :
if cred in [ GtkVnc . DisplayCredential . PASSWORD ,
GtkVnc . DisplayCredential . USERNAME ,
GtkVnc . DisplayCredential . CLIENTNAME ] :
continue
errmsg = ( _ ( " Unable to provide requested credentials to the VNC "
" server. \n The credential type %s is not supported " ) %
str ( cred ) )
2015-04-12 13:04:32 -04:00
# XXX test this
self . emit ( " auth-rejected " , errmsg )
2015-04-03 20:54:38 -04:00
return
withUsername = False
withPassword = False
for cred in values :
logging . debug ( " Got credential request %s " , cred )
if cred == GtkVnc . DisplayCredential . PASSWORD :
withPassword = True
elif cred == GtkVnc . DisplayCredential . USERNAME :
withUsername = True
elif cred == GtkVnc . DisplayCredential . CLIENTNAME :
self . _display . set_credential ( cred , " libvirt-vnc " )
if withUsername or withPassword :
self . emit ( " need-auth " , withPassword , withUsername )
###############################
# Private API implementations #
###############################
def close ( self ) :
self . _display . close ( )
2015-04-11 22:30:07 -04:00
if self . _sockfd :
self . _sockfd . close ( )
self . _sockfd = None
2015-04-03 20:54:38 -04:00
def _is_open ( self ) :
return self . _display . is_open ( )
def _get_scaling ( self ) :
return self . _display . get_scaling ( )
def _set_scaling ( self , scaling ) :
return self . _display . set_scaling ( scaling )
def _get_grab_keys ( self ) :
return self . _display . get_grab_keys ( ) . as_string ( )
def _refresh_grab_keys ( self ) :
if not self . _display :
return
try :
keys = self . config . get_keys_combination ( )
if not keys :
return
try :
keys = [ int ( k ) for k in keys . split ( ' , ' ) ]
except :
logging . debug ( " Error in grab_keys configuration in Gsettings " ,
exc_info = True )
return
seq = GtkVnc . GrabSequence . new ( keys )
self . _display . set_grab_keys ( seq )
except Exception , e :
logging . debug ( " Error when getting the grab keys combination: %s " ,
str ( e ) )
def _send_keys ( self , keys ) :
return self . _display . send_keys ( [ Gdk . keyval_from_name ( k ) for k in keys ] )
def _refresh_keyboard_grab_default ( self ) :
if not self . _display :
return
self . _display . set_keyboard_grab ( self . config . get_keyboard_grab_default ( ) )
def _get_desktop_resolution ( self ) :
return self . _desktop_resolution
def _set_username ( self , cred ) :
self . _display . set_credential ( GtkVnc . DisplayCredential . USERNAME , cred )
def _set_password ( self , cred ) :
self . _display . set_credential ( GtkVnc . DisplayCredential . PASSWORD , cred )
def _set_resizeguest ( self , val ) :
ignore = val
def _get_resizeguest ( self ) :
return False
def _get_usb_widget ( self ) :
return None
def _has_usb_redirection ( self ) :
return False
def _has_agent ( self ) :
return False
#######################
# Connection routines #
#######################
2015-04-11 21:39:24 -04:00
def _open ( self , * args , * * kwargs ) :
2015-04-03 20:54:38 -04:00
self . _init_widget ( )
2015-04-11 21:39:24 -04:00
return Viewer . _open ( self , * args , * * kwargs )
2015-04-03 20:54:38 -04:00
2015-04-11 21:39:24 -04:00
def _open_host ( self ) :
host , port , ignore = self . _ginfo . get_conn_host ( )
2015-04-03 20:54:38 -04:00
2015-04-11 21:39:24 -04:00
if not self . _ginfo . gsocket :
2015-04-03 20:54:38 -04:00
logging . debug ( " VNC connection to %s : %s " , host , port )
self . _display . open_host ( host , port )
return
2015-04-11 21:39:24 -04:00
logging . debug ( " VNC connecting to socket= %s " , self . _ginfo . gsocket )
2015-04-03 20:54:38 -04:00
try :
sock = socket . socket ( socket . AF_UNIX , socket . SOCK_STREAM )
2015-04-11 21:39:24 -04:00
sock . connect ( self . _ginfo . gsocket )
2015-04-03 20:54:38 -04:00
self . _sockfd = sock
except Exception , e :
raise RuntimeError ( _ ( " Error opening socket path ' %s ' : %s " ) %
2015-04-11 21:39:24 -04:00
( self . _ginfo . gsocket , e ) )
2015-04-03 20:54:38 -04:00
fd = self . _sockfd . fileno ( )
if fd < 0 :
raise RuntimeError ( ( _ ( " Error opening socket path ' %s ' " ) %
2015-04-11 21:39:24 -04:00
self . _ginfo . gsocket ) + " fd= %s " % fd )
2015-04-03 20:54:38 -04:00
self . _open_fd ( fd )
def _open_fd ( self , fd ) :
self . _display . open_fd ( fd )
######################
# Spice viewer class #
######################
class SpiceViewer ( Viewer ) :
viewer_type = " spice "
def __init__ ( self , * args , * * kwargs ) :
Viewer . __init__ ( self , * args , * * kwargs )
self . _spice_session = None
self . _display = None
self . _audio = None
self . _main_channel = None
self . _main_channel_hids = [ ]
self . _display_channel = None
self . _usbdev_manager = None
###################
# Private helpers #
###################
def _init_widget ( self ) :
self . emit ( " add-display-widget " , self . _display )
self . _display . realize ( )
self . _display . connect ( " mouse-grab " , self . _mouse_grab_event )
self . _display . show ( )
def _mouse_grab_event ( self , ignore , grab ) :
if grab :
self . emit ( " pointer-grab " )
else :
self . emit ( " pointer-ungrab " )
def _close_main_channel ( self ) :
for i in self . _main_channel_hids :
self . _main_channel . handler_disconnect ( i )
self . _main_channel_hids = [ ]
self . _main_channel = None
def _create_spice_session ( self ) :
self . _spice_session = SpiceClientGLib . Session ( )
SpiceClientGLib . set_session_option ( self . _spice_session )
gtk_session = SpiceClientGtk . GtkSession . get ( self . _spice_session )
gtk_session . set_property ( " auto-clipboard " , True )
GObject . GObject . connect ( self . _spice_session , " channel-new " ,
self . _channel_new_cb )
self . _usbdev_manager = SpiceClientGLib . UsbDeviceManager . get (
self . _spice_session )
self . _usbdev_manager . connect ( " auto-connect-failed " ,
self . _usbdev_redirect_error )
self . _usbdev_manager . connect ( " device-error " ,
self . _usbdev_redirect_error )
autoredir = self . config . get_auto_redirection ( )
if autoredir :
gtk_session . set_property ( " auto-usbredir " , True )
#####################
# Channel listeners #
#####################
def _main_channel_event_cb ( self , channel , event ) :
if event == SpiceClientGLib . ChannelEvent . CLOSED :
self . emit ( " disconnected " )
elif event == SpiceClientGLib . ChannelEvent . ERROR_AUTH :
2015-04-12 13:04:32 -04:00
if not self . _spice_session . get_property ( " password " ) :
logging . debug ( " Spice channel received ERROR_AUTH, but no "
" password set, assuming it wants credentials. " )
self . emit ( " need-auth " , True , False )
else :
logging . debug ( " Spice channel received ERROR_AUTH, but a "
" password is already set. Assuming authentication failed. " )
self . emit ( " auth-error " , channel . get_error ( ) . message , False )
2015-04-03 20:54:38 -04:00
elif event in [ SpiceClientGLib . ChannelEvent . ERROR_CONNECT ,
SpiceClientGLib . ChannelEvent . ERROR_IO ,
SpiceClientGLib . ChannelEvent . ERROR_LINK ,
SpiceClientGLib . ChannelEvent . ERROR_TLS ] :
logging . debug ( " Spice channel event error: %s " , event )
self . emit ( " disconnected " )
def _fd_channel_event_cb ( self , channel , event ) :
# When we see any event from the channel, release the
# associated tunnel lock
channel . disconnect_by_func ( self . _fd_channel_event_cb )
self . _tunnels . unlock ( )
def _channel_open_fd_request ( self , channel , tls_ignore ) :
2015-04-12 12:17:55 -04:00
if not self . _tunnels :
# Can happen if we close the details window and clear self._tunnels
# while initially connecting to spice and channel FD requests
# are still rolling in
return
logging . debug ( " Requesting tunnel for channel: %s " , channel )
2015-04-03 20:54:38 -04:00
channel . connect_after ( " channel-event " , self . _fd_channel_event_cb )
fd = self . _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 ) == SpiceClientGLib . MainChannel and
not self . _main_channel ) :
2015-04-11 21:39:24 -04:00
self . _tunnels . unlock ( )
2015-04-03 20:54:38 -04:00
self . _main_channel = channel
hid = self . _main_channel . connect_after ( " channel-event " ,
self . _main_channel_event_cb )
self . _main_channel_hids . append ( hid )
hid = self . _main_channel . connect_after ( " notify::agent-connected " ,
self . _agent_connected_cb )
self . _main_channel_hids . append ( hid )
elif ( type ( channel ) == SpiceClientGLib . DisplayChannel and
not self . _display ) :
channel_id = channel . get_property ( " channel-id " )
if channel_id != 0 :
logging . debug ( " Spice multi-head unsupported " )
return
self . _display_channel = channel
self . _display = SpiceClientGtk . Display . new ( self . _spice_session ,
channel_id )
self . _init_widget ( )
self . emit ( " connected " )
elif ( type ( channel ) in [ SpiceClientGLib . PlaybackChannel ,
SpiceClientGLib . RecordChannel ] and
not self . _audio ) :
self . _audio = SpiceClientGLib . Audio . get ( self . _spice_session , None )
def _agent_connected_cb ( self , src , val ) :
self . emit ( " agent-connected " )
################################
# Internal API implementations #
################################
def close ( self ) :
if self . _spice_session is not None :
self . _spice_session . disconnect ( )
self . _spice_session = None
self . _audio = None
if self . _display :
self . _display . destroy ( )
self . _display = None
self . _display_channel = None
self . _close_main_channel ( )
self . _usbdev_manager = None
def _is_open ( self ) :
return self . _spice_session is not None
def _refresh_grab_keys ( self ) :
if not self . _display :
return
try :
keys = self . config . get_keys_combination ( )
if not keys :
return
try :
keys = [ int ( k ) for k in keys . split ( ' , ' ) ]
except :
logging . debug ( " Error in grab_keys configuration in Gsettings " ,
exc_info = True )
return
seq = SpiceClientGtk . GrabSequence . new ( keys )
self . _display . set_grab_keys ( seq )
except Exception , e :
logging . debug ( " Error when getting the grab keys combination: %s " ,
str ( e ) )
def _send_keys ( self , keys ) :
return self . _display . send_keys ( [ Gdk . keyval_from_name ( k ) for k in keys ] ,
SpiceClientGtk . DisplayKeyEvent . CLICK )
def _refresh_keyboard_grab_default ( self ) :
if not self . _display :
return
self . _display . set_property ( " grab-keyboard " ,
self . config . get_keyboard_grab_default ( ) )
def _get_desktop_resolution ( self ) :
if not self . _display_channel :
return None
return self . _display_channel . get_properties ( " width " , " height " )
def _has_agent ( self ) :
if not self . _main_channel :
return False
return self . _main_channel . get_property ( " agent-connected " )
2015-04-11 21:39:24 -04:00
def _open_host ( self ) :
host , port , tlsport = self . _ginfo . get_conn_host ( )
2015-04-03 20:54:38 -04:00
self . _create_spice_session ( )
self . _spice_session . set_property ( " host " , str ( host ) )
if port :
self . _spice_session . set_property ( " port " , str ( port ) )
if tlsport :
self . _spice_session . set_property ( " tls-port " , str ( tlsport ) )
self . _spice_session . connect ( )
def _open_fd ( self , fd ) :
self . _create_spice_session ( )
self . _spice_session . open_fd ( fd )
def _set_username ( self , val ) :
ignore = val
def _set_password ( self , cred ) :
self . _spice_session . set_property ( " password " , cred )
2015-04-12 11:06:15 -04:00
if self . _ginfo . need_tunnel ( ) :
self . _spice_session . open_fd ( self . _tunnels . open_new ( ) )
2015-04-03 20:54:38 -04:00
else :
self . _spice_session . connect ( )
def _get_scaling ( self ) :
return self . _display . get_property ( " scaling " )
def _set_scaling ( self , scaling ) :
self . _display . set_property ( " scaling " , scaling )
def _set_resizeguest ( self , val ) :
if self . _display :
self . _display . set_property ( " resize-guest " , val )
def _get_resizeguest ( self ) :
if self . _display :
return self . _display . get_property ( " resize-guest " )
return False
def _usbdev_redirect_error ( self , spice_usbdev_widget , spice_usb_device ,
errstr ) :
ignore = spice_usbdev_widget
ignore = spice_usb_device
self . emit ( " usb-redirect-error " , errstr )
def _get_usb_widget ( self ) :
if not self . _spice_session :
return
usbwidget = SpiceClientGtk . UsbDeviceWidget . new ( self . _spice_session ,
None )
usbwidget . connect ( " connect-failed " , self . _usbdev_redirect_error )
return usbwidget
def _has_usb_redirection ( self ) :
if not self . _spice_session or not self . _usbdev_manager :
return False
for c in self . _spice_session . get_channels ( ) :
if c . __class__ is SpiceClientGLib . UsbredirChannel :
return True
return False