[X2go-Commits] python-x2go.git - build-main (branch) updated: 0.2.1.1

X2Go dev team git-admin at x2go.org
Tue Dec 18 12:44:48 CET 2012


The branch, build-main has been updated
       via  36689cb17e965b1d5437e2235aebe639abe17831 (commit)
       via  453d11e530cc18fe7d88598bad7bbb13fc1cc6f2 (commit)
       via  6efedf038a16505e6cd6c20656b5b9c688da3d6a (commit)
       via  a68e15b9694d2319af2881e7aefa5f24527e05a1 (commit)
       via  3622a7fdaa275265faded8dc826661f329286848 (commit)
       via  b82e7b9674904d0432c6f9fc19a95ec2bb852b51 (commit)
       via  7be3540dfdfa45f76b2d2feccaa9c546b4399609 (commit)
       via  971b6aa66ba93d86bba14ff0b6dc74911773d175 (commit)
       via  a30ff312cbb337dec2cb03f248d2e347910ad688 (commit)
       via  b47b28ff3c27d2dc75a5c18281867c2119790cc0 (commit)
      from  86a4bfdb9c720bc4dd24a59dc9bf7b2403f52005 (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 -----------------------------------------------------------------
-----------------------------------------------------------------------

Summary of changes:
 debian/changelog                  |   28 ++++++++++---
 x2go/__init__.py                  |    2 +-
 x2go/backends/control/_stdout.py  |   12 ++++++
 x2go/backends/terminal/_stdout.py |    7 +++-
 x2go/cache.py                     |   20 +++++----
 x2go/client.py                    |   82 +++++++++++++++++++++++--------------
 x2go/registry.py                  |   19 ++++++---
 7 files changed, 115 insertions(+), 55 deletions(-)

The diff of changes is:
diff --git a/debian/changelog b/debian/changelog
index 9baeb62..4e01b79 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,21 @@
+python-x2go (0.2.1.1-0~x2go1) unstable; urgency=low
+
+  * New upstream version (0.2.1.1):
+    - Make sure that internal calls to _X2goClient__list_sessions are not
+      overridden by other method definitions in classes that inherit from
+      X2goClient class. (Fixes: #89).
+    - Rewrite colour depth of 17bits to 16bits when invoking rdesktop.
+      Relevant for X2Go-proxied RDP sessions started with PyHoca-GUI under
+      Windows.
+    - Handle control sessions being None in session list cache.
+    - In cases where several session profiles connect to the same machine
+      under the same user ID, we have to strictly differentiate between
+      running/suspend sessions associated to the several connected session
+      profiles.
+    - Make connection disruptures more robust.
+
+ -- Mike Gabriel <mike.gabriel at das-netzwerkteam.de>  Tue, 18 Dec 2012 12:36:34 +0100
+
 python-x2go (0.2.1.0-0~x2go1) unstable; urgency=low
 
   * New upstream version (0.2.1.0):
@@ -9,8 +27,7 @@ python-x2go (0.2.1.0-0~x2go1) unstable; urgency=low
     - Implementation of session profile parameters ,,sshproxysameuser''
       and ,,sshproxysameauth''.
     - Fixing typos in __doc__ strings.
-    - Add support for starting maximized session windows. Closes upstream
-      issue #12.
+    - Add support for starting maximized session windows. (Fixes: #12).
     - Fix category ,,Utility'' when parsing .desktop files.
     - For ,,maxdim'' session property fallback to _NET_DESKTOP_GEOMETRY if
       _NET_WORKAREA is not available from the window manager.
@@ -32,8 +49,7 @@ python-x2go (0.2.1.0-0~x2go1) unstable; urgency=low
     - Wait for mounting of print and mimebox spooling share.
     - Allow mixing key file, key object, key discovery and agent
       authentication.
-    - Add progress bar support for session startup / resuming. Closes upstream
-      issue #14.
+    - Add progress bar support for session startup / resuming. (Fixes: #14).
     - Set the session name in case a session start failed due to lack of
       forwarding tunneling support in the server's SSH daemon.
     - Fall back to password auth if agent auth and key discovery fail.
@@ -79,7 +95,7 @@ python-x2go (0.2.0.10-0~x2go1) unstable; urgency=low
 
   * New upstream version (0.2.0.10):
     - Use session type 'D' for X2Go-proxied RDP session when run in
-      fullscreen mode. Fixes upstream issue #27.
+      fullscreen mode. (Fixes: #27).
     - Indentation fix in defaults.py.
     - Make Python X2Go aware of DirectRDP settings in session profiles.
     - Ignore DirectRDP options in session profile config for now.
@@ -410,7 +426,7 @@ python-x2go (0.1.1.5-0~x2go1) unstable; urgency=low
     - If sound is set to false in session profile use snd_system='none' in
       terminal session.
     - Stabilize sshfs related problems in case remote user is not in fuse group.
-    - Do not ignore usekbd session profile option anymore, closes upstream issue #81.
+    - Do not ignore usekbd session profile option anymore.
     - Fix for X2goSession.has_terminated method.
     - Fix for executing commands with arguments that contain a slash (thanks to Dick
       Kniep for digging this out).
diff --git a/x2go/__init__.py b/x2go/__init__.py
index b0567cd..e8470ef 100644
--- a/x2go/__init__.py
+++ b/x2go/__init__.py
@@ -178,7 +178,7 @@ Contact
 """
 
 __NAME__    = 'python-x2go'
-__VERSION__ = '0.2.1.0'
+__VERSION__ = '0.2.1.1'
 
 from gevent import monkey
 monkey.patch_all()
diff --git a/x2go/backends/control/_stdout.py b/x2go/backends/control/_stdout.py
index 7e29166..fdb80d4 100644
--- a/x2go/backends/control/_stdout.py
+++ b/x2go/backends/control/_stdout.py
@@ -798,6 +798,9 @@ class X2goControlSessionSTDOUT(paramiko.SSHClient):
                         if str(e) == 'No authentication methods available':
                             raise paramiko.AuthenticationException('Interactive password authentication required!')
                         else:
+                            self.close()
+                            if self.sshproxy_session:
+                                self.sshproxy_session.stop_thread()
                             raise(e)
 
                 # since Paramiko 1.7.7.1 there is compression available, let's use it if present...
@@ -872,9 +875,16 @@ class X2goControlSessionSTDOUT(paramiko.SSHClient):
                     self.logger('Requesting SSH agent forwarding for control session of connected session profile %s' % self.profile_name, loglevel=log.loglevel_INFO)
                 else:
                     self.logger('SSH agent forwarding is not available in the Paramiko version used with this instance of Python X2Go', loglevel=log.loglevel_WARN)
+        else:
+            self.close()
+            if self.sshproxy_session:
+                self.sshproxy_session.stop_thread()
 
         self._remote_home = None
         if not self.home_exists():
+            self.close()
+            if self.sshproxy_session:
+                self.sshproxy_session.stop_thread()
             raise x2go_exceptions.X2goRemoteHomeException('remote home directory does not exist')
 
         return (self.get_transport() is not None)
@@ -979,6 +989,7 @@ class X2goControlSessionSTDOUT(paramiko.SSHClient):
                 return True
         except x2go_exceptions.X2goControlSessionException:
             self.session_died = True
+            self.disconnect()
         return False
 
     def has_session_died(self):
@@ -1438,6 +1449,7 @@ class X2goControlSessionSTDOUT(paramiko.SSHClient):
 
             if _count >= _maxwait:
                 self.session_died = True
+                self.disconnect()
                 raise x2go_exceptions.X2goControlSessionException('x2golistsessions command failed after we have tried 20 times')
 
             # update internal variables when list_sessions() is called
diff --git a/x2go/backends/terminal/_stdout.py b/x2go/backends/terminal/_stdout.py
index 1ce197f..6f474c0 100644
--- a/x2go/backends/terminal/_stdout.py
+++ b/x2go/backends/terminal/_stdout.py
@@ -83,10 +83,13 @@ def _rewrite_cmd(cmd, params=None):
         cmd = _X2GO_DESKTOPSESSIONS[cmd]
 
     if (cmd == 'RDP') and (type(params) == X2goSessionParams):
+        _depth = params.depth
+        if int(_depth) == 17:
+            _depth = 16
         if params.geometry == 'fullscreen':
-            cmd = 'rdesktop -f -N %s %s -a %s' % (params.rdp_options, params.rdp_server, params.depth)
+            cmd = 'rdesktop -f -N %s %s -a %s' % (params.rdp_options, params.rdp_server, _depth)
         else:
-            cmd = 'rdesktop -g %s -N %s %s -a %s' % (params.geometry, params.rdp_options, params.rdp_server, params.depth)
+            cmd = 'rdesktop -g %s -N %s %s -a %s' % (params.geometry, params.rdp_options, params.rdp_server, _depth)
 
     # place quot marks around cmd if not empty string
     if cmd:
diff --git a/x2go/cache.py b/x2go/cache.py
index 7c73692..1654e08 100644
--- a/x2go/cache.py
+++ b/x2go/cache.py
@@ -152,12 +152,13 @@ class X2goListSessionsCache(object):
             self.x2go_listsessions_cache[profile_name]['mounts'] = {}
             if self.x2go_listsessions_cache[profile_name]['sessions']:
                 for session_name in self.x2go_listsessions_cache[profile_name]['sessions']:
-                    self.x2go_listsessions_cache[profile_name]['mounts'].update(control_session.list_mounts(session_name))
-        except x2go_exceptions.X2goControlSessionException, e:
+                    if control_session is not None and not control_session.has_session_died():
+                        self.x2go_listsessions_cache[profile_name]['mounts'].update(control_session.list_mounts(session_name))
+        except (x2go_exceptions.X2goControlSessionException, AttributeError), e:
             if profile_name in self.x2go_listsessions_cache.keys():
                 del self.x2go_listsessions_cache[profile_name]
             self.protected = False
-            raise e
+            raise x2go_exceptions.X2goControlSessionException
         except x2go_exceptions.X2goTimeOutException:
             pass
 
@@ -173,12 +174,13 @@ class X2goListSessionsCache(object):
         @raise X2goControlSessionException: if the control session's C{list_desktop} method fails
         """
         try:
-            self.x2go_listsessions_cache[profile_name]['desktops'] = control_session.list_desktops()
-        except x2go_exceptions.X2goControlSessionException, e:
+            if control_session is not None and not control_session.has_session_died():
+                self.x2go_listsessions_cache[profile_name]['desktops'] = control_session.list_desktops()
+        except (x2go_exceptions.X2goControlSessionException, AttributeError), e:
             if profile_name in self.x2go_listsessions_cache.keys():
                 del self.x2go_listsessions_cache[profile_name]
             self.protected = False
-            raise e
+            raise x2go_exceptions.X2goControlSessionException
         except x2go_exceptions.X2goTimeOutException:
             pass
 
@@ -192,13 +194,13 @@ class X2goListSessionsCache(object):
         @raise X2goControlSessionException: if the control session's C{list_sessions} method fails
         """
         try:
-            if control_session is not None:
+            if control_session is not None and not control_session.has_session_died():
                 self.x2go_listsessions_cache[profile_name]['sessions'] = control_session.list_sessions()
-        except x2go_exceptions.X2goControlSessionException, e:
+        except (x2go_exceptions.X2goControlSessionException, AttributeError), e:
             if profile_name in self.x2go_listsessions_cache.keys():
                 del self.x2go_listsessions_cache[profile_name]
             self.protected = False
-            raise e
+            raise x2go_exceptions.X2goControlSessionException
 
     def list_sessions(self, session_uuid):
         """\
diff --git a/x2go/client.py b/x2go/client.py
index c1e5358..d160e74 100644
--- a/x2go/client.py
+++ b/x2go/client.py
@@ -935,27 +935,31 @@ class X2goClient(object):
         @rtype: C{str}
 
         """
+        # detect profile name and profile id properly
+        if profile_id and self.session_profiles.has_profile_id(profile_id):
+            _p = profile_id
+        elif profile_name and self.session_profiles.has_profile_name(profile_name):
+            _p = profile_name
+        else:
+            _p = None
+
+        if _p:
+
+            _profile_id = self.session_profiles.check_profile_id_or_name(_p)
+            _profile_name = self.session_profiles.to_profile_name(_profile_id)
 
         # test if session_name has already been registered. If yes, return it immediately.
         if type(session_name) is types.StringType:
-            _retval = self.get_session_of_session_name(session_name, return_object=return_object)
+            _retval = self.get_session_of_session_name(session_name, return_object=return_object, match_profile_name=profile_name)
             if _retval is not None:
                 return _retval
 
         if known_hosts is None:
             known_hosts = os.path.join(_LOCAL_HOME, self.ssh_rootdir, 'known_hosts')
 
-        if profile_id and self.session_profiles.has_profile_id(profile_id):
-            _p = profile_id
-        elif profile_name and self.session_profiles.has_profile_name(profile_name):
-            _p = profile_name
-        else:
-            _p = None
 
         if _p:
 
-            _profile_id = self.session_profiles.check_profile_id_or_name(_p)
-            _profile_name = self.session_profiles.to_profile_name(_profile_id)
             _params = self.session_profiles.to_session_params(profile_id=_profile_id)
             del _params['profile_name']
 
@@ -1094,7 +1098,7 @@ class X2goClient(object):
     with_session = __get_session
     """Alias for L{get_session()}."""
 
-    def get_session_of_session_name(self, session_name, return_object=False):
+    def get_session_of_session_name(self, session_name, return_object=False, match_profile_name=None):
         """\
         Retrieve session UUID or L{X2goSession} for session name
         <session_name> from the session registry.
@@ -1103,13 +1107,15 @@ class X2goClient(object):
         @type session_name: C{str}
         @param return_object: session UUID hash or L{X2goSession} instance wanted?
         @type return_object: C{bool}
+        @param match_profile_name: only return sessions that match this profile name
+        @type match_profile_name: C{str}
 
         @return: the X2Go session's UUID registry hash or L{X2goSession} instance
         @rtype: C{str} or L{X2goSession} instance
 
         """
         try:
-            return self.session_registry.get_session_of_session_name(session_name=session_name, return_object=return_object)
+            return self.session_registry.get_session_of_session_name(session_name=session_name, return_object=return_object, match_profile_name=match_profile_name)
         except x2go_exceptions.X2goSessionRegistryException:
             return None
     __get_session_of_session_name = get_session_of_session_name
@@ -1503,7 +1509,7 @@ class X2goClient(object):
         return self.session_registry(session_uuid).share_desktop(desktop=_desktop, share_mode=share_mode, check_desktop_list=False, **sessionopts)
     __share_desktop_session = share_desktop_session
 
-    def resume_session(self, session_uuid=None, session_name=None, **sessionopts):
+    def resume_session(self, session_uuid=None, session_name=None, match_profile_name=None, **sessionopts):
         """\
         Resume or continue a suspended / running X2Go session on a
         remote X2Go server (as specified when L{register_session} was
@@ -1513,6 +1519,8 @@ class X2goClient(object):
         @type session_uuid: C{str}
         @param session_name: the server-side name of an X2Go session
         @type session_name: C{str}
+        @param match_profile_name: only resume a session if this profile name matches
+        @type match_profile_name: C{str}
         @param sessionopts: pass-through of options directly to the session instance's L{X2goSession.resume()} method
         @type sessionopts: C{dict}
 
@@ -1528,17 +1536,17 @@ class X2goClient(object):
             if session_name is None and self.session_registry(session_uuid).session_name is None:
                 raise x2go_exceptions.X2goClientException('don\'t know which session to resume')
             if session_uuid is None:
-                session_uuid = self.session_registry.get_session_of_session_name(session_name=session_name, return_object=False)
-                return self.session_registry(session_uuid).resume(session_list=self.list_sessions(session_uuid=session_uuid), **sessionopts)
+                session_uuid = self.session_registry.get_session_of_session_name(session_name=session_name, return_object=False, match_profile_name=match_profile_name)
+                return self.session_registry(session_uuid).resume(session_list=self._X2goClient__list_sessions(session_uuid=session_uuid), **sessionopts)
             else:
-                return self.session_registry(session_uuid).resume(session_name=session_name, session_list=self.list_sessions(session_uuid=session_uuid), **sessionopts)
+                return self.session_registry(session_uuid).resume(session_name=session_name, session_list=self._X2goClient__list_sessions(session_uuid=session_uuid), **sessionopts)
         except x2go_exceptions.X2goControlSessionException:
             profile_name = self.get_session_profile_name(session_uuid)
             self.HOOK_on_control_session_death(profile_name)
             self.disconnect_profile(profile_name)
     __resume_session = resume_session
 
-    def suspend_session(self, session_uuid, session_name=None, **sessionopts):
+    def suspend_session(self, session_uuid, session_name=None, match_profile_name=None, **sessionopts):
         """\
         Suspend an X2Go session.
 
@@ -1560,6 +1568,8 @@ class X2goClient(object):
         @param session_name: the server-side name of an X2Go session (for 
             non-associated session suspend)
         @type session_name: C{str}
+        @param match_profile_name: only suspend a session if this profile name matches
+        @type match_profile_name: C{str}
         @param sessionopts: pass-through of options directly to the session instance's L{X2goSession.suspend()} method
         @type sessionopts: C{dict}
 
@@ -1575,7 +1585,11 @@ class X2goClient(object):
 
                 return self.session_registry(session_uuid).suspend(**sessionopts)
             else:
-                for session in self.session_registry.running_sessions():
+                if match_profile_name is None:
+                    running_sessions = self.session_registry.running_sessions()
+                else:
+                    running_sessions = self.session_registry.running_sessions_of_profile_name(match_profile_name)
+                for session in running_sessions:
                     if session_name == session.get_session_name():
                         return session.suspend()
             return self.session_registry(session_uuid).control_session.suspend(session_name=session_name, **sessionopts)
@@ -1585,7 +1599,7 @@ class X2goClient(object):
             self.disconnect_profile(profile_name)
     __suspend_session = suspend_session
 
-    def terminate_session(self, session_uuid, session_name=None, **sessionopts):
+    def terminate_session(self, session_uuid, session_name=None, match_profile_name=None, **sessionopts):
         """\
         Terminate an X2Go session.
 
@@ -1606,6 +1620,8 @@ class X2goClient(object):
         @type session_uuid: C{str}
         @param session_name: the server-side name of an X2Go session
         @type session_name: C{str}
+        @param match_profile_name: only terminate a session if this profile name matches
+        @type match_profile_name: C{str}
         @param sessionopts: pass-through of options directly to the session instance's L{X2goSession.terminate()} method
         @type sessionopts: C{dict}
 
@@ -1621,7 +1637,11 @@ class X2goClient(object):
 
                 return self.session_registry(session_uuid).terminate(**sessionopts)
             else:
-                for session in self.session_registry.running_sessions() + self.session_registry.suspended_sessions():
+                if match_profile_name is None:
+                    terminatable_sessions = self.session_registry.running_sessions() + self.session_registry.suspended_sessions()
+                else:
+                    terminatable_sessions = self.session_registry.running_sessions_of_profile_name(match_profile_name) + self.session_registry.suspended_sessions_of_profile_name(match_profile_name)
+                for session in terminatable_sessions:
                     if session_name == session.get_session_name():
                         return session.terminate()
             return self.session_registry(session_uuid).control_session.terminate(session_name=session_name, **sessionopts)
@@ -2459,9 +2479,9 @@ class X2goClient(object):
         session.clean_sessions(destroy_terminals=_destroy_terminals, published_applications=published_applications)
     __clean_sessions = clean_sessions
 
-    def list_sessions(self, session_uuid=None, 
-                      profile_name=None, profile_id=None, 
-                      no_cache=False, refresh_cache=False, 
+    def list_sessions(self, session_uuid=None,
+                      profile_name=None, profile_id=None,
+                      no_cache=False, refresh_cache=False,
                       update_sessionregistry=True,
                       register_sessions=False,
                       raw=False):
@@ -2873,10 +2893,10 @@ class X2goClient(object):
         session_uuids = self.client_registered_sessions_of_profile_name(profile_name, return_objects=False)
         if session_uuids:
             if session_list is None:
-                session_list = self.list_sessions(session_uuids[0],
-                                                  update_sessionregistry=False, 
-                                                  register_sessions=False,
-                                                 )
+                session_list = self._X2goClient__list_sessions(session_uuids[0],
+                                                               update_sessionregistry=False,
+                                                               register_sessions=False,
+                                                              )
             try:
                 self.session_registry.update_status(profile_name=profile_name, session_list=session_list)
             except x2go_exceptions.X2goControlSessionException:
@@ -2893,7 +2913,7 @@ class X2goClient(object):
         @type session_uuid: C{str}
 
         """
-        session_list = self.list_sessions(session_uuid, update_sessionregistry=False, register_sessions=False)
+        session_list = self._X2goClient__list_sessions(session_uuid, update_sessionregistry=False, register_sessions=False)
         if session_list:
             self.session_registry.update_status(session_uuid=session_uuid, session_list=session_list)
     __update_sessionregistry_status_by_session_uuid = update_sessionregistry_status_by_session_uuid
@@ -3013,10 +3033,10 @@ class X2goClient(object):
         """
         if profile_name not in self.client_connected_profiles(return_profile_names=True):
             return
-        session_list = self.list_sessions(profile_name=profile_name, 
-                                          update_sessionregistry=False,
-                                          register_sessions=False,
-                                         )
+        session_list = self._X2goClient__list_sessions(profile_name=profile_name,
+                                                       update_sessionregistry=False,
+                                                       register_sessions=False,
+                                                      )
         try:
             self.session_registry.register_available_server_sessions(profile_name, session_list=session_list, re_register=re_register, skip_pubapp_sessions=skip_pubapp_sessions)
         except x2go_exceptions.X2goControlSessionException, e:
diff --git a/x2go/registry.py b/x2go/registry.py
index 5b4d67f..4669ea2 100644
--- a/x2go/registry.py
+++ b/x2go/registry.py
@@ -553,7 +553,7 @@ class X2goSessionRegistry(object):
             self.logger('using already initially-registered yet-unused session %s' % session_uuid, loglevel=log.loglevel_NOTICE)
 
         else:
-            session_uuid = self.get_session_of_session_name(session_name)
+            session_uuid = self.get_session_of_session_name(session_name, match_profile_name=profile_name)
             if session_uuid is not None: self.logger('using already registered-by-session-name session %s' % session_uuid, loglevel=log.loglevel_NOTICE)
 
         if session_uuid is not None:
@@ -606,20 +606,22 @@ class X2goSessionRegistry(object):
         self._profile_locks[profile_id].release()
         return session_uuid
 
-    def has_session_of_session_name(self, session_name):
+    def has_session_of_session_name(self, session_name, match_profile_name=None):
         """\
         Detect if we know about an L{X2goSession} of name C{<session_name>}.
 
         @param session_name: name of session to be searched for
         @type session_name: C{str}
+        @param match_profile_name: a session's profile_name must match this profile name
+        @type match_profile_name: C{str}
 
         @return: C{True} if a session of C{<session_name>} has been found
         @rtype: C{bool}
 
         """
-        return bool(self.get_session_of_session_name(session_name))
+        return bool(self.get_session_of_session_name(session_name, match_profile_name=match_profile_name))
 
-    def get_session_of_session_name(self, session_name, return_object=False):
+    def get_session_of_session_name(self, session_name, return_object=False, match_profile_name=None):
         """\
         Retrieve the L{X2goSession} instance with session name C{<session_name>}.
 
@@ -627,6 +629,8 @@ class X2goSessionRegistry(object):
         @type session_name: C{str}
         @param return_object: if C{False} the session UUID hash will be returned, if C{True} the L{X2goSession} instance will be returned
         @type return_object: C{bool}
+        @param match_profile_name: returned sessions must match this profile name
+        @type match_profile_name: C{str}
 
         @return: L{X2goSession} object or its representing session UUID hash
         @rtype: L{X2goSession} instance or C{str}
@@ -635,7 +639,11 @@ class X2goSessionRegistry(object):
             the same L{X2goClient} instance. This should never happen!
 
         """
-        found_sessions = [ s for s in self.registered_sessions() if s.session_name == session_name and s.session_name is not None ]
+        if match_profile_name is None:
+            reg_sessions = self.registered_sessions()
+        else:
+            reg_sessions = self.registered_sessions_of_profile_name(match_profile_name)
+        found_sessions = [ s for s in reg_sessions if s.session_name == session_name and s.session_name is not None ]
         if len(found_sessions) == 1:
             session = found_sessions[0]
             if return_object:
@@ -938,7 +946,6 @@ class X2goSessionRegistry(object):
         @rtype: C{list}
 
         """
-
         if return_objects:
             return self.registered_sessions() and [ s for s in self.registered_sessions() if s.get_profile_name() == profile_name ]
         elif return_session_names:


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).




More information about the x2go-commits mailing list