1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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
34 import os
35 import threading
36 import gevent
37 import copy
38
39
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
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
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
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
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
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
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
151 _running.append(xserver_name)
152 continue
153 return _running
154
155 @property
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
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
187
188 @property
190 """\
191 Returns the list of preferred XServer names (most preferred first).
192
193 """
194 return self.installed_xservers
195
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
213 if utils.detect_unused_port(preferred_port=_last_xserver_port) == _last_xserver_port:
214 _detect_xserver_port = _last_xserver_port
215
216
217 elif utils.detect_unused_port(preferred_port=_default_xserver_port) == _default_xserver_port:
218 _detect_xserver_port = _default_xserver_port
219
220
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
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
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 """
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
291 """\
292 Class destructor. Terminate XServer process.
293
294 """
295 self._terminate_xserver()
296
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
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
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