Package x2go :: Module xserver

[frames] | no frames]

# Source Code for Module x2go.xserver

      1  # -*- coding: utf-8 -*-
      2
      3  # Copyright (C) 2010-2013 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      defaultValues = _X2GO_CLIENTXCONFIG_DEFAULTS
     54
     55 -    def __init__(self, config_files=_X2GO_XCONFIG_CONFIGFILES, defaults=None, logger=None, loglevel=log.loglevel_DEFAULT):
     56          """\
     57          Constructs an L{X2GoClientXConfig} instance. This is normally done by an L{X2GoClient} instance.
     58          You can retrieve this L{X2GoClientXConfig} instance with the C{X2GoClient.get_client_xconfig()}
     59          method.
     60
     61          On construction the L{X2GoClientXConfig} instance is filled with values from the configuration files::
     62
     63              /etc/x2goclient/xconfig
     64              ~/.x2goclient/xconfig
     65
     66          The files are read in the specified order and config options of both files are merged. Options
     67          set in the user configuration file (C{~/.x2goclient/xconfig}) override global options set in
     68          C{/etc/x2goclient/xconfig}.
     69
     70          @param config_files: a list of configuration file names
     71          @type config_files: C{list}
     72          @param defaults: a Python dictionary with configuration file defaults (use on your own risk)
     73          @type defaults: C{dict}
     74          @param logger: you can pass an L{X2GoLogger} object to the L{X2GoClientXConfig} constructor
     75          @type logger: C{obj}
     76          @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be
     77              constructed with the given loglevel
     78          @type loglevel: C{int}
     79
     80          """
     81          if _X2GOCLIENT_OS not in ("Windows"):
     82              import exceptions
     83              class OSNotSupportedException(exceptions.StandardError): pass
     84              raise OSNotSupportedException('classes of x2go.xserver module are for Windows only')
     85
     86          inifiles.X2GoIniFile.__init__(self, config_files, defaults=defaults, logger=logger, loglevel=loglevel)
     87
     88          _known_xservers = utils.merge_ordered_lists(self.defaultValues['XServers']['known_xservers'], self.known_xservers)
     89
     90          if _known_xservers != self.known_xservers:
     91              self.update_value('XServers', 'known_xservers', _known_xservers)
     92              self.write_user_config = True
     93              self.write()
     94
     95 -    def get_xserver_config(self, xserver_name):
     96          """\
     97          Retrieve the XServer configuration (from the xconfig file) for the given XServer application.
     98
     99          @param xserver_name: name of the XServer application
    100          @type xserver_name: C{str}
    101
    102          @return: A Python dictionary containing the XServer's configuration settings
    103          @rtype: C{list}
    104
    105          """
    106          _xserver_config = {}
    107          for option in self.iniConfig.options(xserver_name):
    108              try:
    109                  _xserver_config[option] = self.get(xserver_name, option, key_type=self.get_type(xserver_name, option))
    110              except KeyError:
    111                  pass
    112          return _xserver_config
    113
    114      @property
    115 -    def known_xservers(self):
    116          """\
    117          Renders a list of XServers that are known to Python X2Go.
    118
    119          """
    120          return self.get_value('XServers', 'known_xservers')
    121
    122      @property
    123 -    def installed_xservers(self):
    124          """\
    125          Among the known XServers renders a list of XServers that are actually
    126          installed on the system.
    127
    128          """
    129          _installed = []
    130          for xserver_name in self.known_xservers:
    131              if os.path.exists(os.path.normpath(self.get_xserver_config(xserver_name)['test_installed'])):
    132                  _installed.append(xserver_name)
    133          return _installed
    134
    135      @property
    136 -    def running_xservers(self):
    137          """\
    138          Tries to render a list of running XServer processes from the system's process list.
    139
    140          """
    141          _running = []
    142          _wmi = wmi.WMI()
    143          _p_names = []
    144          for process in _wmi.Win32_Process():
    145              _p_names.append(process.Name)
    146
    147          for xserver_name in self.installed_xservers:
    148              process_name = self.get_xserver_config(xserver_name)['process_name']
    149              if process_name in _p_names:
    150                  # XServer is already running
    151                  _running.append(xserver_name)
    152                  continue
    153          return _running
    154
    155      @property
    156 -    def xserver_launch_possible(self):
    157          """\
    158          Detect if there is an XServer (that is known to Python X2Go) installed on the system.
    159          Equals C{True} if we have found an installed XServer that we can launch.
    160
    161          """
    162          return bool(self.installed_xservers)
    163
    164      @property
    165 -    def xserver_launch_needed(self):
    166          """\
    167          Detect if an XServer launch is really needed (or if we use an already running XServer instance).
    168          Equals C{True} if we have to launch an XServer before we can start/resume
    169          X2Go sessions.
    170
    171          """
    172          return not bool(self.running_xservers)
    173
    174      @property
    175 -    def preferred_xserver(self):
    176          """\
    177          Returns a tuple of (<xserver_name>, <xserver_config>).
    178
    179          return: (<xserver_name>, <xserver_config>)
    180          rtype: C{tuple}
    181
    182          """
    183          if self.xserver_launch_possible:
    184              return (self.installed_xservers[0], self.get_xserver_config(self.installed_xservers[0]))
    185          else:
    186              return None
    187
    188      @property
    189 -    def preferred_xserver_names(self):
    190          """\
    191          Returns the list of preferred XServer names (most preferred first).
    192
    193          """
    194          return self.installed_xservers
    195
    196 -    def detect_unused_xdisplay_port(self, xserver_name):
    197          """\
    198          Get an unused TCP/IP port for the to-be-launched X server and write it
    199          to the user's X configuration file.
    200
    201          @param xserver_name: name of the XServer application
    202          @type xserver_name: C{str}
    203
    204          """
    205          _default_display = self.get_xserver_config(xserver_name)['display']
    206          _last_display = self.get_xserver_config(xserver_name)['last_display']
    207
    208          try:
    209              _default_xserver_port = int(_default_display.split(":")[1].split(".")[0]) + 6000
    210              _last_xserver_port = int(_last_display.split(":")[1].split(".")[0]) + 6000
    211
    212              # try the last used $DISPLAY first...
    213              if utils.detect_unused_port(preferred_port=_last_xserver_port) == _last_xserver_port:
    214                  _detect_xserver_port = _last_xserver_port
    215
    216              # then try the default $DISPLAY...
    217              elif utils.detect_unused_port(preferred_port=_default_xserver_port) == _default_xserver_port:
    218                  _detect_xserver_port = _default_xserver_port
    219
    220              # otherwise use a detection algorithm to find a free TCP/IP port
    221              else:
    222                  _xserver_port = _default_xserver_port +1
    223                  while utils.detect_unused_port(preferred_port=_xserver_port) != _xserver_port:
    224                          _xserver_port += 1
    225                  _detect_xserver_port = _xserver_port
    226
    227              # if the port changed, let's write it to our configuration file
    228              if _detect_xserver_port != _last_xserver_port:
    229                  _new_display = _last_display.replace(str(_last_xserver_port -6000), str(_detect_xserver_port -6000))
    230                  self.logger('cannot used configured X DISPLAY, the new available DISPLAY port %s has been detected' % _new_display, loglevel=log.loglevel_NOTICE)
    231                  self.update_value(xserver_name, 'last_display', _new_display)
    232                  _parameters = self.get_value(xserver_name, 'parameters')
    233                  _parameters[0] = ":%s" % (_detect_xserver_port -6000)
    234                  self.update_value(xserver_name, 'parameters', tuple(_parameters))
    235                  self.write_user_config = True
    236                  self.write()
    237
    238          except TypeError:
    239              pass
    240
    241
    242 -class X2GoXServer(threading.Thread):
    243      """
    244      This class is responsible for starting/stopping an external XServer application.
    245
    246      X2Go applications require a running XServer on the client system. This class will
    247      manage/handle the XServer while your X2Go application is running.
    248
    249      """
    250 -    def __init__(self, xserver_name, xserver_config, logger=None, loglevel=log.loglevel_DEFAULT):
    251          """\
    252          Initialize an XServer thread.
    253
    254          @param xserver_name: name of the XServer to start (refer to the xconfig file for available names)
    255          @type xserver_name: C{str}
    256          @param xserver_config: XServer configuration node (as derived from L{X2GoClientXConfig.get_xserver_config()}
    257          @type xserver_config: C{dict}
    258          @param logger: you can pass an L{X2GoLogger} object to the L{X2GoClientXConfig} constructor
    259          @type logger: C{obj}
    260          @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be
    261              constructed with the given loglevel
    262          @type loglevel: C{int}
    263
    264          """
    265          if _X2GOCLIENT_OS not in ("Windows"):
    266              import exceptions
    267              class OSNotSupportedException(exceptions.StandardError): pass
    268              raise OSNotSupportedException('classes of x2go.xserver module are for Windows only')
    269
    270          if logger is None:
    271              self.logger = log.X2GoLogger(loglevel=loglevel)
    272          else:
    273              self.logger = copy.deepcopy(logger)
    274          self.logger.tag = __NAME__
    275
    276          self._keepalive = None
    277
    278          self.xserver_name = xserver_name
    279          self.xserver_config = xserver_config
    280          self.hProcess = None
    281
    282          if self.xserver_config.has_key('last_display'):
    283
    284              self.logger('setting DISPLAY environment variable to %s' % self.xserver_config['last_display'], loglevel=log.loglevel_NOTICE)
    285              os.environ.update({'DISPLAY': str(self.xserver_config['last_display'])})
    286          threading.Thread.__init__(self)
    287          self.daemon = True
    288          self.start()
    289
    290 -    def __del__(self):
    291          """\
    292          Class destructor. Terminate XServer process.
    293
    294          """
    295          self._terminate_xserver()
    296
    297 -    def run(self):
    298          """\
    299          Start this L{X2GoXServer} thread. This will launch the configured XServer application.
    300
    301          """
    302          self._keepalive = True
    303          cmd_line = [self.xserver_config['run_command']]
    304          cmd_line.extend(self.xserver_config['parameters'])
    305          self.logger('starting XServer ,,%s\'\' with command line: %s' % (self.xserver_name, ' '.join(cmd_line)), loglevel=log.loglevel_DEBUG)
    306
    307          if _X2GOCLIENT_OS == 'Windows':
    308              si = win32process.STARTUPINFO()
    309              p_info = win32process.CreateProcess(None,
    310                                                  ' '.join(cmd_line),
    311                                                  None,
    312                                                  None,
    313                                                  0,
    314                                                  win32process.NORMAL_PRIORITY_CLASS,
    315                                                  None,
    316                                                  None,
    317                                                  si,
    318                                                 )
    319              (self.hProcess, hThread, processId, threadId) = p_info
    320
    321          while self._keepalive:
    322              gevent.sleep(1)
    323
    324          self._terminate_xserver()
    325
    326 -    def _terminate_xserver(self):
    327          """\
    328          Terminate the runnint XServer process.
    329
    330          """
    331          self.logger('terminating running XServer ,,%s\'\'' % self.xserver_name, loglevel=log.loglevel_DEBUG)
    332
    333          if _X2GOCLIENT_OS == 'Windows' and self.hProcess is not None:
    334              try:
    335                  win32process.TerminateProcess(self.hProcess, 0)
    336              except win32process.error:
    337                  self.logger('XServer ,,%s\'\' could not be terminated.' % self.xserver_name, loglevel=log.loglevel_DEBUG)
    338
    339 -    def stop_thread(self):
    340          """\
    341          A call to this method will stop the XServer application and do a cleanup afterwards.
    342
    343          """
    344          self._keepalive = False
    345          self.logger('stop_thread() method has been called', loglevel=log.loglevel_DEBUG)
    346