1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 """\
21 X2GoTerminalSession class - core functions for handling your individual X2Go sessions.
22
23 This backend handles X2Go server implementations that respond with session infos
24 via server-side PLAIN text output.
25
26 """
27 __NAME__ = 'x2goterminalsession-pylib'
28
29
30 import os
31 import types
32 import gevent
33 import cStringIO
34 import copy
35 import shutil
36 import threading
37
38
39 import x2go.rforward as rforward
40 import x2go.sftpserver as sftpserver
41 import x2go.printqueue as printqueue
42 import x2go.mimebox as mimebox
43 import x2go.telekinesis as telekinesis
44 import x2go.log as log
45 import x2go.defaults as defaults
46 import x2go.utils as utils
47 import x2go.x2go_exceptions as x2go_exceptions
48
49
50 from x2go.defaults import X2GOCLIENT_OS as _X2GOCLIENT_OS
51 from x2go.defaults import LOCAL_HOME as _LOCAL_HOME
52 from x2go.defaults import CURRENT_LOCAL_USER as _CURRENT_LOCAL_USER
53 from x2go.defaults import X2GO_CLIENT_ROOTDIR as _X2GO_CLIENT_ROOTDIR
54 from x2go.defaults import X2GO_SESSIONS_ROOTDIR as _X2GO_SESSIONS_ROOTDIR
55 from x2go.defaults import X2GO_GENERIC_APPLICATIONS as _X2GO_GENERIC_APPLICATIONS
56 from x2go.defaults import X2GO_DESKTOPSESSIONS as _X2GO_DESKTOPSESSIONS
57
58 from x2go.defaults import BACKENDS as _BACKENDS
59
60 _local_color_depth = utils.local_color_depth()
61
63 """\
64 Mechansim that rewrites X2Go server commands into something that gets understood by
65 the server-side script C{x2goruncommand}.
66
67 @param cmd: the current command for execution (as found in the session profile parameter C{cmd})
68 @type cmd: C{str}
69 @param params: an session paramter object
70 @type params: L{X2GoSessionParams}
71
72 @return: the rewritten command for server-side execution
73 @rtype: C{str}
74
75 """
76
77 cmd = cmd or ''
78
79
80 if cmd in _X2GO_DESKTOPSESSIONS.keys():
81 cmd = _X2GO_DESKTOPSESSIONS[cmd]
82
83 if (cmd == 'RDP') and (type(params) == X2GoSessionParams):
84 _depth = params.depth
85 if int(_depth) == 17:
86 _depth = 16
87 if params.geometry == 'fullscreen':
88 cmd = 'rdesktop -f -N %s %s -a %s' % (params.rdp_options, params.rdp_server, _depth)
89 else:
90 cmd = 'rdesktop -g %s -N %s %s -a %s' % (params.geometry, params.rdp_options, params.rdp_server, _depth)
91
92
93 if cmd:
94 cmd = '"%s"' % cmd
95
96 if ((type(params) == X2GoSessionParams) and params.published_applications and cmd == ''):
97 cmd = 'PUBLISHED'
98
99 return cmd
100
101
103 """\
104 In command strings X2Go server scripts expect blanks being rewritten to ,,X2GO_SPACE_CHAR''.
105
106 @param cmd: command that has to be rewritten for passing to the server
107 @type cmd: C{str}
108
109 @return: the command with blanks rewritten to ,,X2GO_SPACE_CHAR''
110 @rtype: C{str}
111
112 """
113
114 if cmd:
115 cmd = cmd.replace(" ", "X2GO_SPACE_CHAR")
116 return cmd
117
118
120 """\
121 The L{X2GoSessionParams} class is used to store all parameters that
122 C{X2GoTerminalSession} backend objects are constructed with.
123
124 """
126 """\
127 Rewrite the X2Go session type, so that the X2Go server
128 can understand it (C{desktop} -> C{D}, etc.).
129
130 Also if the object's C{command} property is a known window
131 manager, the session type will be set to 'D'
132 (i.e. desktop).
133
134 @return: 'D' if session should probably a desktop session,
135 'R' for rootless sessions, 'P' for sessions providing published
136 applications, and 'S' for desktop sharing sessions
137 @rtype: C{str}
138
139 """
140 cmd = self.cmd
141 published = self.published_applications
142
143 if published and self.cmd in ('', 'PUBLISHED'):
144 self.session_type = 'P'
145 self.cmd = 'PUBLISHED'
146 else:
147 if cmd == 'RDP' or cmd.startswith('rdesktop') or cmd.startswith('xfreedrp'):
148 if self.geometry == 'fullscreen': self.session_type = 'D'
149 else: self.session_type = 'R'
150 elif cmd == 'XDMCP':
151 self.session_type = 'D'
152 elif cmd in _X2GO_DESKTOPSESSIONS.keys():
153 self.session_type = 'D'
154 elif os.path.basename(cmd) in _X2GO_DESKTOPSESSIONS.values():
155 self.session_type = 'D'
156
157 if self.session_type in ("D", "desktop"):
158 self.session_type = 'D'
159 elif self.session_type in ("S", "shared", "shadow"):
160 self.session_type = 'S'
161 elif self.session_type in ("R", "rootless", "application"):
162 self.session_type = 'R'
163 elif self.session_type in ("P", "published", "published_applications"):
164 self.session_type = 'P'
165
166 return self.session_type
167
168 - def update(self, **properties_to_be_updated):
169 """\
170 Update all properties in the object L{X2GoSessionParams} object from
171 the passed on dictionary.
172
173 @param properties_to_be_updated: a dictionary with L{X2GoSessionParams}
174 property names as keys und their values to be update in
175 L{X2GoSessionParams} object.
176 @type properties_to_be_updated: C{dict}
177
178 """
179 for key in properties_to_be_updated.keys():
180 setattr(self, key, properties_to_be_updated[key] or '')
181 self.rewrite_session_type()
182
183
185 """\
186 Class for managing X2Go terminal sessions on a remote X2Go server via Paramiko/SSH.
187
188 With the L{x2go.backends.terminal.plain.X2GoTerminalSession} class you can start new X2Go sessions, resume suspended
189 sessions or suspend resp. terminate currently running sessions on a
190 connected X2Go server.
191
192 An L{x2go.backends.terminal.plain.X2GoTerminalSession} object uses two main data structure classes:
193
194 - L{X2GoSessionParams}: stores all parameters that have been passed to the
195 constructor method.
196
197 - C{X2GoServerSessionInfo*} backend class: when starting or resuming a session, an object of this class
198 will be used to store all information retrieved from the X2Go server.
199
200 The terminal session instance works closely together (i.e. depends on) a connected control
201 session instance (e.g. L{x2go.backends.control.plain.X2GoControlSession}). You never should use either of them as a standalone
202 instance. Both, control session and terminal session(s) get managed/controlled via L{X2GoSession} instances.
203
204 """
205 - def __init__(self, control_session, session_info=None,
206 geometry="800x600", depth=_local_color_depth, link="adsl", pack="16m-jpeg-9", dpi='',
207 cache_type="unix-kde",
208 kbtype='null/null', kblayout='null', kbvariant='null',
209 clipboard='both',
210 session_type="application", snd_system='pulse', snd_port=4713, cmd=None,
211 published_applications=False,
212 set_session_title=False, session_title="", applications=[],
213 rdp_server=None, rdp_options=None,
214 xdmcp_server=None,
215 convert_encoding=False, server_encoding='UTF-8', client_encoding='UTF-8',
216 rootdir=None,
217 profile_name='UNKNOWN', profile_id=utils._genSessionProfileId(),
218 print_action=None, print_action_args={},
219 info_backend=_BACKENDS['X2GoServerSessionInfo']['default'],
220 list_backend=_BACKENDS['X2GoServerSessionList']['default'],
221 proxy_backend=_BACKENDS['X2GoProxy']['default'], proxy_options={},
222 printing_backend=_BACKENDS['X2GoClientPrinting']['default'],
223 client_rootdir=os.path.join(_LOCAL_HOME, _X2GO_CLIENT_ROOTDIR),
224 sessions_rootdir=os.path.join(_LOCAL_HOME, _X2GO_SESSIONS_ROOTDIR),
225 session_instance=None,
226 logger=None, loglevel=log.loglevel_DEFAULT):
227 """\
228 Initialize an X2Go session. With the L{x2go.backends.terminal.plain.X2GoTerminalSession} class you can start
229 new X2Go sessions, resume suspended sessions or suspend resp. terminate
230 currently running sessions on a connected X2Go server.
231
232 @param geometry: screen geometry of the X2Go session. Can be either C{<width>x<height>},
233 C{maximize} or C{fullscreen}
234 @type geometry: C{str}
235 @param depth: color depth in bits (common values: C{16}, C{24})
236 @type depth: C{int}
237 @param link: network link quality (either one of C{modem}, C{isdn}, C{adsl}, C{wan} or C{lan})
238 @type link: C{str}
239 @param pack: compression method for NX based session proxying
240 @type pack: C{str}
241 @param dpi: dots-per-inch value for the session screen (has an impact on the font size on screen)
242 @type dpi: C{str}
243 @param cache_type: a dummy parameter that is passed to the L{x2go.backends.proxy.base.X2GoProxy}. In NX Proxy
244 (class C{X2GoProxyNX3}) this originally is the session name. With X2Go it
245 defines the name of the NX cache directory. Best is to leave it untouched.
246 @type cache_type: C{str}
247 @param kbtype: keyboard type, e.g. C{pc105/us} (default), C{pc105/de}, ...
248 @type kbtype: C{str}
249 @param kblayout: keyboard layout, e.g. C{us} (default), C{de}, C{fr}, ...
250 @type kblayout: C{str}
251 @param kbvariant: keyboard variant, e.g. C{nodeadkeys} (for C{de} layout), C{intl} (for C{us} layout), etc.
252 @type kbvariant: C{str}
253 @param clipboard: clipboard mode (C{both}: bidirectional copy+paste, C{server}: copy+paste from server to
254 client, C{client}: copy+paste from client to server, C{none}: disable clipboard completely
255 @type clipboard: C{str}
256 @param session_type: either C{desktop}, C{application} (rootless session) or C{shared}
257 @type session_type: C{str}
258 @param snd_system: sound system to be used on server (C{none}, C{pulse} (default),
259 C{arts} (obsolete) or C{esd})
260 @type snd_system: C{str}
261 @param snd_port: local sound port for network capable audio system
262 @type snd_port: C{int}
263 @param cmd: command to be run on X2Go server after session start (only used
264 when L{x2go.backends.terminal.plain.X2GoTerminalSession.start()} is called, ignored on resume, suspend etc.
265 @type cmd: C{str}
266 @param published_applications: session is published applications provider
267 @type published_applications: C{bool}
268 @param set_session_title: modify the session title (i.e. the Window title) of desktop or shared desktop sessions
269 @type set_session_title: C{bool}
270 @param session_title: session title for this (desktop or shared desktop) session
271 @type session_title: C{str}
272 @param applications: applications available for rootless application execution
273 @type applications: C{list}
274 @param rdp_server: host name of server-side RDP server
275 @type rdp_server: C{str}
276 @param rdp_options: options for the C{rdesktop} command executed on the X2Go server (RDP proxy mode of X2Go)
277 @type rdp_options: C{str}
278 @param xdmcp_server: XDMCP server to connect to
279 @type xdmcp_server: C{str}
280 @param convert_encoding: convert file system encodings between server and client (for client-side shared folders)
281 @type convert_encoding: C{bool}
282 @param server_encoding: server-side file system / session encoding
283 @type server_encoding: C{str}
284 @param client_encoding: client-side file system encoding (if client-side is MS Windows, this parameter gets overwritten to WINDOWS-1252)
285 @type client_encoding: C{str}
286 @param rootdir: X2Go session directory, normally C{~/.x2go}
287 @type rootdir: C{str}
288 @param profile_name: the session profile name for this terminal session
289 @type profile_name: C{str}
290 @param profile_id: the session profile ID for this terminal session
291 @type profile_id: C{str}
292 @param print_action: either a print action short name (PDFVIEW, PDFSAVE, PRINT, PRINTCMD) or the
293 resp. C{X2GoPrintActionXXX} class (where XXX equals one of the given short names)
294 @type print_action: C{str} or C{class}
295 @param print_action_args: optional arguments for a given print_action (for further info refer to
296 L{X2GoPrintActionPDFVIEW}, L{X2GoPrintActionPDFSAVE}, L{X2GoPrintActionPRINT} and L{X2GoPrintActionPRINTCMD})
297 @type print_action_args: dict
298 @param info_backend: backend for handling storage of server session information
299 @type info_backend: C{X2GoServerSessionInfo*} instance
300 @param list_backend: backend for handling storage of session list information
301 @type list_backend: C{X2GoServerSessionList*} instance
302 @param proxy_backend: backend for handling the X-proxy connections
303 @type proxy_backend: C{X2GoProxy*} instance
304 @param proxy_options: a set of very C{X2GoProxy} backend specific options; any option that is not known
305 to the C{X2GoProxy} backend will simply be ignored
306 @type proxy_options: C{dict}
307 @param client_rootdir: client base dir (default: ~/.x2goclient)
308 @type client_rootdir: C{str}
309 @param sessions_rootdir: sessions base dir (default: ~/.x2go)
310 @type sessions_rootdir: C{str}
311 @param session_instance: the L{X2GoSession} instance that is parent to this terminal session
312 @type session_instance: C{obj}
313 @param logger: you can pass an L{X2GoLogger} object to the
314 L{x2go.backends.terminal.plain.X2GoTerminalSession} constructor
315 @type logger: L{X2GoLogger} instance
316 @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be
317 constructed with the given loglevel
318 @type loglevel: C{int}
319
320 """
321 self.proxy = None
322 self.proxy_subprocess = None
323 self.proxy_options = proxy_options
324
325 self.telekinesis_client = None
326
327 self.active_threads = []
328 self.reverse_tunnels = {}
329
330 self.print_queue = None
331 self.mimebox_queue = None
332
333 if logger is None:
334 self.logger = log.X2GoLogger(loglevel=loglevel)
335 else:
336 self.logger = copy.deepcopy(logger)
337 self.logger.tag = __NAME__
338
339 self.control_session = control_session
340 self.reverse_tunnels = self.control_session.get_transport().reverse_tunnels
341
342 self.client_rootdir = client_rootdir
343 self.sessions_rootdir = sessions_rootdir
344
345 self.params = X2GoSessionParams()
346
347 self.params.geometry = str(geometry)
348 self.params.link = str(link)
349 self.params.pack = str(pack)
350 self.params.dpi = str(dpi)
351 self.params.cache_type = str(cache_type)
352 self.params.session_type = str(session_type)
353 self.params.kbtype = str(kbtype)
354 self.params.kblayout = str(kblayout)
355 self.params.kbvariant = str(kbvariant)
356 self.params.snd_system = str(snd_system)
357 self.params.cmd = str(cmd)
358 self.params.depth = str(depth)
359 self.params.clipboard = str(clipboard)
360
361 self.params.published_applications = published_applications
362 self.published_applications = published_applications
363
364 self.params.rdp_server = str(rdp_server)
365 self.params.rdp_options = str(rdp_options)
366 self.params.xdmcp_server = str(xdmcp_server)
367
368 self.params.convert_encoding = convert_encoding
369 self.params.client_encoding = str(client_encoding)
370 self.params.server_encoding = str(server_encoding)
371
372 self.params.rootdir = (type(rootdir) is types.StringType) and rootdir or self.sessions_rootdir
373 self.params.update()
374
375 self.profile_name = profile_name
376 self.set_session_title = set_session_title
377 self.session_title = session_title
378 self.session_window = None
379 self.proxy_backend = utils._get_backend_class(proxy_backend, "X2GoProxy")
380
381 self.snd_port = snd_port
382 self.print_action = print_action
383 self.print_action_args = print_action_args
384 self.printing_backend = utils._get_backend_class(printing_backend, "X2GoClientPrinting")
385 self.session_instance = session_instance
386 if self.session_instance:
387 self.client_instance = self.session_instance.client_instance
388 else:
389 self.client_instance = None
390
391 self._share_local_folder_busy = False
392 self._mk_sessions_rootdir(self.params.rootdir)
393
394 self.session_info = session_info
395 if self.session_info is not None:
396 if self.session_info.name:
397 self.session_info.local_container = os.path.join(self.params.rootdir, 'S-%s' % self.session_info.name)
398 else:
399 raise x2go_exceptions.X2GoTerminalSessionException('no valid session info availble')
400 else:
401 self.session_info = info_backend()
402
403 self._share_local_folder_lock = threading.Lock()
404 self._cleaned_up = False
405
406 self.telekinesis_subprocess = None
407
409 """\
410 Tidy up if terminal session gets destructed.
411
412 """
413 self._x2go_tidy_up()
414
416 """\
417 Tidy up this terminal session...
418 - shutdown all forwarding and reverse forwarding tunnels
419 - shutdown the print queue (if running)
420 - shutdown the MIME box queue (if running)
421 - clear the session info
422
423 """
424 self._share_local_folder_lock.release()
425 self.release_telekinesis()
426 self.release_proxy()
427 self.session_window = None
428 self.update_session_window_file()
429
430 try:
431
432 if self.control_session.get_transport() is not None:
433 try:
434 for _tunnel in [ _tun[1] for _tun in self.reverse_tunnels[self.session_info.name].values() ]:
435 if _tunnel is not None:
436 _tunnel.__del__()
437 except KeyError:
438 pass
439
440 if self.print_queue is not None:
441 self.print_queue.__del__()
442
443 if self.mimebox_queue is not None:
444 self.mimebox_queue.__del__()
445
446 except AttributeError:
447 pass
448
449 self.session_info.clear()
450
452 """\
453 Create the server-side session root dir (normally ~/.x2go).
454
455 @param rootdir: server-side session root directory
456 @type rootdir: C{str}
457
458 """
459 try:
460 os.makedirs(rootdir)
461 except OSError, e:
462 if e.errno == 17:
463
464 pass
465 else:
466 raise OSError, e
467
469 """\
470 Purge client-side session dir (session cache directory).
471
472 """
473 if self.session_info.name:
474 shutil.rmtree('%s/S-%s' % (self.params.rootdir, self.session_info), ignore_errors=True)
475
477 """\
478 Purge client-side session dir (C-<display> directory)
479
480 """
481 if self.session_info.display:
482 shutil.rmtree('%s/S-%s' % (self.params.rootdir, self.session_info.display), ignore_errors=True)
483
485 """\
486 Retrieve the X2Go session's name from the session info object.
487
488 @return: the session name
489 @rtype: C{str}
490
491 """
492 return self.session_info.name
493
495 """\
496 Retrieve the X2Go session's session info object.
497
498 @return: the session info object
499 @rtype: C{X2GoServerSessionInfo*}
500
501 """
502 return self.session_info
503
505 """\
506 Retrieve the X2Go session's command as stored in the session parameter object.
507
508 @return: the session command
509 @rtype: C{str}
510
511 """
512 return self.params.cmd
513
515 """\
516 Retrieve the X2Go session's session type as stored in the session parameter object.
517
518 @return: the session type
519 @rtype: C{str}
520
521 """
522 return self.params.session_type
523
525 """\
526 Initialize Paramiko/SSH reverse forwarding tunnel for X2Go sound.
527
528 Currently supported audio protocols:
529
530 - PulseAudio
531 - Esound (not tested very much)
532
533 @raise X2GoControlSessionException: if the control session of this terminal session is not connected
534
535 """
536 _tunnel = None
537 if self.reverse_tunnels[self.session_info.name]['snd'][1] is None:
538 if self.params.snd_system == 'pulse':
539 self.logger('initializing PulseAudio sound support in X2Go session', loglevel=log.loglevel_INFO)
540
541
542
543 cookie_filepath = None
544 if os.path.exists(os.path.normpath('%s/.pulse-cookie' % _LOCAL_HOME)):
545 cookie_filepath = os.path.normpath('%s/.pulse-cookie' % _LOCAL_HOME)
546 elif os.path.exists(os.path.normpath('%s/.config/pulse/cookie' % _LOCAL_HOME)):
547 cookie_filepath = os.path.normpath('%s/.config/pulse/cookie' % _LOCAL_HOME)
548 if cookie_filepath is not None:
549
550 cmd_line = "echo 'default-server=127.0.0.1:%s'>%s/.pulse-client.conf;" % (self.session_info.snd_port, self.session_info.remote_container) + \
551 "echo 'cookie-file=%s/.pulse-cookie'>>%s/.pulse-client.conf" % (self.session_info.remote_container, self.session_info.remote_container)
552 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line)
553
554 self.control_session._x2go_sftp_put(local_path=cookie_filepath, remote_path='%s/.pulse-cookie' % self.session_info.remote_container)
555
556
557 _tunnel = rforward.X2GoRevFwTunnel(server_port=self.session_info.snd_port,
558 remote_host='127.0.0.1',
559 remote_port=self.snd_port,
560 ssh_transport=self.control_session.get_transport(),
561 session_instance=self.session_instance,
562 logger=self.logger
563 )
564 else:
565 if self.client_instance:
566 self.client_instance.HOOK_on_sound_tunnel_failed(profile_name=self.profile_name, session_name=self.session_info.name)
567 elif self.params.snd_system == 'arts':
568
569
570
571 self.logger('the ArtsD sound server (as in KDE3) is obsolete and will not be supported by Python X2Go...', loglevel=log.loglevel_WARNING)
572
573 elif self.params.snd_system == 'esd':
574
575
576
577
578 self.logger('initializing ESD sound support in X2Go session', loglevel=log.loglevel_INFO)
579 self.control_session._x2go_sftp_put(local_path='%s/.esd_auth' % _LOCAL_HOME, remote_path='%s/.esd_auth' % self.control_session._x2go_remote_home)
580
581
582 _tunnel = rforward.X2GoRevFwTunnel(server_port=self.session_info.snd_port,
583 remote_host='127.0.0.1',
584 remote_port=self.snd_port,
585 ssh_transport=self.control_session.get_transport(),
586 session_instance=self.session_instance,
587 logger=self.logger
588 )
589
590
591 if _tunnel is not None:
592 self.reverse_tunnels[self.session_info.name]['snd'] = (self.session_info.snd_port, _tunnel)
593 _tunnel.start()
594 self.active_threads.append(_tunnel)
595
596 else:
597
598 self.reverse_tunnels[self.session_info.name]['snd'][1].resume()
599
601 """\
602 Initialize Paramiko/SSH reverse forwarding tunnel for X2Go folder sharing.
603
604 """
605 if not self.control_session.is_sshfs_available():
606 raise x2go_exceptions.X2GoUserException('Remote user %s is not allowed to share SSHFS resources with the server.' % self.session_info.username)
607
608
609 ssh_transport = self.control_session.get_transport()
610 if self.reverse_tunnels[self.session_info.name]['sshfs'][1] is None:
611
612 _tunnel = sftpserver.X2GoRevFwTunnelToSFTP(server_port=self.session_info.sshfs_port,
613 ssh_transport=ssh_transport,
614 auth_key=self.control_session._x2go_session_auth_rsakey,
615 session_instance=self.session_instance,
616 logger=self.logger
617 )
618
619 if _tunnel is not None:
620 self.reverse_tunnels[self.session_info.name]['sshfs'] = (self.session_info.sshfs_port, _tunnel)
621 _tunnel.start()
622 self.active_threads.append(_tunnel)
623 while not _tunnel.ready:
624 gevent.sleep(.1)
625
626 else:
627
628 self.reverse_tunnels[self.session_info.name]['sshfs'][1].resume()
629
631 """\
632 Pause reverse SSH tunnel of name <name>.
633
634 @param name: tunnel name (either of C{sshfs}, C{snd})
635 @type name: C{str}
636
637 """
638 _tunnel = self.reverse_tunnels[self.session_info.name][name][1]
639 if _tunnel is not None:
640 _tunnel.pause()
641
643 """\
644 Shutdown (pause) Paramiko/SSH reverse forwarding tunnel for X2Go sound.
645
646 """
647 self._x2go_pause_rev_fw_tunnel('snd')
648
650 """\
651 Shutdown (pause) Paramiko/SSH reverse forwarding tunnel for X2Go folder sharing.
652
653 """
654 self._x2go_pause_rev_fw_tunnel('sshfs')
655
657 """\
658 Initialize X2Go print spooling.
659
660 @raise X2GoUserException: if the X2Go printing feature is not available to this user
661
662 """
663 if not self.control_session.is_sshfs_available():
664 raise x2go_exceptions.X2GoUserException('Remote user %s is not allowed to use client-side printing.' % self.session_info.username)
665
666 spool_dir = os.path.join(self.session_info.local_container, 'spool')
667 if not os.path.exists(spool_dir):
668 os.makedirs(spool_dir)
669 self.share_local_folder(local_path=spool_dir, folder_type='spool')
670 self.print_queue = printqueue.X2GoPrintQueue(profile_name=self.profile_name,
671 session_name=self.session_info.name,
672 spool_dir=spool_dir,
673 print_action=self.print_action,
674 print_action_args=self.print_action_args,
675 client_instance=self.client_instance,
676 printing_backend=self.printing_backend,
677 logger=self.logger,
678 )
679 self.print_queue.start()
680 self.active_threads.append(self.print_queue)
681
683 """\
684 Set a print action for the next incoming print jobs.
685
686 This method is a wrapper for L{X2GoPrintQueue}C{.set_print_action()}.
687
688 @param print_action: print action name or object (i.e. an instance of C{X2GoPrintAction*} classes)
689 @type print_action: C{str} or C{X2GoPrintAction*}
690 @param kwargs: print action specific parameters
691 @type kwargs: dict
692
693 """
694 self.print_queue.set_print_action(print_action, logger=self.logger, **kwargs)
695
697 """\
698 Shutdown (pause) the X2Go Print Queue thread.
699
700 """
701 if self.print_queue is not None:
702 self.print_queue.pause()
703
705 """\
706 Return the server-side printing spooldir path.
707
708 @return: the directory for remote print job spooling
709 @rtype: C{str}
710
711 """
712 return '%s/%s' % (self.session_info.remote_container, 'spool')
713
714 - def start_mimebox(self, mimebox_extensions=[], mimebox_action=None):
715 """\
716 Initialize the X2Go MIME box. Open/process incoming files from the server-side locally.
717
718 @param mimebox_extensions: file name extensions that are allowed for local opening/processing
719 @type mimebox_extensions: C{list}
720 @param mimebox_action: MIME box action given as name or object (i.e. an instance of C{X2GoMIMEboxAction*} classes).
721 @type mimebox_action: C{str} or C{obj}
722
723 @raise X2GoUserException: if the X2Go MIME box feature is not available to this user
724
725 """
726 if not self.control_session.is_sshfs_available():
727 raise x2go_exceptions.X2GoUserException('Remote user %s is not allowed to use the MIME box.' % self.session_info.username)
728
729 mimebox_dir = os.path.join(self.session_info.local_container, 'mimebox')
730 if not os.path.exists(mimebox_dir):
731 os.makedirs(mimebox_dir)
732 self.share_local_folder(local_path=mimebox_dir, folder_type='mimebox')
733 self.mimebox_queue = mimebox.X2GoMIMEboxQueue(profile_name=self.profile_name,
734 session_name=self.session_info.name,
735 mimebox_dir=mimebox_dir,
736 mimebox_extensions=mimebox_extensions,
737 mimebox_action=mimebox_action,
738 client_instance=self.client_instance,
739 logger=self.logger,
740 )
741 self.mimebox_queue.start()
742 self.active_threads.append(self.mimebox_queue)
743
745 """\
746 Set a MIME box action for the next incoming MIME jobs.
747
748 This method is a wrapper for L{X2GoMIMEboxQueue}C{set_mimebox_action()}.
749
750 @param mimebox_action: MIME box action name or object (i.e. an instance of C{X2GoMIMEboxAction*} classes)
751 @type mimebox_action: C{str} or C{X2GoMIMEboxAction*}
752 @param kwargs: MIME box action specific parameters
753 @type kwargs: dict
754
755 """
756 self.mimebox_queue.set_mimebox_action(mimebox_action, logger=self.logger, **kwargs)
757
759 """\
760 Shutdown (pause) the X2Go MIME box Queue thread.
761
762 """
763 if self.mimebox_queue is not None:
764 self.mimebox_queue.pause()
765
767 """\
768 Return the server-side MIME box spooldir path.
769
770 @return: the directory where remote MIME box jobs are placed
771 @rtype: C{str}
772
773 """
774 return '%s/%s' % (self.session_info.remote_container, 'mimebox')
775
777 """\
778 Initialize Telekinesis client for X2Go.
779
780 """
781 if self.telekinesis_client is not None:
782 del self.telekinesis_client
783 self.telekinesis_client = None
784 if self.telekinesis_subprocess is not None:
785 self.telekinesis_subprocess = None
786 if self.session_info.tekictrl_port != -1 and self.session_info.tekidata_port != -1:
787 self.telekinesis_client = telekinesis.X2GoTelekinesisClient(session_info=self.session_info,
788 ssh_transport=self.control_session.get_transport(),
789 sessions_rootdir=self.sessions_rootdir,
790 session_instance=self.session_instance,
791 logger=self.logger)
792 if self.telekinesis_client.has_telekinesis_client():
793 self.telekinesis_subprocess, telekinesis_ok = self.telekinesis_client.start_telekinesis()
794 else:
795 del self.telekinesis_client
796 self.telekinesis_client = None
797
799 """\
800 Test if this terminal's session info object is write-protected.
801
802 @return: C{True}, if session info object is read-only, C{False} for read-write.
803 @rtype: C{bool}
804
805 """
806 self.session_info.is_protected()
807
809 """\
810 Protect this terminal session's info object against updates.
811
812 """
813 self.session_info.protect()
814
816 """\
817 Allow session info updates from within the list_sessions method of the control session.
818
819 """
820 self.session_info.unprotect()
821
823 """\
824 Share a local folder with the X2Go session.
825
826 @param local_path: the full path to an existing folder on the local
827 file system
828 @type local_path: C{str}
829 @param folder_type: one of 'disk' (a folder on your local hard drive), 'rm' (removeable device),
830 'cdrom' (CD/DVD Rom) or 'spool' (for X2Go print spooling)
831 @type folder_type: C{str}
832
833 @return: returns C{True} if the local folder has been successfully mounted within the X2Go server session
834 @rtype: C{bool}
835
836 @raise X2GoUserException: if local folder sharing is not available to this user
837 @raise Exception: any other exception occuring on the way is passed through by this method
838
839 """
840 if not self.control_session.is_sshfs_available():
841 raise x2go_exceptions.X2GoUserException('Remote user %s is not allowed to share local folders with the server.' % self.session_info.username)
842
843 if local_path is None:
844 self.logger('no folder name given...', log.loglevel_WARN)
845 return False
846
847 if type(local_path) not in (types.StringType, types.UnicodeType):
848 self.logger('folder name needs to be of type StringType...', log.loglevel_WARN)
849 return False
850
851 if not os.path.exists(local_path):
852 self.logger('local folder does not exist: %s' % local_path, log.loglevel_WARN)
853 return False
854
855 local_path = os.path.normpath(local_path)
856 self.logger('sharing local folder: %s' % local_path, log.loglevel_INFO)
857
858 _auth_rsakey = self.control_session._x2go_session_auth_rsakey
859 _host_rsakey = defaults.RSAHostKey
860
861 _tmp_io_object = cStringIO.StringIO()
862 _auth_rsakey.write_private_key(_tmp_io_object)
863 _tmp_io_object.write('----BEGIN RSA IDENTITY----')
864 _tmp_io_object.write('%s %s' % (_host_rsakey.get_name(),_host_rsakey.get_base64(),))
865
866
867 _x2go_key_fname = '%s/%s/%s' % (os.path.dirname(self.session_info.remote_container), 'ssh', 'key.z%s' % self.session_info.agent_pid)
868 _x2go_key_bundle = _tmp_io_object.getvalue()
869
870
871 self._share_local_folder_lock.acquire()
872
873 try:
874 self.control_session._x2go_sftp_write(_x2go_key_fname, _x2go_key_bundle)
875
876 _convert_encoding = self.params.convert_encoding
877 _client_encoding = self.params.client_encoding
878 _server_encoding = self.params.server_encoding
879
880 if _X2GOCLIENT_OS == 'Windows':
881 if local_path.startswith('\\\\'):
882
883 if 'X2GO_MOUNT_UNCPATHS' in self.control_session.get_server_features():
884 local_path = local_path.repalce('\\\\', '/uncpath/')
885 else:
886 local_path = local_path.repalce('\\\\', '/windrive/')
887 local_path = local_path.replace('\\', '/')
888 else:
889 local_path = local_path.replace('\\', '/')
890 local_path = local_path.replace(':', '')
891 local_path = '/windrive/%s' % local_path
892 _convert_encoding = True
893 _client_encoding = 'WINDOWS-1252'
894
895 if _convert_encoding:
896 export_iconv_settings = 'export X2GO_ICONV=modules=iconv,from_code=%s,to_code=%s && ' % (_client_encoding, _server_encoding)
897 else:
898 export_iconv_settings = ''
899
900 if folder_type == 'disk':
901
902 cmd_line = [ '%sexport HOSTNAME &&' % export_iconv_settings,
903 'x2gomountdirs',
904 'dir',
905 str(self.session_info.name),
906 '\'%s\'' % _CURRENT_LOCAL_USER,
907 _x2go_key_fname,
908 '%s__REVERSESSH_PORT__%s; ' % (local_path, self.session_info.sshfs_port),
909 'rm -f %s %s.ident' % (_x2go_key_fname, _x2go_key_fname),
910 ]
911
912 elif folder_type == 'spool':
913
914 cmd_line = [ '%sexport HOSTNAME &&' % export_iconv_settings,
915 'x2gomountdirs',
916 'dir',
917 str(self.session_info.name),
918 '\'%s\'' % _CURRENT_LOCAL_USER,
919 _x2go_key_fname,
920 '%s__PRINT_SPOOL___REVERSESSH_PORT__%s; ' % (local_path, self.session_info.sshfs_port),
921 'rm -f %s %s.ident' % (_x2go_key_fname, _x2go_key_fname),
922 ]
923
924 elif folder_type == 'mimebox':
925
926 cmd_line = [ '%sexport HOSTNAME &&' % export_iconv_settings,
927 'x2gomountdirs',
928 'dir',
929 str(self.session_info.name),
930 '\'%s\'' % _CURRENT_LOCAL_USER,
931 _x2go_key_fname,
932 '%s__MIMEBOX_SPOOL___REVERSESSH_PORT__%s; ' % (local_path, self.session_info.sshfs_port),
933 'rm -f %s %s.ident' % (_x2go_key_fname, _x2go_key_fname),
934 ]
935
936 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line)
937 _stdout = stdout.read().split('\n')
938 self.logger('x2gomountdirs output is: %s' % _stdout, log.loglevel_NOTICE)
939
940 except:
941 self._share_local_folder_lock.release()
942 raise
943 self._share_local_folder_lock.release()
944
945 if len(_stdout) >= 6 and _stdout[5].endswith('ok'):
946 return True
947 return False
948
950 """\
951 Unshare all local folders mount in the X2Go session.
952
953 @return: returns C{True} if all local folders could be successfully unmounted from the X2Go server session
954 @rtype: C{bool}
955
956 """
957 self.logger('unsharing all local folders from session %s' % self.session_info, log.loglevel_INFO)
958
959 cmd_line = [ 'export HOSTNAME &&',
960 'x2goumount-session',
961 self.session_info.name,
962 ]
963
964 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line)
965 if not stderr.read():
966 self.logger('x2goumount-session (all mounts) for session %s has been successful' % self.session_info, log.loglevel_NOTICE)
967 return True
968 else:
969 self.logger('x2goumount-session (all mounts) for session %s failed' % self.session_info, log.loglevel_ERROR)
970 return False
971
973 """\
974 Unshare local folder given as <local_path> from X2Go session.
975
976 @return: returns C{True} if the local folder <local_path> could be successfully unmounted from the X2Go server session
977 @rtype: C{bool}
978
979 """
980 self.logger('unsharing local folder from session %s' % self.session_info, log.loglevel_INFO)
981
982 cmd_line = [ 'export HOSTNAME &&',
983 'x2goumount-session',
984 self.session_info.name,
985 "'%s'" % local_path,
986 ]
987
988 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line)
989 if not stderr.read():
990 self.logger('x2goumount-session (%s) for session %s has been successful' % (local_path, self.session_info, ), log.loglevel_NOTICE)
991 return True
992 else:
993 self.logger('x2goumount-session (%s) for session %s failed' % (local_path, self.session_info, ), log.loglevel_ERROR)
994 return False
995
997 """\
998 Retrieve the session's color depth.
999
1000 @return: the session's color depth
1001 @rtype: C{int}
1002
1003 """
1004 return self.params.depth
1005
1007 """\
1008 Automatically generate an appropriate human-readable session window title.
1009
1010 The session window title will be provider in the C{session_title} property of
1011 this method.
1012
1013 @param dont_set: generate the session window title, but do not actually set it
1014 @type dont_set: C{bool}
1015
1016 """
1017 _generic_title = 'X2GO-%s' % self.session_info.name
1018
1019
1020 self.session_title = self.session_title.strip()
1021
1022 if self.params.session_type == 'D':
1023 if self.set_session_title:
1024
1025 if not self.session_title:
1026 self.session_title = '%s for %s@%s' % (self.params.cmd, self.control_session.remote_username(), self.control_session.get_hostname())
1027
1028 else:
1029
1030 self.session_title = _generic_title
1031
1032 elif self.params.session_type == 'S':
1033 if self.set_session_title:
1034
1035 shared_user = _generic_title.split('XSHAD')[1]
1036 shared_display = _generic_title.split('XSHAD')[2].replace('PP', ':').split("_")[0]
1037
1038 self.session_title = 'Desktop %s@%s shared with %s@%s' % (shared_user, shared_display, self.control_session.remote_username(), self.control_session.get_hostname())
1039
1040 else:
1041
1042 self.session_title = _generic_title
1043
1044 else:
1045
1046 self.session_title = _generic_title
1047
1048 if self.session_title != _generic_title and not dont_set:
1049 self.set_session_window_title(title=self.session_title)
1050
1052 """\
1053 Try for <timeout> seconds to find the X2Go session window of this
1054 terminal session.
1055
1056 A background thread will get spawned for this operation.
1057
1058 @param timeout: try for <timeout> seconds to find the session window
1059 @type timeout: C{int}
1060
1061 """
1062 gevent.spawn(self._find_session_window, timeout=timeout)
1063
1065 """\
1066 Try for <timeout> seconds to find the X2Go session window of this
1067 terminal session.
1068
1069 @param timeout: try for <timeout> seconds to find the session window
1070 @type timeout: C{int}
1071
1072 """
1073 self.session_window = None
1074
1075
1076
1077 timeout += 1
1078 while timeout:
1079
1080 timeout -= 1
1081
1082 window = utils.find_session_window(self.session_info.name)
1083
1084 if window is not None:
1085 if _X2GOCLIENT_OS == "Windows":
1086 self.logger('Session window handle for session %s is: %s' % (self.session_info.name, window), loglevel=log.loglevel_DEBUG)
1087 else:
1088 self.logger('Session window ID for session %s is: %s' % (self.session_info.name, window.id), loglevel=log.loglevel_DEBUG)
1089 self.session_window = window
1090
1091 self.update_session_window_file()
1092 break
1093
1094 gevent.sleep(1)
1095
1097 """\
1098 Create a file that contains information on the session window.
1099 .
1100 If the file already exists, its content gets update.
1101
1102 """
1103 session_window_file = os.path.join(self.session_info.local_container, 'session.window')
1104 if self.session_window is not None:
1105 f = open(session_window_file,'w')
1106 if _X2GOCLIENT_OS != "Windows":
1107 _id = self.session_window.id
1108 else:
1109 _id = self.session_window
1110 f.write('ID:{window_id}\n'.format(window_id=_id))
1111 f.close()
1112 self.logger('Updating session.window file %s: Window-ID->%s' % (session_window_file, _id), loglevel=log.loglevel_DEBUG)
1113 else:
1114 try:
1115 os.remove(session_window_file)
1116 except OSError,e:
1117
1118 self.logger('The session window file %s is already gone (we failed to remove it with error: %s). In most cases this can be safely ignored.' % (session_window_file, str(e)), loglevel=log.loglevel_INFO)
1119
1121 """\
1122 Modify the session window title.
1123
1124 A background thread will get spawned for this operation.
1125
1126 @param title: new title for the terminal session's session window
1127 @type title: C{str}
1128 @param timeout: try for <timeout> seconds to find the session window
1129 @type timeout: C{int}
1130
1131 """
1132 gevent.spawn(self._set_session_window_title, title=title.strip(), timeout=timeout)
1133
1135 """\
1136 Modify the session window title.
1137
1138 @param title: new title for the terminal session's session window
1139 @type title: C{str}
1140 @param timeout: try for <timeout> seconds to find the session window
1141 @type timeout: C{int}
1142
1143 """
1144 self.session_title = title
1145
1146 if not self.session_title:
1147 self.auto_session_title(dont_set=True)
1148
1149 timeout += 1
1150 while timeout:
1151
1152 timeout -= 1
1153
1154 if self.session_window is not None:
1155 self.logger('Setting session window title for session %s is: %s' % (self.session_info.name, self.session_title), loglevel=log.loglevel_DEBUG)
1156 utils.set_session_window_title(self.session_window, self.session_title)
1157 break
1158
1159 gevent.sleep(1)
1160
1162 """\
1163 Try for <timeout> seconds to raise the X2Go session window of this
1164 terminal session to the top and bring it to focus.
1165
1166 A background thread will get spawned for this operation.
1167
1168 @param timeout: try for <timeout> seconds to raise the session window
1169 @type timeout: C{int}
1170
1171 """
1172 gevent.spawn(self._raise_session_window, timeout=timeout)
1173
1175 """
1176 Try for <timeout> seconds to raise the X2Go session window of this
1177 terminal session to the top and bring it to focus.
1178
1179 @param timeout: try for <timeout> seconds to raise the session window
1180 @type timeout: C{int}
1181
1182 """
1183 timeout += 1
1184 while timeout:
1185
1186 timeout -= 1
1187
1188 if self.session_window is not None:
1189
1190 utils.raise_session_window(self.session_window)
1191 break
1192
1193 gevent.sleep(1)
1194
1196 """\
1197 ,,Guess'' if the command C{<cmd>} exists on the X2Go server and is executable.
1198 The expected result is not 100% safe, however, it comes with a high probability to
1199 be correct.
1200
1201 @param cmd: session command
1202 @type cmd: C{str}
1203
1204 @return: C{True} if this method reckons that the command is executable on the remote X2Go server
1205 @rtype: C{bool}
1206
1207 """
1208 test_cmd = None;
1209
1210 cmd = cmd.strip('"').strip('"')
1211 if cmd.find('RDP') != -1:
1212 cmd = 'rdesktop'
1213
1214 if cmd in _X2GO_GENERIC_APPLICATIONS:
1215 return True
1216 if cmd in _X2GO_DESKTOPSESSIONS.keys():
1217 return True
1218 elif 'XSHAD' in cmd:
1219 return True
1220 elif 'PUBLISHED' in cmd and 'X2GO_PUBLISHED_APPLICATIONS' in self.control_session.get_server_features():
1221 return True
1222 elif cmd and cmd.startswith('/'):
1223
1224 test_cmd = 'test -x %s && which %s && echo OK' % (cmd, os.path.basename(cmd.split()[0]))
1225 elif cmd and '/' not in cmd.split()[0]:
1226
1227 test_cmd = 'which %s && echo OK' % os.path.basename(cmd.split()[0])
1228
1229 if test_cmd:
1230 (stdin, stdout, stderr) = self.control_session._x2go_exec_command([test_cmd])
1231 _stdout = stdout.read()
1232 return _stdout.find('OK') != -1
1233 else:
1234 return False
1235
1237 """\
1238 Run a command in this session.
1239
1240 After L{x2go.backends.terminal.plain.X2GoTerminalSession.start()} has been called
1241 one or more commands can be executed with L{x2go.backends.terminal.plain.X2GoTerminalSession.run_command()}
1242 within the current X2Go session.
1243
1244 @param cmd: Command to be run
1245 @type cmd: C{str}
1246 @param env: add server-side environment variables
1247 @type env: C{dict}
1248
1249 @return: stdout.read() and stderr.read() as returned by the run command
1250 on the X2Go server
1251 @rtype: C{tuple} of C{str}
1252
1253 """
1254 if not self.has_command(_rewrite_cmd(str(self.params.cmd), params=self.params)):
1255 if self.client_instance:
1256 self.client_instance.HOOK_no_such_command(profile_name=self.profile_name, session_name=self.session_info.name, cmd=self.params.cmd)
1257 return False
1258
1259 if cmd in ("", None):
1260 if self.params.cmd is None:
1261 cmd = 'TERMINAL'
1262 else:
1263 cmd = self.params.cmd
1264
1265 if cmd == 'XDMCP':
1266
1267 return None
1268
1269 if 'XSHAD' in cmd:
1270
1271 return None
1272
1273 self.params.update(cmd=cmd)
1274
1275
1276 if '/' in cmd:
1277 cmd = os.path.basename(cmd)
1278
1279 cmd_line = [ "setsid x2goruncommand",
1280 str(self.session_info.display),
1281 str(self.session_info.agent_pid),
1282 str(self.session_info.name),
1283 str(self.session_info.snd_port),
1284 _rewrite_blanks(_rewrite_cmd(cmd, params=self.params)),
1285 str(self.params.snd_system),
1286 str(self.params.session_type),
1287 "1>/dev/null 2>/dev/null & exit",
1288 ]
1289
1290 if self.params.snd_system == 'pulse':
1291 cmd_line = [ 'PULSE_CLIENTCONFIG=%s/.pulse-client.conf' % self.session_info.remote_container ] + cmd_line
1292
1293 if env:
1294 for env_var in env.keys():
1295 cmd_line = [ '%s=%s' % (env_var, env[env_var]) ] + cmd_line
1296
1297 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line)
1298
1299 if self.params.kbtype not in ('null/null', 'auto') and (self.params.kblayout not in ('null', '') or self.params.kbvariant not in ('null', '')):
1300 self.set_keyboard(layout=self.params.kblayout, variant=self.params.kbvariant)
1301
1302 return stdout.read(), stderr.read()
1303
1305 """\
1306 Is this (terminal) session a desktop session?
1307
1308 @return: Returns C{True} is this session is a desktop session.
1309 @rtype: C{bool}
1310
1311 """
1312 if self.session_info:
1313 return self.session_info.is_desktop_session()
1314 return False
1315
1317 """\
1318 Is this (terminal) session a published applications provider?
1319
1320 @return: Returns C{True} is this session is a provider session for published applications.
1321 @rtype: C{bool}
1322
1323 """
1324 if self.session_info and self.is_running():
1325 return self.session_info.is_published_applications_provider()
1326 return False
1327
1329 """\
1330 Set the keyboard layout and variant for this (running) session.
1331
1332 @param layout: keyboard layout to be set
1333 @type layout: C{str}
1334 @param variant: keyboard variant to be set
1335 @type variant: C{str}
1336
1337 @return: returns C{True} if the {setxkbmap} command could be executed successfully.
1338 @rtype: C{bool}
1339
1340 """
1341 if not self.is_running():
1342 return False
1343
1344 cmd_line = [ 'export DISPLAY=:%s && ' % str(self.session_info.display),
1345 'setxkbmap '
1346 ]
1347
1348 if layout != 'null':
1349 self.logger('setting keyboad layout ,,%s\'\' for session %s' % (layout, self.session_info), log.loglevel_INFO)
1350 cmd_line.append('-layout %s' % layout)
1351 if variant != 'null':
1352 self.logger('setting keyboad variant ,,%s\'\' for session %s' % (variant, self.session_info), log.loglevel_INFO)
1353 cmd_line.append('-variant %s' % variant)
1354
1355 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line)
1356 _stderr = stderr.read()
1357 if not _stderr:
1358 self.logger('setting keyboard layout ,,%s\'\' and variant ,,%s\'\' for session %s has been successful' % (layout, variant, self.session_info), log.loglevel_NOTICE)
1359 return True
1360 else:
1361 self.logger('setting keyboard layout ,,%s\'\' and variant ,,%s\'\' for session %s failed: %s' % (layout, variant, self.session_info, _stderr.replace('\n', ' ')), log.loglevel_ERROR)
1362 return False
1363
1365 """\
1366 Executed a published application.
1367
1368 @param exec_name: application to be executed
1369 @type exec_name: C{str}
1370 @param timeout: execution timeout
1371 @type timeout: C{int}
1372 @param env: session environment dictionary
1373 @type env: C{dict}
1374
1375 """
1376 cmd_line = [
1377 "export DISPLAY=:%s && " % str(self.session_info.display),
1378 "export X2GO_SESSION=%s && " % str(self.get_session_name()),
1379 ]
1380
1381 if self.params.snd_system == 'pulse':
1382 cmd_line.append("export PULSE_CLIENTCONFIG=%s/.pulse-client.conf && " % self.session_info.remote_container)
1383
1384 if env:
1385 for env_var in env.keys():
1386 cmd_line = [ 'export %s=%s && ' % (env_var, env[env_var]) ] + cmd_line
1387
1388 cmd_line.extend(
1389 [
1390 "setsid %s" % exec_name,
1391 "1>/dev/null 2>/dev/null & exit",
1392 ]
1393 )
1394
1395 self.logger('executing published application %s for %s with command line: %s' % (exec_name, self.profile_name, cmd_line), loglevel=log.loglevel_DEBUG)
1396 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line, timeout=timeout)
1397
1399 """\
1400 X2Go session OK?
1401
1402 @return: Returns C{True} if this X2Go (terminal) session is up and running,
1403 C{False} otherwise.
1404 @rtype: C{bool}
1405
1406 """
1407 _ok = bool(self.session_info.name and self.proxy.ok())
1408 return _ok
1409
1411 """\
1412 X2Go session running?
1413
1414 @return: Returns C{True} if this X2Go (terminal) session is in running state,
1415 C{False} otherwise.
1416 @rtype: C{bool}
1417
1418 """
1419 return self.session_info.is_running()
1420
1422 """\
1423 X2Go session suspended?
1424
1425 @return: Returns C{True} if this X2Go (terminal) session is in suspended state,
1426 C{False} otherwise.
1427 @rtype: C{bool}
1428
1429 """
1430 return self.session_info.is_suspended()
1431
1433 """\
1434 X2Go session connected?
1435
1436 @return: Returns C{True} if this X2Go session's Paramiko/SSH transport is
1437 connected/authenticated, C{False} else.
1438 @rtype: C{bool}
1439
1440 """
1441 return self.control_session.is_connected()
1442
1444 """\
1445 Start a new X2Go session.
1446
1447 @return: C{True} if session startup has been successful and the X2Go proxy is up-and-running
1448 @rtype: C{bool}
1449
1450 @raise X2GoTerminalSessionException: if the session startup failed
1451 @raise X2GoDesktopSharingDenied: if desktop sharing fails because of denial by the user running the desktop to be shared
1452
1453 """
1454 self.params.rewrite_session_type()
1455
1456 if not self.has_command(_rewrite_cmd(self.params.cmd, params=self.params)):
1457 if self.client_instance:
1458 self.client_instance.HOOK_no_such_command(profile_name=self.profile_name, session_name=self.session_info.name, cmd=self.params.cmd)
1459 return False
1460
1461 setkbd = "0"
1462 if self.params.kbtype != "null/null":
1463 setkbd = "1"
1464
1465 if '/' in self.params.cmd:
1466 self.params.cmd = os.path.basename(self.params.cmd)
1467
1468 self.params.rewrite_session_type()
1469
1470 if self.params.geometry == 'maximize':
1471 _geometry = utils.get_workarea_geometry()
1472 if _geometry is None or len(_geometry) != 2:
1473 _geometry = utils.get_desktop_geometry()
1474 if _geometry and len(_geometry) == 2:
1475 self.params.geometry = "%sx%s" % _geometry
1476 else:
1477 self.logger('failed to detect best maximized geometry of your client-side desktop', loglevel=log.loglevel_WARN)
1478 self.params.geometry = "1024x768"
1479
1480 cmd_line = [ "x2gostartagent",
1481 str(self.params.geometry),
1482 str(self.params.link),
1483 str(self.params.pack),
1484 str(self.params.cache_type+'-depth_'+self.params.depth),
1485 str(self.params.kblayout),
1486 str(self.params.kbtype),
1487 str(setkbd),
1488 str(self.params.session_type),
1489 str(self.params.cmd),
1490 ]
1491 if self.params.session_type != 'S':
1492 cmd_line.append(
1493 str(self.params.clipboard),
1494 )
1495
1496 if self.params.cmd == 'XDMCP' and self.params.xdmcp_server:
1497 cmd_line = ['X2GOXDMCP=%s' % self.params.xdmcp_server] + cmd_line
1498
1499 if self.params.dpi:
1500 cmd_line = ['X2GODPI=%s' % self.params.dpi] + cmd_line
1501
1502 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line)
1503
1504 _stdout = stdout.read()
1505 _stderr = stderr.read()
1506
1507
1508
1509 if "ACCESS DENIED" in _stderr and "XSHAD" in _stderr:
1510 raise x2go_exceptions.X2GoDesktopSharingDenied('X2Go desktop sharing has been denied by the remote user')
1511
1512 try:
1513 self.session_info.initialize(_stdout,
1514 username=self.control_session.remote_username(),
1515 hostname=self.control_session.remote_peername(),
1516 )
1517 except ValueError:
1518 raise x2go_exceptions.X2GoTerminalSessionException("failed to start X2Go session")
1519 except IndexError:
1520 raise x2go_exceptions.X2GoTerminalSessionException("failed to start X2Go session")
1521
1522
1523 self.session_info.local_container = os.path.join(self.params.rootdir, 'S-%s' % self.session_info.name)
1524
1525 self.session_info.remote_container = '%s/.x2go/C-%s' % (self.control_session._x2go_remote_home,
1526 self.session_info.name,
1527 )
1528
1529
1530 self.proxy = self.proxy_backend(session_info=self.session_info,
1531 ssh_transport=self.control_session.get_transport(),
1532 sessions_rootdir=self.sessions_rootdir,
1533 session_instance=self.session_instance,
1534 proxy_options=self.proxy_options,
1535 logger=self.logger)
1536 self.proxy_subprocess, proxy_ok = self.proxy.start_proxy()
1537
1538 if proxy_ok:
1539 self.active_threads.append(self.proxy)
1540
1541 if self.params.session_type in ('D', 'S'):
1542 self.find_session_window()
1543 self.auto_session_window_title()
1544 self.raise_session_window()
1545
1546 if self.params.published_applications:
1547 self.control_session.get_published_applications()
1548
1549 else:
1550 raise x2go_exceptions.X2GoTerminalSessionException("failed to start X2Go session")
1551
1552 return proxy_ok
1553
1555 """\
1556 Resume a running/suspended X2Go session.
1557
1558 @return: C{True} if the session could successfully be resumed
1559 @rtype: C{bool}
1560
1561 @raise X2GoTerminalSessionException: if the terminal session failed to update server-side reported port changes
1562
1563 """
1564 setkbd = "0"
1565 if self.params.kbtype != "null/null":
1566 setkbd = "1"
1567
1568 if self.params.geometry == 'maximize':
1569 _geometry = utils.get_workarea_geometry()
1570 if _geometry is None or len(_geometry) != 2:
1571 _geometry = utils.get_desktop_geometry()
1572 if _geometry and len(_geometry) == 2:
1573 self.params.geometry = "%sx%s" % _geometry
1574 else:
1575 self.logger('failed to detect best maxmimized geometry of your client-side desktop, using 1024x768 instead', loglevel=log.loglevel_WARN)
1576 self.params.geometry = "1024x768"
1577
1578 cmd_line = [ "x2goresume-session", self.session_info.name,
1579 self.params.geometry,
1580 self.params.link,
1581 self.params.pack,
1582 self.params.kblayout,
1583 self.params.kbtype,
1584 setkbd,
1585 self.params.clipboard,
1586 ]
1587
1588 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line)
1589
1590
1591 for stdout_line in stdout.read():
1592 try:
1593 _new_value = stdout_line.split("=")[1].strip()
1594 if 'gr_port=' in stdout_line and _new_value != str(self.session_info.graphics_port):
1595 try:
1596 self.session_info.graphics_port = int(_new_value)
1597 self.logger('re-allocating graphics port for session %s, old server-side port is in use; new graphics port is %s' % (self.session_info, self.session_info.graphics_port), loglevel=log.loglevel_NOTICE)
1598 except TypeError:
1599
1600 raise x2go_exceptions.X2GoTerminalSessionException('Failed to retrieve new graphics port from server. X2Go Session cannot be resumed.')
1601 elif 'sound_port=' in stdout_line and _new_value != str(self.session_info.snd_port):
1602 try:
1603 self.session_info.snd_port = int(_new_value)
1604 self.logger('re-allocating sound port for session %s, old server-side port is in use; new sound port is %s' % (self.session_info, self.session_info.snd_port), loglevel=log.loglevel_NOTICE)
1605 except TypeError:
1606 self.logger('Failed to retrieve new sound port from server for session %s, session will be without sound.' % self.session_info, loglevel=log.loglevel_WARN)
1607 elif 'fs_port=' in stdout_line and _new_value != str(self.session_info.sshfs_port):
1608 try:
1609 self.session_info.sshfs_port = int(_new_value)
1610 self.logger('re-allocating sshfs port for session %s, old server-side port is in use; new sshfs port is %s' % (self.session_info, self.session_info.sshfs_port), loglevel=log.loglevel_NOTICE)
1611 except TypeError:
1612 self.logger('Failed to retrieve new sshfs port from server for session %s, session will be without client-side folder sharing. Neither will there be X2Go printing nor X2Go MIME box support.' % self.session_info, loglevel=log.loglevel_WARN)
1613 except IndexError:
1614 continue
1615
1616
1617 self.session_info.local_container = os.path.join(self.params.rootdir, 'S-%s' % self.session_info.name)
1618
1619 self.session_info.remote_container = '%s/.x2go/C-%s' % (self.control_session._x2go_remote_home,
1620 self.session_info.name,
1621 )
1622 self.proxy = self.proxy_backend(session_info=self.session_info,
1623 ssh_transport=self.control_session.get_transport(),
1624 sessions_rootdir=self.sessions_rootdir,
1625 session_instance=self.session_instance,
1626 proxy_options=self.proxy_options,
1627 logger=self.logger
1628 )
1629 self.proxy_subprocess, proxy_ok = self.proxy.start_proxy()
1630
1631 if proxy_ok:
1632 self.params.depth = self.session_info.name.split('_')[2][2:]
1633
1634
1635 self.session_info.username = self.control_session.remote_username()
1636
1637 if self.params.kbtype not in ('null/null', 'auto') and (self.params.kblayout not in ('null', '') or self.params.kbvariant not in ('null', '')):
1638 self.set_keyboard(layout=self.params.kblayout, variant=self.params.kbvariant)
1639
1640 if self.params.session_type in ('D', 'S'):
1641 self.find_session_window()
1642 self.auto_session_window_title()
1643 self.raise_session_window()
1644
1645 if self.is_published_applications_provider():
1646 self.control_session.get_published_applications()
1647 self.published_applications = True
1648 else:
1649 raise x2go_exceptions.X2GoTerminalSessionException("failed to start X2Go session")
1650
1651 return proxy_ok
1652
1654 """\
1655 Suspend this X2Go (terminal) session.
1656
1657 @return: C{True} if the session terminal could be successfully suspended
1658 @rtype: C{bool}
1659
1660 """
1661 self.release_telekinesis()
1662 self.control_session.suspend(session_name=self.session_info.name)
1663 self.release_proxy()
1664
1665
1666 _ret = True
1667
1668 return _ret
1669
1688
1690 """\
1691 Let the X2Go proxy command cleanly die away... (by calling its destructor).
1692
1693 """
1694 if self.proxy is not None:
1695 self.proxy.__del__()
1696 self.proxy = None
1697
1699 """\
1700 Let the attached Telekinesis client cleanly die away... (by calling its destructor).
1701
1702 """
1703 if self.telekinesis_client is not None:
1704 self.telekinesis_client.__del__()
1705 self.telekinesis_client = None
1706
1708 """\
1709 Do some cleanup after this session has terminated.
1710
1711 """
1712
1713
1714
1715 if not self._cleaned_up and self.session_info.name:
1716
1717
1718 self.logger('cleaning up session %s after termination' % self.session_info, loglevel=log.loglevel_NOTICE)
1719
1720
1721 if self.logger.get_loglevel() & log.loglevel_DEBUG != log.loglevel_DEBUG:
1722
1723 self._rm_session_dirtree()
1724 self._rm_desktop_dirtree()
1725
1726 self._cleaned_up = True
1727
1729 """\
1730 Test if this terminal session is a rootless session.
1731
1732 @return: C{True} if this session is of session type rootless ('R').
1733 @rtype: C{bool}
1734
1735 """
1736 self.params.rewrite_session_type()
1737 return self.params.session_type == 'R'
1738
1740 """\
1741 Test if this terminal session is a desktop sharing (aka shadow) session.
1742
1743 @return: C{True} if this session is of session type shadow ('S').
1744 @rtype: C{bool}
1745
1746 """
1747 self.params.rewrite_session_type()
1748 return self.params.session_type == 'S'
1749
1751 """\
1752 Test if this terminal session is a published applications session.
1753
1754 @return: C{True} if this session is of session type published applications ('P').
1755 @rtype: C{bool}
1756
1757 """
1758 self.params.rewrite_session_type()
1759 return self.params.session_type == 'P'
1760