The branch, master has been updated via 01ea13f9b472bf7139c0640fbddb1cf4309c0861 (commit) from 3133baf8a3c0e8dc6d80bad8d5aef6b0d0f34862 (commit) Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below. - Log ----------------------------------------------------------------- commit 01ea13f9b472bf7139c0640fbddb1cf4309c0861 Author: Mike Gabriel <mike.gabriel@das-netzwerkteam.de> Date: Sun Jan 20 12:14:34 2013 +0100 Improve desktop sharing code. Add code to obtain version information of server-side X2Go components. ----------------------------------------------------------------------- Summary of changes: debian/changelog | 2 + x2go/backends/control/_stdout.py | 54 ++++++++++++++++++--- x2go/backends/terminal/_stdout.py | 57 ++++++++++++++++++++++ x2go/client.py | 84 ++++++++++++++++++++++++++++++-- x2go/session.py | 95 ++++++++++++++++++++++++++++++------- 5 files changed, 263 insertions(+), 29 deletions(-) The diff of changes is: diff --git a/debian/changelog b/debian/changelog index 87f6a60..b9c9ed2 100644 --- a/debian/changelog +++ b/debian/changelog @@ -25,6 +25,8 @@ python-x2go (0.4.0.0-0~x2go1) UNRELEASED; urgency=low - Fix auto-starting and auto-resuming of sessions. - Avoid false-positive notifications of dead control session directly after a disconnect request from the user. + - Improve desktop sharing code. Add code to obtain version information of + server-side X2Go components. -- Mike Gabriel <mike.gabriel@das-netzwerkteam.de> Thu, 20 Dec 2012 08:58:44 +0100 diff --git a/x2go/backends/control/_stdout.py b/x2go/backends/control/_stdout.py index b8c4c10..52eee5f 100644 --- a/x2go/backends/control/_stdout.py +++ b/x2go/backends/control/_stdout.py @@ -194,6 +194,7 @@ class X2GoControlSessionSTDOUT(paramiko.SSHClient): self._remote_username = None self._remote_peername = None + self._server_versions = None self._server_features = None if logger is None: @@ -439,18 +440,51 @@ class X2GoControlSessionSTDOUT(paramiko.SSHClient): return _retval @property + def _x2go_server_versions(self): + """\ + Render a dictionary of server-side X2Go components and their versions. Results get cached + once there has been one successful query. + + """ + if self._server_versions is None: + self._server_versions = {} + (stdin, stdout, stderr) = self._x2go_exec_command('which x2goversion >/dev/null && x2goversion') + _lines = stdout.read().split('\n') + for _line in _lines: + if ':' not in _line: continue + comp = _line.split(':')[0].strip() + version = _line.split(':')[1].strip() + self._server_versions.update({comp: version}) + self.logger('server-side X2Go components and their versions are: %s' % self._server_versions, loglevel=log.loglevel_DEBUG) + return self._server_versions + + def query_server_versions(self, force=False): + """\ + Do a query for the server-side list of X2Go components and their versions. + + @param force: do not use the cached component list, really ask the server (again) + @type force: C{bool} + + @return: dictionary of X2Go components (as keys) and their versions (as values) + @rtype: C{list} + + """ + if force: + self._server_versions = None + return self._x2go_server_versions + get_server_versions = query_server_versions + + @property def _x2go_server_features(self): """\ - Render a list of server-side X2Go features. Results get cached once there has been one successfull query. + Render a list of server-side X2Go features. Results get cached once there has been one successful query. """ if self._server_features is None: (stdin, stdout, stderr) = self._x2go_exec_command('which x2gofeaturelist >/dev/null && x2gofeaturelist') self._server_features = stdout.read().split('\n') self.logger('server-side X2Go features are: %s' % self._server_features, loglevel=log.loglevel_DEBUG) - return self._server_features - else: - return self._server_features + return self._server_features def query_server_features(self, force=False): """\ @@ -1333,7 +1367,7 @@ class X2GoControlSessionSTDOUT(paramiko.SSHClient): user = desktop.split('@')[0] display = desktop.split('@')[1] if not (user and display): - raise x2go_exceptions.X2GoDesktopSharingException('Need user name and display number of sharable desktop.') + raise x2go_exceptions.X2GoDesktopSharingException('Need user name and display number of shared desktop.') cmd = '%sXSHAD%sXSHAD%s' % (share_mode, user, display) @@ -1443,7 +1477,10 @@ class X2GoControlSessionSTDOUT(paramiko.SSHClient): be interpreted as disconnected due to connection loss """ if raw: - (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && x2golistsessions") + if 'X2GO_LIST_SHADOWSESSIONS' in self._x2go_server_features: + (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && { x2golistsessions; x2golistshadowsessions; }") + else: + (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && x2golistsessions") return stdout.read(), stderr.read() else: @@ -1460,7 +1497,10 @@ class X2GoControlSessionSTDOUT(paramiko.SSHClient): while not _success and _count < _maxwait: _count += 1 try: - (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && x2golistsessions") + if 'X2GO_LIST_SHADOWSESSIONS' in self._x2go_server_features: + (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && { x2golistsessions; x2golistshadowsessions; }") + else: + (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && x2golistsessions") _stdout_read = stdout.read() _listsessions = self._list_backend(_stdout_read, info_backend=self._info_backend).sessions _success = True diff --git a/x2go/backends/terminal/_stdout.py b/x2go/backends/terminal/_stdout.py index 0156700..4872832 100644 --- a/x2go/backends/terminal/_stdout.py +++ b/x2go/backends/terminal/_stdout.py @@ -983,6 +983,18 @@ class X2GoTerminalSessionSTDOUT(object): # session title fallback... (like X2Go server does it...) self.session_title = _generic_title + elif self.params.session_type == 'S': + if self.set_session_title: + + shared_user = _generic_title.split('XSHAD')[1] + shared_display = _generic_title.split('XSHAD')[2].replace('PP', ':').split("_")[0] + + self.session_title = 'Desktop %s@%s shared with %s@%s' % (shared_user, shared_display, self.control_session.remote_username(), self.control_session.get_hostname()) + + else: + # session title fallback... (like X2Go server does it...) + self.session_title = _generic_title + else: # do nothing for rootless sessions self.session_title = _generic_title @@ -1616,3 +1628,48 @@ class X2GoTerminalSessionSTDOUT(object): self._rm_desktop_dirtree() self._cleaned_up = True + + def is_desktop_session(self): + """\ + Test if this terminal session is a desktop session. + + @return: C{True} if this session is of session type desktop ('D'). + @rtype: C{bool} + + """ + self.params.rewrite_session_type() + return self.params.session_type == 'D' + + def is_rootless_session(self): + """\ + Test if this terminal session is a rootless session. + + @return: C{True} if this session is of session type rootless ('R'). + @rtype: C{bool} + + """ + self.params.rewrite_session_type() + return self.params.session_type == 'R' + + def is_shadow_session(self): + """\ + Test if this terminal session is a desktop sharing (aka shadow) session. + + @return: C{True} if this session is of session type shadow ('S'). + @rtype: C{bool} + + """ + self.params.rewrite_session_type() + return self.params.session_type == 'S' + + def is_pubapp_session(self): + """\ + Test if this terminal session is a published applications session. + + @return: C{True} if this session is of session type published applications ('P'). + @rtype: C{bool} + + """ + self.params.rewrite_session_type() + return self.params.session_type == 'P' + diff --git a/x2go/client.py b/x2go/client.py index 3a091f2..b7fd646 100644 --- a/x2go/client.py +++ b/x2go/client.py @@ -38,6 +38,7 @@ Supported Features - enabling and mangaging X2Go printing (real printing, viewing as PDF, saving to a local folder or executing a custom »print« command - transparent tunneling of audio (Pulseaudio, ESD) + - sharing of other desktops - LDAP support for X2Go server clusters (NOT IMPLEMENTED YET) Non-Profile Sessions @@ -384,6 +385,20 @@ class X2GoClient(object): """ self.logger('HOOK_session_startup_failed: session startup for session profile ,,%s'' failed.' % profile_name, loglevel=log.loglevel_WARN) + def HOOK_list_desktops_timeout(self, profile_name='UNKNOWN'): + """\ + HOOK method: called if the x2golistdesktops command generates a timeout due to long execution time. + + """ + self.logger('HOOK_list_desktops_timeout: the server-side x2golistdesktops command for session profile %s took too long to return results. This can happen from time to time, please try again.' % profile_name, loglevel=log.loglevel_WARN) + + def HOOK_no_such_desktop(self, profile_name='UNKNOWN', desktop='UNKNOWN'): + """\ + HOOK method: called if it is tried to connect to a (seen before) sharable desktop that's not available (anymore). + + """ + self.logger('HOOK_no_such_desktop: the desktop %s (via session profile %s) is not available for sharing (anymore).' % (desktop, profile_name), loglevel=log.loglevel_WARN) + def HOOK_no_known_xserver_found(self): """\ HOOK method: called if the Python X2Go module could not find any usable XServer @@ -477,7 +492,6 @@ class X2GoClient(object): # this HOOK has to return either True (accept host connection) or False (deny host conection) return True - def HOOK_on_control_session_death(self, profile_name): """\ HOOK method: called if a control session (server connection) has unexpectedly encountered a failure. @@ -1465,7 +1479,7 @@ class X2GoClient(object): return _retval __start_session = start_session - def share_desktop_session(self, session_uuid, desktop=None, user=None, display=None, share_mode=0, **sessionopts): + def share_desktop_session(self, session_uuid, desktop=None, user=None, display=None, share_mode=0, check_desktop_list=False, **sessionopts): """\ Share another already running desktop session. Desktop sharing can be run in two different modes: view-only and full-access mode. Like new sessions @@ -1503,10 +1517,8 @@ class X2GoClient(object): if not _desktop in self._X2GoClient__list_desktops(session_uuid): _orig_desktop = _desktop _desktop = '%s.0' % _desktop - if not _desktop in self._X2GoClient__list_desktops(session_uuid): - raise x2go_exceptions.X2GoDesktopSharingException('No such desktop ID: %s' % _orig_desktop) - return self.session_registry(session_uuid).share_desktop(desktop=_desktop, share_mode=share_mode, check_desktop_list=False, **sessionopts) + return self.session_registry(session_uuid).share_desktop(desktop=_desktop, share_mode=share_mode, check_desktop_list=check_desktop_list, **sessionopts) __share_desktop_session = share_desktop_session def resume_session(self, session_uuid=None, session_name=None, match_profile_name=None, **sessionopts): @@ -2022,6 +2034,7 @@ class X2GoClient(object): @type return_profile_ids: C{bool} @param return_session_names: return as list of session names @type return_session_names: C{bool} + @return: list of connected sessions @rtype: C{list} @@ -2050,6 +2063,7 @@ class X2GoClient(object): @type return_profile_ids: C{bool} @param return_session_names: return as list of session names @type return_session_names: C{bool} + @return: list of associated sessions @rtype: C{list} @@ -2078,6 +2092,7 @@ class X2GoClient(object): @type return_profile_ids: C{bool} @param return_session_names: return as list of session names @type return_session_names: C{bool} + @return: list of running sessions @rtype: C{list} @@ -2106,6 +2121,7 @@ class X2GoClient(object): @type return_profile_ids: C{bool} @param return_session_names: return as list of session names @type return_session_names: C{bool} + @return: list of suspended sessions @rtype: C{list} @@ -2134,6 +2150,7 @@ class X2GoClient(object): @type return_profile_ids: C{bool} @param return_session_names: return as list of session names @type return_session_names: C{bool} + @return: list of registered sessions @rtype: C{list} @@ -2164,6 +2181,63 @@ class X2GoClient(object): return self.session_registry.control_session_of_profile_name(profile_name) __client_control_session_of_profile_name = client_control_session_of_profile_name + def get_server_versions(self, profile_name, component=None): + """\ + Query the server configured in session profile <profile_name> for the list of install X2Go components + and its versions. + + @param profile_name: use the control session of this profile to query the X2Go server for its component list + @type profile_name: C{str} + @param component: only return the version of a specific component + @type component: C{str} + + @return: dictionary of server components (as keys) and their versions (as values) or the version of the given <component> + @rtype: C{dict} or C{str} + + @raise X2GoClientException: if component is not available on the X2Go Server. + + """ + control_session = self.client_control_session_of_profile_name(profile_name) + if component is None: + return control_session.get_server_versions() + else: + try: + return control_session.get_server_versions()[component] + except KeyError: + raise x2go_exceptions.X2GoClientException('No such component on X2Go Server') + + def get_server_features(self, profile_name): + """\ + Query the server configured in session profile <profile_name> for the list of server-side + X2Go features. + + @param profile_name: use the control session of this profile to query the X2Go server for its feature list + @type profile_name: C{str} + + @return: list of server feature names (as returned by server-side command ,,x2gofeaturelist'' + @rtype: C{list} + + """ + control_session = self.client_control_session_of_profile_name(profile_name) + return control_session.get_server_features() + + def has_server_feature(self, profile_name, feature): + """\ + Query the server configured in session profile <profile_name> for the availability + of a certain server feature. + + @param profile_name: use the control session of this profile to query the X2Go server for its feature + @type profile_name: C{str} + @param feature: test the availability of this feature on the X2Go server + @type feature: C{str} + + @return: C{True} if the feature is available on the queried server + @rtype: C{bool} + + """ + control_session = self.client_control_session_of_profile_name(profile_name) + return feature in control_session.get_server_features() + def client_registered_session_of_name(self, session_name, return_object=False): """\ Retrieve X2Go session of a given session name. diff --git a/x2go/session.py b/x2go/session.py index 2284c09..fbe1bf3 100644 --- a/x2go/session.py +++ b/x2go/session.py @@ -467,6 +467,26 @@ class X2GoSession(object): else: self.logger('HOOK_session_startup_failed: session startup for session profile ,,%s\'\' failed.' % self.profile_name, loglevel=log.loglevel_WARN) + def HOOK_list_desktops_timeout(self): + """\ + HOOK method: called if the x2golistdesktops command generates a timeout due to long execution time. + + """ + if self.client_instance: + self.client_instance.HOOK_list_desktops_timeout(profile_name=self.profile_name) + else: + self.logger('HOOK_list_desktops_timeout: the server-side x2golistdesktops command for session profile %s took too long to return results. This can happen from time to time, please try again.' % self.profile_name, loglevel=log.loglevel_WARN) + + def HOOK_no_such_desktop(self, desktop='UNKNOWN'): + """\ + HOOK method: called if it is tried to connect to a shared desktop that's not available (anymore). + + """ + if self.client_instance: + self.client_instance.HOOK_no_such_desktop(profile_name=self.profile_name, desktop=desktop) + else: + self.logger('HOOK_no_such_desktop: the desktop %s (via session profile %s) is not available for sharing (anymore).' % (desktop, self.profile_name), loglevel=log.loglevel_WARN) + def HOOK_rforward_request_denied(self, server_port=0): """\ HOOK method: called if a reverse port forwarding request has been denied. @@ -1451,6 +1471,9 @@ class X2GoSession(object): """ try: return self.control_session.list_desktops(raw=raw) + except x2go_exceptions.X2GoTimeoutException: + if self.is_alive(): self.HOOK_list_desktop_timeout() + return [] except x2go_exceptions.X2GoControlSessionException: if self.connected: self.HOOK_on_control_session_death() self._X2GoSession__disconnect() @@ -1570,19 +1593,6 @@ class X2GoSession(object): return False __is_published_applications_provider = is_published_applications_provider - def is_desktop_session(self): - """\ - Returns true if this session is configured as desktop session. - - @return: returns C{True} if this session is a desktop session. - @rtype: C{bool} - - """ - if self.has_terminal_session(): - return self.terminal_session.is_desktop_session() - return False - __is_desktop_session = is_desktop_session - def get_published_applications(self, lang=None, refresh=False, raw=False, very_raw=False, max_no_submenus=defaults.PUBAPP_MAX_NO_SUBMENUS): """\ Return a list of published menu items from the X2Go server @@ -1928,7 +1938,6 @@ class X2GoSession(object): self._X2GoSession__disconnect() return False - __resume = resume def start(self, cmd=None, progress_event=None): @@ -2029,11 +2038,15 @@ class X2GoSession(object): _desktop = desktop or '%s@%s' % (user, display) if check_desktop_list: - if not _desktop in self._X2GoSession__list_desktops(): + desktop_list = self._X2GoSession__list_desktops() + if not _desktop in desktop_list: _orig_desktop = _desktop _desktop = '%s.0' % _desktop - if not _desktop in self._X2GoSession__list_desktops(): - raise x2go_exceptions.X2GoDesktopSharingException('No such desktop ID: %s' % _orig_desktop) + if not _desktop in desktop_list: + self.HOOK_no_such_desktop(desktop=_orig_desktop) + self._progress_status = -1 + progress_event.set() + return False self._progress_status = 33 progress_event.set() @@ -2101,6 +2114,54 @@ class X2GoSession(object): return False __share_desktop = share_desktop + def is_desktop_session(self): + """\ + Test if this X2Go session is a desktop session. + + @return: C{True} if this session is of session type desktop ('D'). + @rtype: C{bool} + + """ + if self.has_terminal_session(): + return self.terminal_session.is_desktop_session() + __is_desktop_session = is_desktop_session + + def is_rootless_session(self): + """\ + Test if this X2Go session is a rootless session. + + @return: C{True} if this session is of session type rootless ('R'). + @rtype: C{bool} + + """ + if self.has_terminal_session(): + return self.terminal_session.is_rootless_session() + __is_rootless_session = is_rootless_session + + def is_shadow_session(self): + """\ + Test if this X2Go session is a desktop sharing (aka shadow) session. + + @return: C{True} if this session is of session type shadow ('S'). + @rtype: C{bool} + + """ + if self.has_terminal_session(): + return self.terminal_session.is_shadow_session() + __is_shadow_session = is_shadow_session + + def is_pubapp_session(self): + """\ + Test if this X2Go session is a published applications session. + + @return: C{True} if this session is of session type published applications ('P'). + @rtype: C{bool} + + """ + if self.has_terminal_session(): + return self.terminal_session.is_pubapp_session() + __is_pubapp_session = is_pubapp_session + def suspend(self): """\ Suspend this X2Go session. hooks/post-receive -- python-x2go.git (Python X2Go Client API) This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "python-x2go.git" (Python X2Go Client API).