Package x2go :: Module xserver
[frames] | no frames]

Source Code for Module x2go.xserver

  1  # -*- coding: utf-8 -*- 
  2   
  3  # Copyright (C) 2010-2014 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