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

Source Code for Module x2go.xserver

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