Package x2go :: Module xserver

[frames] | no frames]

# Source Code for Module x2go.xserver

      1  # -*- coding: utf-8 -*-
      2
      3  # Copyright (C) 2010-2016 by Mike Gabriel <mike.gabriel@das-netzwerkteam.de>
      4  #
      5  # Python X2Go is free software; you can redistribute it and/or modify
      6  # it under the terms of the GNU Affero General Public License as published by
      7  # the Free Software Foundation; either version 3 of the License, or
      8  # (at your option) any later version.
      9  #
     10  # Python X2Go is distributed in the hope that it will be useful,
     11  # but WITHOUT ANY WARRANTY; without even the implied warranty of
     12  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13  # GNU Affero General Public License for more details.
     14  #
     15  # You should have received a copy of the GNU Affero General Public License
     16  # along with this program; if not, write to the
     17  # Free Software Foundation, Inc.,
     18  # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
     19  #
     20  # This code was initially written by:
     21  #       2010 Dick Kniep <dick.kniep@lindix.nl>
     22  #
     23  # Other contributors:
     24  #       none so far
     25
     26  __NAME__ = 'x2goxserver-pylib'
     27
     28  from defaults import X2GOCLIENT_OS as _X2GOCLIENT_OS
     29  if _X2GOCLIENT_OS == 'Windows':
     30      import wmi
     31      import win32process
     32
     33  # modules
     34  import os
     35  import threading
     36  import gevent
     37  import copy
     38
     39  # Python X2Go modules
     40  import log
     41  from defaults import X2GO_XCONFIG_CONFIGFILES as _X2GO_XCONFIG_CONFIGFILES
     42  from defaults import X2GO_CLIENTXCONFIG_DEFAULTS as _X2GO_CLIENTXCONFIG_DEFAULTS
     43  import inifiles
     44  import utils
     45
     46 -class X2GoClientXConfig(inifiles.X2GoIniFile):
     47      """\
     48      Configuration file based XServer startup settings for X2GoClient instances.
     49
     50      This class is needed for Windows systems and (maybe soon) for Unix desktops using Wayland.
     51
     52      """
     53
     54 -    def __init__(self, config_files=_X2GO_XCONFIG_CONFIGFILES, defaults=_X2GO_CLIENTXCONFIG_DEFAULTS, logger=None, loglevel=log.loglevel_DEFAULT):
     55          """\
     56          Constructs an L{X2GoClientXConfig} instance. This is normally done by an L{X2GoClient} instance.
     57          You can retrieve this L{X2GoClientXConfig} instance with the C{X2GoClient.get_client_xconfig()}
     58          method.
     59
     60          On construction the L{X2GoClientXConfig} instance is filled with values from the configuration files::
     61
     62              /etc/x2goclient/xconfig
     63              ~/.x2goclient/xconfig
     64
     65          The files are read in the specified order and config options of both files are merged. Options
     66          set in the user configuration file (C{~/.x2goclient/xconfig}) override global options set in
     67          C{/etc/x2goclient/xconfig}.
     68
     69          @param config_files: a list of configuration file names
     70          @type config_files: C{list}
     71          @param defaults: a Python dictionary with configuration file defaults (use on your own risk)
     72          @type defaults: C{dict}
     73          @param logger: you can pass an L{X2GoLogger} object to the L{X2GoClientXConfig} constructor
     74          @type logger: C{obj}
     75          @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be
     76              constructed with the given loglevel
     77          @type loglevel: C{int}
     78
     79          """
     80          if _X2GOCLIENT_OS not in ("Windows"):
     81              import exceptions
     82              class OSNotSupportedException(exceptions.StandardError): pass
     83              raise OSNotSupportedException('classes of x2go.xserver module are for Windows only')
     84
     85          inifiles.X2GoIniFile.__init__(self, config_files, defaults=defaults, logger=logger, loglevel=loglevel)
     86
     87          _known_xservers = utils.merge_ordered_lists(self.defaultValues['XServers']['known_xservers'], self.known_xservers)
     88
     89          if _known_xservers != self.known_xservers:
     90              self.update_value('XServers', 'known_xservers', _known_xservers)
     91              self.write_user_config = True
     92              self.write()
     93
     94 -    def write(self):
     95          """\
     96          Store the Xserver configuration to the storage backend (i.e. on disk).
     97
     98          For writing the first of the C{config_files} specified on instance construction
     99          that is writable will be used.
    100
    101          @return: C{True} if the user config file has been successfully written, C{False} otherwise.
    102          @rtype: C{bool}
    103
    104          """
    105          self._write_user_config = self.write_user_config
    106          return self._X2GoIniFile__write()
    107
    108 -    def get_xserver_config(self, xserver_name):
    109          """\
    110          Retrieve the XServer configuration (from the xconfig file) for the given XServer application.
    111
    112          @param xserver_name: name of the XServer application
    113          @type xserver_name: C{str}
    114
    115          @return: A Python dictionary containing the XServer's configuration settings
    116          @rtype: C{list}
    117
    118          """
    119          _xserver_config = {}
    120          for option in self.iniConfig.options(xserver_name):
    121              try:
    122                  _xserver_config[option] = self.get(xserver_name, option, key_type=self.get_type(xserver_name, option))
    123              except KeyError:
    124                  pass
    125          return _xserver_config
    126
    127      @property
    128 -    def known_xservers(self):
    129          """\
    130          Renders a list of XServers that are known to Python X2Go.
    131
    132          """
    133          return self.get_value('XServers', 'known_xservers')
    134
    135      @property
    136 -    def installed_xservers(self):
    137          """\
    138          Among the known XServers renders a list of XServers that are actually
    139          installed on the system.
    140
    141          """
    142          _installed = []
    143          for xserver_name in self.known_xservers:
    144              if os.path.exists(os.path.normpath(self.get_xserver_config(xserver_name)['test_installed'])):
    145                  _installed.append(xserver_name)
    146          return _installed
    147
    148      @property
    149 -    def running_xservers(self):
    150          """\
    151          Tries to render a list of running XServer processes from the system's process list.
    152
    153          """
    154          _running = []
    155          _wmi = wmi.WMI()
    156          _my_wmi_sessionid = [ _p.SessionId for _p in _wmi.Win32_Process() if _p.ProcessId == os.getpid() ][0]
    157
    158          _process_list = _wmi.Win32_Process()
    159          for xserver_name in self.installed_xservers:
    160              process_name = self.get_xserver_config(xserver_name)['process_name']
    161              if [ _p.Name for _p in _process_list if _p.Name == process_name and _p.SessionId == _my_wmi_sessionid ]:
    162                  # XServer is already running
    163                  _running.append(xserver_name)
    164                  continue
    165          return _running
    166
    167      @property
    168 -    def xserver_launch_possible(self):
    169          """\
    170          Detect if there is an XServer (that is known to Python X2Go) installed on the system.
    171          Equals C{True} if we have found an installed XServer that we can launch.
    172
    173          """
    174          return bool(self.installed_xservers)
    175
    176      @property
    177 -    def xserver_launch_needed(self):
    178          """\
    179          Detect if an XServer launch is really needed (or if we use an already running XServer instance).
    180          Equals C{True} if we have to launch an XServer before we can start/resume
    181          X2Go sessions.
    182
    183          """
    184          return not bool(self.running_xservers)
    185
    186      @property
    187 -    def preferred_xserver(self):
    188          """\
    189          Returns a tuple of (<xserver_name>, <xserver_config>).
    190
    191          return: (<xserver_name>, <xserver_config>)
    192          rtype: C{tuple}
    193
    194          """
    195          if self.xserver_launch_possible:
    196              return (self.installed_xservers[0], self.get_xserver_config(self.installed_xservers[0]))
    197          else:
    198              return None
    199
    200      @property
    201 -    def preferred_xserver_names(self):
    202          """\
    203          Returns the list of preferred XServer names (most preferred first).
    204
    205          """
    206          return self.installed_xservers
    207
    208 -    def detect_unused_xdisplay_port(self, xserver_name):
    209          """\
    210          Get an unused TCP/IP port for the to-be-launched X server and write it
    211          to the user's X configuration file.
    212
    213          @param xserver_name: name of the XServer application
    214          @type xserver_name: C{str}
    215
    216          """
    217          _default_display = self.get_xserver_config(xserver_name)['display']
    218          _last_display = self.get_xserver_config(xserver_name)['last_display']
    219
    220          try:
    221              _default_xserver_port = int(_default_display.split(":")[1].split(".")[0]) + 6000
    222              _last_xserver_port = int(_last_display.split(":")[1].split(".")[0]) + 6000
    223
    224              # try the last used $DISPLAY first...
    225              if utils.detect_unused_port(preferred_port=_last_xserver_port) == _last_xserver_port:
    226                  _detect_xserver_port = _last_xserver_port
    227
    228              # then try the default $DISPLAY...
    229              elif utils.detect_unused_port(preferred_port=_default_xserver_port) == _default_xserver_port:
    230                  _detect_xserver_port = _default_xserver_port
    231
    232              # otherwise use a detection algorithm to find a free TCP/IP port
    233              else:
    234                  _xserver_port = _default_xserver_port +1
    235                  self.logger('Attempting to detect an unused TCP/IP port for our X-Server, starting with port %s' % _xserver_port, loglevel=log.loglevel_DEBUG)
    236                  while utils.detect_unused_port(preferred_port=_xserver_port) != _xserver_port:
    237                      _xserver_port += 1
    238                      self.logger('TCP/IP port was in use, trying next port: %s' % _xserver_port, loglevel=log.loglevel_DEBUG)
    239                  self.logger('allocating TCP/IP port %s for our X-Server' % _xserver_port, loglevel=log.loglevel_DEBUG)
    240                  _detect_xserver_port = _xserver_port
    241
    242              # if the port changed, let's write it to our configuration file
    243              if _detect_xserver_port != _last_xserver_port:
    244                  _new_display = _last_display.replace(str(_last_xserver_port -6000), str(_detect_xserver_port -6000))
    245                  self.logger('cannot use configured X DISPLAY, the new available DISPLAY port %s has been detected' % _new_display, loglevel=log.loglevel_NOTICE)
    246                  self.update_value(xserver_name, 'last_display', _new_display)
    247                  _parameters = self.get_value(xserver_name, 'parameters')
    248                  _parameters[0] = ":%s" % (_detect_xserver_port -6000)
    249                  self.update_value(xserver_name, 'parameters', tuple(_parameters))
    250                  self.write_user_config = True
    251                  self.write()
    252                  return _new_display
    253
    254              return _last_display
    255
    256          except TypeError:
    257              pass
    258
    259
    260 -class X2GoXServer(threading.Thread):
    261      """
    262      This class is responsible for starting/stopping an external XServer application.
    263
    264      X2Go applications require a running XServer on the client system. This class will
    265      manage/handle the XServer while your X2Go application is running.
    266
    267      """
    268 -    def __init__(self, xserver_name, xserver_config, logger=None, loglevel=log.loglevel_DEFAULT):
    269          """\
    270          Initialize an XServer thread.
    271
    272          @param xserver_name: name of the XServer to start (refer to the xconfig file for available names)
    273          @type xserver_name: C{str}
    274          @param xserver_config: XServer configuration node (as derived from L{X2GoClientXConfig.get_xserver_config()}
    275          @type xserver_config: C{dict}
    276          @param logger: you can pass an L{X2GoLogger} object to the L{X2GoClientXConfig} constructor
    277          @type logger: C{obj}
    278          @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be
    279              constructed with the given loglevel
    280          @type loglevel: C{int}
    281
    282          """
    283          if _X2GOCLIENT_OS not in ("Windows"):
    284              import exceptions
    285              class OSNotSupportedException(exceptions.StandardError): pass
    286              raise OSNotSupportedException('classes of x2go.xserver module are for Windows only')
    287
    288          if logger is None:
    289              self.logger = log.X2GoLogger(loglevel=loglevel)
    290          else:
    291              self.logger = copy.deepcopy(logger)
    292          self.logger.tag = __NAME__
    293
    294          self._keepalive = None
    295
    296          self.xserver_name = xserver_name
    297          self.xserver_config = xserver_config
    298          self.hProcess = None
    299
    300          if self.xserver_config.has_key('last_display'):
    301
    302              self.logger('setting DISPLAY environment variable to %s' % self.xserver_config['last_display'], loglevel=log.loglevel_NOTICE)
    303              os.environ.update({'DISPLAY': str(self.xserver_config['last_display'])})
    304          threading.Thread.__init__(self)
    305          self.daemon = True
    306          self.start()
    307
    308 -    def __del__(self):
    309          """\
    310          Class destructor. Terminate XServer process.
    311
    312          """
    313          self._terminate_xserver()
    314
    315 -    def run(self):
    316          """\
    317          Start this L{X2GoXServer} thread. This will launch the configured XServer application.
    318
    319          """
    320          self._keepalive = True
    321          cmd_line = [self.xserver_config['run_command']]
    322          cmd_line.extend(self.xserver_config['parameters'])
    323          self.logger('starting XServer ,,%s\'\' with command line: %s' % (self.xserver_name, ' '.join(cmd_line)), loglevel=log.loglevel_DEBUG)
    324
    325          if _X2GOCLIENT_OS == 'Windows':
    326              si = win32process.STARTUPINFO()
    327              p_info = win32process.CreateProcess(None,
    328                                                  ' '.join(cmd_line),
    329                                                  None,
    330                                                  None,
    331                                                  0,
    332                                                  win32process.NORMAL_PRIORITY_CLASS,
    333                                                  None,
    334                                                  None,
    335                                                  si,
    336                                                 )
    337              (self.hProcess, hThread, processId, threadId) = p_info
    338
    339          while self._keepalive:
    340              gevent.sleep(1)
    341
    342          self._terminate_xserver()
    343
    344 -    def _terminate_xserver(self):
    345          """\
    346          Terminate the runnint XServer process.
    347
    348          """
    349          self.logger('terminating running XServer ,,%s\'\'' % self.xserver_name, loglevel=log.loglevel_DEBUG)
    350
    351          if _X2GOCLIENT_OS == 'Windows' and self.hProcess is not None:
    352              try:
    353                  win32process.TerminateProcess(self.hProcess, 0)
    354              except win32process.error:
    355                  self.logger('XServer ,,%s\'\' could not be terminated.' % self.xserver_name, loglevel=log.loglevel_DEBUG)
    356
    357 -    def stop_thread(self):
    358          """\
    359          A call to this method will stop the XServer application and do a cleanup afterwards.
    360
    361          """
    362          self._keepalive = False
    363          self.logger('stop_thread() method has been called', loglevel=log.loglevel_DEBUG)
    364