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 if self._share_local_folder_lock.locked():
425 self._share_local_folder_lock.release()
426 self.release_telekinesis()
427 self.release_proxy()
428 self.session_window = None
429 self.update_session_window_file()
430
431 try:
432
433 if self.control_session.get_transport() is not None:
434 try:
435 for _tunnel in [ _tun[1] for _tun in self.reverse_tunnels[self.session_info.name].values() ]:
436 if _tunnel is not None:
437 _tunnel.__del__()
438 except KeyError:
439 pass
440
441 if self.print_queue is not None:
442 self.print_queue.__del__()
443
444 if self.mimebox_queue is not None:
445 self.mimebox_queue.__del__()
446
447 except AttributeError:
448 pass
449
450 self.session_info.clear()
451
453 """\
454 Create the server-side session root dir (normally ~/.x2go).
455
456 @param rootdir: server-side session root directory
457 @type rootdir: C{str}
458
459 """
460 try:
461 os.makedirs(rootdir)
462 except OSError, e:
463 if e.errno == 17:
464
465 pass
466 else:
467 raise OSError, e
468
470 """\
471 Purge client-side session dir (session cache directory).
472
473 """
474 if self.session_info.name:
475 shutil.rmtree('%s/S-%s' % (self.params.rootdir, self.session_info), ignore_errors=True)
476
478 """\
479 Purge client-side session dir (C-<display> directory)
480
481 """
482 if self.session_info.display:
483 shutil.rmtree('%s/S-%s' % (self.params.rootdir, self.session_info.display), ignore_errors=True)
484
486 """\
487 Retrieve the X2Go session's name from the session info object.
488
489 @return: the session name
490 @rtype: C{str}
491
492 """
493 return self.session_info.name
494
496 """\
497 Retrieve the X2Go session's session info object.
498
499 @return: the session info object
500 @rtype: C{X2GoServerSessionInfo*}
501
502 """
503 return self.session_info
504
506 """\
507 Retrieve the X2Go session's command as stored in the session parameter object.
508
509 @return: the session command
510 @rtype: C{str}
511
512 """
513 return self.params.cmd
514
516 """\
517 Retrieve the X2Go session's session type as stored in the session parameter object.
518
519 @return: the session type
520 @rtype: C{str}
521
522 """
523 return self.params.session_type
524
526 """\
527 Initialize Paramiko/SSH reverse forwarding tunnel for X2Go sound.
528
529 Currently supported audio protocols:
530
531 - PulseAudio
532 - Esound (not tested very much)
533
534 @raise X2GoControlSessionException: if the control session of this terminal session is not connected
535
536 """
537 _tunnel = None
538 if self.reverse_tunnels[self.session_info.name]['snd'][1] is None:
539 if self.params.snd_system == 'pulse':
540 self.logger('initializing PulseAudio sound support in X2Go session', loglevel=log.loglevel_INFO)
541
542
543
544 cookie_filepath = None
545 if os.path.exists(os.path.normpath('%s/.pulse-cookie' % _LOCAL_HOME)):
546 cookie_filepath = os.path.normpath('%s/.pulse-cookie' % _LOCAL_HOME)
547 elif os.path.exists(os.path.normpath('%s/.config/pulse/cookie' % _LOCAL_HOME)):
548 cookie_filepath = os.path.normpath('%s/.config/pulse/cookie' % _LOCAL_HOME)
549 if cookie_filepath is not None:
550
551 cmd_line = "echo 'default-server=127.0.0.1:%s'>%s/.pulse-client.conf;" % (self.session_info.snd_port, self.session_info.remote_container) + \
552 "echo 'cookie-file=%s/.pulse-cookie'>>%s/.pulse-client.conf" % (self.session_info.remote_container, self.session_info.remote_container)
553 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line)
554
555 self.control_session._x2go_sftp_put(local_path=cookie_filepath, remote_path='%s/.pulse-cookie' % self.session_info.remote_container)
556
557
558 _tunnel = rforward.X2GoRevFwTunnel(server_port=self.session_info.snd_port,
559 remote_host='127.0.0.1',
560 remote_port=self.snd_port,
561 ssh_transport=self.control_session.get_transport(),
562 session_instance=self.session_instance,
563 logger=self.logger
564 )
565 else:
566 if self.client_instance:
567 self.client_instance.HOOK_on_sound_tunnel_failed(profile_name=self.profile_name, session_name=self.session_info.name)
568 elif self.params.snd_system == 'arts':
569
570
571
572 self.logger('the ArtsD sound server (as in KDE3) is obsolete and will not be supported by Python X2Go...', loglevel=log.loglevel_WARN)
573
574 elif self.params.snd_system == 'esd':
575
576
577
578
579 self.logger('initializing ESD sound support in X2Go session', loglevel=log.loglevel_INFO)
580 self.control_session._x2go_sftp_put(local_path='%s/.esd_auth' % _LOCAL_HOME, remote_path='%s/.esd_auth' % self.control_session._x2go_remote_home)
581
582
583 _tunnel = rforward.X2GoRevFwTunnel(server_port=self.session_info.snd_port,
584 remote_host='127.0.0.1',
585 remote_port=self.snd_port,
586 ssh_transport=self.control_session.get_transport(),
587 session_instance=self.session_instance,
588 logger=self.logger
589 )
590
591
592 if _tunnel is not None:
593 self.reverse_tunnels[self.session_info.name]['snd'] = (self.session_info.snd_port, _tunnel)
594 _tunnel.start()
595 self.active_threads.append(_tunnel)
596
597 else:
598
599 self.reverse_tunnels[self.session_info.name]['snd'][1].resume()
600
602 """\
603 Initialize Paramiko/SSH reverse forwarding tunnel for X2Go folder sharing.
604
605 """
606 if not self.control_session.is_sshfs_available():
607 raise x2go_exceptions.X2GoUserException('Remote user %s is not allowed to share SSHFS resources with the server.' % self.session_info.username)
608
609
610 ssh_transport = self.control_session.get_transport()
611 if self.reverse_tunnels[self.session_info.name]['sshfs'][1] is None:
612
613 _tunnel = sftpserver.X2GoRevFwTunnelToSFTP(server_port=self.session_info.sshfs_port,
614 ssh_transport=ssh_transport,
615 auth_key=self.control_session._x2go_session_auth_rsakey,
616 session_instance=self.session_instance,
617 logger=self.logger
618 )
619
620 if _tunnel is not None:
621 self.reverse_tunnels[self.session_info.name]['sshfs'] = (self.session_info.sshfs_port, _tunnel)
622 _tunnel.start()
623 self.active_threads.append(_tunnel)
624 while not _tunnel.ready:
625 gevent.sleep(.1)
626
627 else:
628
629 self.reverse_tunnels[self.session_info.name]['sshfs'][1].resume()
630
632 """\
633 Pause reverse SSH tunnel of name <name>.
634
635 @param name: tunnel name (either of C{sshfs}, C{snd})
636 @type name: C{str}
637
638 """
639 _tunnel = self.reverse_tunnels[self.session_info.name][name][1]
640 if _tunnel is not None:
641 _tunnel.pause()
642
644 """\
645 Shutdown (pause) Paramiko/SSH reverse forwarding tunnel for X2Go sound.
646
647 """
648 self._x2go_pause_rev_fw_tunnel('snd')
649
651 """\
652 Shutdown (pause) Paramiko/SSH reverse forwarding tunnel for X2Go folder sharing.
653
654 """
655 self._x2go_pause_rev_fw_tunnel('sshfs')
656
658 """\
659 Initialize X2Go print spooling.
660
661 @raise X2GoUserException: if the X2Go printing feature is not available to this user
662
663 """
664 if not self.control_session.is_sshfs_available():
665 raise x2go_exceptions.X2GoUserException('Remote user %s is not allowed to use client-side printing.' % self.session_info.username)
666
667 spool_dir = os.path.join(self.session_info.local_container, 'spool')
668 if not os.path.exists(spool_dir):
669 os.makedirs(spool_dir)
670 self.share_local_folder(local_path=spool_dir, folder_type='spool')
671 self.print_queue = printqueue.X2GoPrintQueue(profile_name=self.profile_name,
672 session_name=self.session_info.name,
673 spool_dir=spool_dir,
674 print_action=self.print_action,
675 print_action_args=self.print_action_args,
676 client_instance=self.client_instance,
677 printing_backend=self.printing_backend,
678 logger=self.logger,
679 )
680 self.print_queue.start()
681 self.active_threads.append(self.print_queue)
682
684 """\
685 Set a print action for the next incoming print jobs.
686
687 This method is a wrapper for L{X2GoPrintQueue}C{.set_print_action()}.
688
689 @param print_action: print action name or object (i.e. an instance of C{X2GoPrintAction*} classes)
690 @type print_action: C{str} or C{X2GoPrintAction*}
691 @param kwargs: print action specific parameters
692 @type kwargs: dict
693
694 """
695 self.print_queue.set_print_action(print_action, logger=self.logger, **kwargs)
696
698 """\
699 Shutdown (pause) the X2Go Print Queue thread.
700
701 """
702 if self.print_queue is not None:
703 self.print_queue.pause()
704
706 """\
707 Return the server-side printing spooldir path.
708
709 @return: the directory for remote print job spooling
710 @rtype: C{str}
711
712 """
713 return '%s/%s' % (self.session_info.remote_container, 'spool')
714
715 - def start_mimebox(self, mimebox_extensions=[], mimebox_action=None):
716 """\
717 Initialize the X2Go MIME box. Open/process incoming files from the server-side locally.
718
719 @param mimebox_extensions: file name extensions that are allowed for local opening/processing
720 @type mimebox_extensions: C{list}
721 @param mimebox_action: MIME box action given as name or object (i.e. an instance of C{X2GoMIMEboxAction*} classes).
722 @type mimebox_action: C{str} or C{obj}
723
724 @raise X2GoUserException: if the X2Go MIME box feature is not available to this user
725
726 """
727 if not self.control_session.is_sshfs_available():
728 raise x2go_exceptions.X2GoUserException('Remote user %s is not allowed to use the MIME box.' % self.session_info.username)
729
730 mimebox_dir = os.path.join(self.session_info.local_container, 'mimebox')
731 if not os.path.exists(mimebox_dir):
732 os.makedirs(mimebox_dir)
733 self.share_local_folder(local_path=mimebox_dir, folder_type='mimebox')
734 self.mimebox_queue = mimebox.X2GoMIMEboxQueue(profile_name=self.profile_name,
735 session_name=self.session_info.name,
736 mimebox_dir=mimebox_dir,
737 mimebox_extensions=mimebox_extensions,
738 mimebox_action=mimebox_action,
739 client_instance=self.client_instance,
740 logger=self.logger,
741 )
742 self.mimebox_queue.start()
743 self.active_threads.append(self.mimebox_queue)
744
746 """\
747 Set a MIME box action for the next incoming MIME jobs.
748
749 This method is a wrapper for L{X2GoMIMEboxQueue}C{set_mimebox_action()}.
750
751 @param mimebox_action: MIME box action name or object (i.e. an instance of C{X2GoMIMEboxAction*} classes)
752 @type mimebox_action: C{str} or C{X2GoMIMEboxAction*}
753 @param kwargs: MIME box action specific parameters
754 @type kwargs: dict
755
756 """
757 self.mimebox_queue.set_mimebox_action(mimebox_action, logger=self.logger, **kwargs)
758
760 """\
761 Shutdown (pause) the X2Go MIME box Queue thread.
762
763 """
764 if self.mimebox_queue is not None:
765 self.mimebox_queue.pause()
766
768 """\
769 Return the server-side MIME box spooldir path.
770
771 @return: the directory where remote MIME box jobs are placed
772 @rtype: C{str}
773
774 """
775 return '%s/%s' % (self.session_info.remote_container, 'mimebox')
776
778 """\
779 Initialize Telekinesis client for X2Go.
780
781 """
782 if self.telekinesis_client is not None:
783 del self.telekinesis_client
784 self.telekinesis_client = None
785 if self.telekinesis_subprocess is not None:
786 self.telekinesis_subprocess = None
787 if self.session_info.tekictrl_port != -1 and self.session_info.tekidata_port != -1:
788 self.telekinesis_client = telekinesis.X2GoTelekinesisClient(session_info=self.session_info,
789 ssh_transport=self.control_session.get_transport(),
790 sessions_rootdir=self.sessions_rootdir,
791 session_instance=self.session_instance,
792 logger=self.logger)
793 if self.telekinesis_client.has_telekinesis_client():
794 self.telekinesis_subprocess, telekinesis_ok = self.telekinesis_client.start_telekinesis()
795 else:
796 del self.telekinesis_client
797 self.telekinesis_client = None
798
800 """\
801 Test if this terminal's session info object is write-protected.
802
803 @return: C{True}, if session info object is read-only, C{False} for read-write.
804 @rtype: C{bool}
805
806 """
807 self.session_info.is_protected()
808
810 """\
811 Protect this terminal session's info object against updates.
812
813 """
814 self.session_info.protect()
815
817 """\
818 Allow session info updates from within the list_sessions method of the control session.
819
820 """
821 self.session_info.unprotect()
822
824 """\
825 Share a local folder with the X2Go session.
826
827 @param local_path: the full path to an existing folder on the local
828 file system
829 @type local_path: C{str}
830 @param folder_type: one of 'disk' (a folder on your local hard drive), 'rm' (removeable device),
831 'cdrom' (CD/DVD Rom) or 'spool' (for X2Go print spooling)
832 @type folder_type: C{str}
833
834 @return: returns C{True} if the local folder has been successfully mounted within the X2Go server session
835 @rtype: C{bool}
836
837 @raise X2GoUserException: if local folder sharing is not available to this user
838 @raise Exception: any other exception occuring on the way is passed through by this method
839
840 """
841 if not self.control_session.is_sshfs_available():
842 raise x2go_exceptions.X2GoUserException('Remote user %s is not allowed to share local folders with the server.' % self.session_info.username)
843
844 if local_path is None:
845 self.logger('no folder name given...', log.loglevel_WARN)
846 return False
847
848 if type(local_path) not in (types.StringType, types.UnicodeType):
849 self.logger('folder name needs to be of type StringType...', log.loglevel_WARN)
850 return False
851
852 if not os.path.exists(local_path):
853 self.logger('local folder does not exist: %s' % local_path, log.loglevel_WARN)
854 return False
855
856 local_path = os.path.normpath(local_path)
857 self.logger('sharing local folder: %s' % local_path, log.loglevel_INFO)
858
859 _auth_rsakey = self.control_session._x2go_session_auth_rsakey
860 _host_rsakey = defaults.RSAHostKey
861
862 _tmp_io_object = cStringIO.StringIO()
863 _auth_rsakey.write_private_key(_tmp_io_object)
864 _tmp_io_object.write('----BEGIN RSA IDENTITY----')
865 _tmp_io_object.write('%s %s' % (_host_rsakey.get_name(),_host_rsakey.get_base64(),))
866
867
868 _x2go_key_fname = '%s/%s/%s' % (os.path.dirname(self.session_info.remote_container), 'ssh', 'key.z%s' % self.session_info.agent_pid)
869 _x2go_key_bundle = _tmp_io_object.getvalue()
870
871
872 self._share_local_folder_lock.acquire()
873
874 try:
875 self.control_session._x2go_sftp_write(_x2go_key_fname, _x2go_key_bundle)
876
877 _convert_encoding = self.params.convert_encoding
878 _client_encoding = self.params.client_encoding
879 _server_encoding = self.params.server_encoding
880
881 if _X2GOCLIENT_OS == 'Windows':
882 if local_path.startswith('\\\\'):
883
884 if 'X2GO_MOUNT_UNCPATHS' in self.control_session.get_server_features():
885 local_path = local_path.repalce('\\\\', '/uncpath/')
886 else:
887 local_path = local_path.repalce('\\\\', '/windrive/')
888 local_path = local_path.replace('\\', '/')
889 else:
890 local_path = local_path.replace('\\', '/')
891 local_path = local_path.replace(':', '')
892 local_path = '/windrive/%s' % local_path
893 _convert_encoding = True
894 _client_encoding = 'WINDOWS-1252'
895
896 if _convert_encoding:
897 export_iconv_settings = 'export X2GO_ICONV=modules=iconv,from_code=%s,to_code=%s && ' % (_client_encoding, _server_encoding)
898 else:
899 export_iconv_settings = ''
900
901 if folder_type == 'disk':
902
903 cmd_line = [ '%sexport HOSTNAME &&' % export_iconv_settings,
904 'x2gomountdirs',
905 'dir',
906 str(self.session_info.name),
907 '\'%s\'' % _CURRENT_LOCAL_USER,
908 _x2go_key_fname,
909 '%s__REVERSESSH_PORT__%s; ' % (local_path, self.session_info.sshfs_port),
910 'rm -f %s %s.ident' % (_x2go_key_fname, _x2go_key_fname),
911 ]
912
913 elif folder_type == 'spool':
914
915 cmd_line = [ '%sexport HOSTNAME &&' % export_iconv_settings,
916 'x2gomountdirs',
917 'dir',
918 str(self.session_info.name),
919 '\'%s\'' % _CURRENT_LOCAL_USER,
920 _x2go_key_fname,
921 '%s__PRINT_SPOOL___REVERSESSH_PORT__%s; ' % (local_path, self.session_info.sshfs_port),
922 'rm -f %s %s.ident' % (_x2go_key_fname, _x2go_key_fname),
923 ]
924
925 elif folder_type == 'mimebox':
926
927 cmd_line = [ '%sexport HOSTNAME &&' % export_iconv_settings,
928 'x2gomountdirs',
929 'dir',
930 str(self.session_info.name),
931 '\'%s\'' % _CURRENT_LOCAL_USER,
932 _x2go_key_fname,
933 '%s__MIMEBOX_SPOOL___REVERSESSH_PORT__%s; ' % (local_path, self.session_info.sshfs_port),
934 'rm -f %s %s.ident' % (_x2go_key_fname, _x2go_key_fname),
935 ]
936
937 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line)
938 _stdout = stdout.read().split('\n')
939 if _stdout[0]:
940 self.logger('x2gomountdirs stdout is: %s' % _stdout, log.loglevel_NOTICE)
941 _stderr = stderr.read().split('\n')
942 if _stderr[0]:
943 self.logger('x2gomountdirs stderr is: %s' % _stderr, log.loglevel_WARN)
944
945 except:
946 self._share_local_folder_lock.release()
947 raise
948 self._share_local_folder_lock.release()
949
950 if len(_stdout) >= 6 and _stdout[5].endswith('ok'):
951 return True
952 return False
953
955 """\
956 Unshare all local folders mount in the X2Go session.
957
958 @return: returns C{True} if all local folders could be successfully unmounted from the X2Go server session
959 @rtype: C{bool}
960
961 """
962 self.logger('unsharing all local folders from session %s' % self.session_info, log.loglevel_INFO)
963
964 cmd_line = [ 'export HOSTNAME &&',
965 'x2goumount-session',
966 self.session_info.name,
967 ]
968
969 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line)
970 if not stderr.read():
971 self.logger('x2goumount-session (all mounts) for session %s has been successful' % self.session_info, log.loglevel_NOTICE)
972 return True
973 else:
974 self.logger('x2goumount-session (all mounts) for session %s failed' % self.session_info, log.loglevel_ERROR)
975 return False
976
978 """\
979 Unshare local folder given as <local_path> from X2Go session.
980
981 @return: returns C{True} if the local folder <local_path> could be successfully unmounted from the X2Go server session
982 @rtype: C{bool}
983
984 """
985 self.logger('unsharing local folder from session %s' % self.session_info, log.loglevel_INFO)
986
987 cmd_line = [ 'export HOSTNAME &&',
988 'x2goumount-session',
989 self.session_info.name,
990 "'%s'" % local_path,
991 ]
992
993 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line)
994 if not stderr.read():
995 self.logger('x2goumount-session (%s) for session %s has been successful' % (local_path, self.session_info, ), log.loglevel_NOTICE)
996 return True
997 else:
998 self.logger('x2goumount-session (%s) for session %s failed' % (local_path, self.session_info, ), log.loglevel_ERROR)
999 return False
1000
1002 """\
1003 Retrieve the session's color depth.
1004
1005 @return: the session's color depth
1006 @rtype: C{int}
1007
1008 """
1009 return self.params.depth
1010
1012 """\
1013 Automatically generate an appropriate human-readable session window title.
1014
1015 The session window title will be provider in the C{session_title} property of
1016 this method.
1017
1018 @param dont_set: generate the session window title, but do not actually set it
1019 @type dont_set: C{bool}
1020
1021 """
1022 _generic_title = 'X2GO-%s' % self.session_info.name
1023
1024
1025 self.session_title = self.session_title.strip()
1026
1027 if self.params.session_type == 'D':
1028 if self.set_session_title:
1029
1030 if not self.session_title:
1031 self.session_title = '%s for %s@%s' % (self.params.cmd, self.control_session.remote_username(), self.control_session.get_hostname())
1032
1033 else:
1034
1035 self.session_title = _generic_title
1036
1037 elif self.params.session_type == 'S':
1038 if self.set_session_title:
1039
1040 shared_user = _generic_title.split('XSHAD')[1]
1041 shared_display = _generic_title.split('XSHAD')[2].replace('PP', ':').split("_")[0]
1042
1043 self.session_title = 'Desktop %s@%s shared with %s@%s' % (shared_user, shared_display, self.control_session.remote_username(), self.control_session.get_hostname())
1044
1045 else:
1046
1047 self.session_title = _generic_title
1048
1049 else:
1050
1051 self.session_title = _generic_title
1052
1053 if self.session_title != _generic_title and not dont_set:
1054 self.set_session_window_title(title=self.session_title)
1055
1057 """\
1058 Try for <timeout> seconds to find the X2Go session window of this
1059 terminal session.
1060
1061 A background thread will get spawned for this operation.
1062
1063 @param timeout: try for <timeout> seconds to find the session window
1064 @type timeout: C{int}
1065
1066 """
1067 gevent.spawn(self._find_session_window, timeout=timeout)
1068
1070 """\
1071 Try for <timeout> seconds to find the X2Go session window of this
1072 terminal session.
1073
1074 @param timeout: try for <timeout> seconds to find the session window
1075 @type timeout: C{int}
1076
1077 """
1078 self.session_window = None
1079
1080
1081
1082 timeout += 1
1083 while timeout:
1084
1085 timeout -= 1
1086
1087 window = utils.find_session_window(self.session_info.name)
1088
1089 if window is not None:
1090 if _X2GOCLIENT_OS == "Windows":
1091 self.logger('Session window handle for session %s is: %s' % (self.session_info.name, window), loglevel=log.loglevel_DEBUG)
1092 else:
1093 self.logger('Session window ID for session %s is: %s' % (self.session_info.name, window.id), loglevel=log.loglevel_DEBUG)
1094 self.session_window = window
1095
1096 self.update_session_window_file()
1097 break
1098
1099 gevent.sleep(1)
1100
1102 """\
1103 Create a file that contains information on the session window.
1104 .
1105 If the file already exists, its content gets update.
1106
1107 """
1108 session_window_file = os.path.join(self.session_info.local_container, 'session.window')
1109 if self.session_window is not None:
1110 f = open(session_window_file,'w')
1111 if _X2GOCLIENT_OS != "Windows":
1112 _id = self.session_window.id
1113 else:
1114 _id = self.session_window
1115 f.write('ID:{window_id}\n'.format(window_id=_id))
1116 f.close()
1117 self.logger('Updating session.window file %s: Window-ID->%s' % (session_window_file, _id), loglevel=log.loglevel_DEBUG)
1118 else:
1119 try:
1120 os.remove(session_window_file)
1121 except OSError,e:
1122
1123 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)
1124
1126 """\
1127 Modify the session window title.
1128
1129 A background thread will get spawned for this operation.
1130
1131 @param title: new title for the terminal session's session window
1132 @type title: C{str}
1133 @param timeout: try for <timeout> seconds to find the session window
1134 @type timeout: C{int}
1135
1136 """
1137 gevent.spawn(self._set_session_window_title, title=title.strip(), timeout=timeout)
1138
1140 """\
1141 Modify the session window title.
1142
1143 @param title: new title for the terminal session's session window
1144 @type title: C{str}
1145 @param timeout: try for <timeout> seconds to find the session window
1146 @type timeout: C{int}
1147
1148 """
1149 self.session_title = title
1150
1151 if not self.session_title:
1152 self.auto_session_title(dont_set=True)
1153
1154 timeout += 1
1155 while timeout:
1156
1157 timeout -= 1
1158
1159 if self.session_window is not None:
1160 self.logger('Setting session window title for session %s is: %s' % (self.session_info.name, self.session_title), loglevel=log.loglevel_DEBUG)
1161 utils.set_session_window_title(self.session_window, self.session_title)
1162 break
1163
1164 gevent.sleep(1)
1165
1167 """\
1168 Try for <timeout> seconds to raise the X2Go session window of this
1169 terminal session to the top and bring it to focus.
1170
1171 A background thread will get spawned for this operation.
1172
1173 @param timeout: try for <timeout> seconds to raise the session window
1174 @type timeout: C{int}
1175
1176 """
1177 gevent.spawn(self._raise_session_window, timeout=timeout)
1178
1180 """
1181 Try for <timeout> seconds to raise the X2Go session window of this
1182 terminal session to the top and bring it to focus.
1183
1184 @param timeout: try for <timeout> seconds to raise the session window
1185 @type timeout: C{int}
1186
1187 """
1188 timeout += 1
1189 while timeout:
1190
1191 timeout -= 1
1192
1193 if self.session_window is not None:
1194
1195 utils.raise_session_window(self.session_window)
1196 break
1197
1198 gevent.sleep(1)
1199
1201 """\
1202 ,,Guess'' if the command C{<cmd>} exists on the X2Go server and is executable.
1203 The expected result is not 100% safe, however, it comes with a high probability to
1204 be correct.
1205
1206 @param cmd: session command
1207 @type cmd: C{str}
1208
1209 @return: C{True} if this method reckons that the command is executable on the remote X2Go server
1210 @rtype: C{bool}
1211
1212 """
1213 test_cmd = None;
1214
1215 cmd = cmd.strip('"').strip('"')
1216 if cmd.find('RDP') != -1:
1217 cmd = 'rdesktop'
1218
1219 if cmd in _X2GO_GENERIC_APPLICATIONS:
1220 return True
1221 if cmd in _X2GO_DESKTOPSESSIONS.keys():
1222 return True
1223 elif 'XSHAD' in cmd:
1224 return True
1225 elif 'PUBLISHED' in cmd and 'X2GO_PUBLISHED_APPLICATIONS' in self.control_session.get_server_features():
1226 return True
1227 elif cmd and cmd.startswith('/'):
1228
1229 test_cmd = 'test -x %s && which %s && echo OK' % (cmd, os.path.basename(cmd.split()[0]))
1230 elif cmd and '/' not in cmd.split()[0]:
1231
1232 test_cmd = 'which %s && echo OK' % os.path.basename(cmd.split()[0])
1233
1234 if test_cmd:
1235 (stdin, stdout, stderr) = self.control_session._x2go_exec_command([test_cmd])
1236 _stdout = stdout.read()
1237 return _stdout.find('OK') != -1
1238 else:
1239 return False
1240
1242 """\
1243 Run a command in this session.
1244
1245 After L{x2go.backends.terminal.plain.X2GoTerminalSession.start()} has been called
1246 one or more commands can be executed with L{x2go.backends.terminal.plain.X2GoTerminalSession.run_command()}
1247 within the current X2Go session.
1248
1249 @param cmd: Command to be run
1250 @type cmd: C{str}
1251 @param env: add server-side environment variables
1252 @type env: C{dict}
1253
1254 @return: stdout.read() and stderr.read() as returned by the run command
1255 on the X2Go server
1256 @rtype: C{tuple} of C{str}
1257
1258 """
1259 if not self.has_command(_rewrite_cmd(str(self.params.cmd), params=self.params)):
1260 if self.client_instance:
1261 self.client_instance.HOOK_no_such_command(profile_name=self.profile_name, session_name=self.session_info.name, cmd=self.params.cmd)
1262 return False
1263
1264 if cmd in ("", None):
1265 if self.params.cmd is None:
1266 cmd = 'TERMINAL'
1267 else:
1268 cmd = self.params.cmd
1269
1270 if cmd == 'XDMCP':
1271
1272 return None
1273
1274 if 'XSHAD' in cmd:
1275
1276 return None
1277
1278 self.params.update(cmd=cmd)
1279
1280
1281 if '/' in cmd:
1282 cmd = os.path.basename(cmd)
1283
1284 cmd_line = [ "setsid x2goruncommand",
1285 str(self.session_info.display),
1286 str(self.session_info.agent_pid),
1287 str(self.session_info.name),
1288 str(self.session_info.snd_port),
1289 _rewrite_blanks(_rewrite_cmd(cmd, params=self.params)),
1290 str(self.params.snd_system),
1291 str(self.params.session_type),
1292 "1>/dev/null 2>/dev/null & exit",
1293 ]
1294
1295 if self.params.snd_system == 'pulse':
1296 cmd_line = [ 'PULSE_CLIENTCONFIG=%s/.pulse-client.conf' % self.session_info.remote_container ] + cmd_line
1297
1298 if env:
1299 for env_var in env.keys():
1300 cmd_line = [ '%s=%s' % (env_var, env[env_var]) ] + cmd_line
1301
1302 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line)
1303
1304 if self.params.kbtype not in ('null/null', 'auto') and (self.params.kblayout not in ('null', '') or self.params.kbvariant not in ('null', '')):
1305 self.set_keyboard(layout=self.params.kblayout, variant=self.params.kbvariant)
1306
1307 return stdout.read(), stderr.read()
1308
1310 """\
1311 Is this (terminal) session a desktop session?
1312
1313 @return: Returns C{True} is this session is a desktop session.
1314 @rtype: C{bool}
1315
1316 """
1317 if self.session_info:
1318 return self.session_info.is_desktop_session()
1319 return False
1320
1322 """\
1323 Is this (terminal) session a published applications provider?
1324
1325 @return: Returns C{True} is this session is a provider session for published applications.
1326 @rtype: C{bool}
1327
1328 """
1329 if self.session_info and self.is_running():
1330 return self.session_info.is_published_applications_provider()
1331 return False
1332
1334 """\
1335 Set the keyboard layout and variant for this (running) session.
1336
1337 @param layout: keyboard layout to be set
1338 @type layout: C{str}
1339 @param variant: keyboard variant to be set
1340 @type variant: C{str}
1341
1342 @return: returns C{True} if the {setxkbmap} command could be executed successfully.
1343 @rtype: C{bool}
1344
1345 """
1346 if not self.is_running():
1347 return False
1348
1349 cmd_line = [ 'export DISPLAY=:%s && ' % str(self.session_info.display),
1350 'setxkbmap '
1351 ]
1352
1353 if layout != 'null':
1354 self.logger('setting keyboad layout ,,%s\'\' for session %s' % (layout, self.session_info), log.loglevel_INFO)
1355 cmd_line.append('-layout %s' % layout)
1356 if variant != 'null':
1357 self.logger('setting keyboad variant ,,%s\'\' for session %s' % (variant, self.session_info), log.loglevel_INFO)
1358 cmd_line.append('-variant %s' % variant)
1359
1360 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line)
1361 _stderr = stderr.read()
1362 if not _stderr:
1363 self.logger('setting keyboard layout ,,%s\'\' and variant ,,%s\'\' for session %s has been successful' % (layout, variant, self.session_info), log.loglevel_NOTICE)
1364 return True
1365 else:
1366 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)
1367 return False
1368
1370 """\
1371 Executed a published application.
1372
1373 @param exec_name: application to be executed
1374 @type exec_name: C{str}
1375 @param timeout: execution timeout
1376 @type timeout: C{int}
1377 @param env: session environment dictionary
1378 @type env: C{dict}
1379
1380 """
1381 cmd_line = [
1382 "export DISPLAY=:%s && " % str(self.session_info.display),
1383 "export X2GO_SESSION=%s && " % str(self.get_session_name()),
1384 ]
1385
1386 if self.params.snd_system == 'pulse':
1387 cmd_line.append("export PULSE_CLIENTCONFIG=%s/.pulse-client.conf && " % self.session_info.remote_container)
1388
1389 if env:
1390 for env_var in env.keys():
1391 cmd_line = [ 'export %s=%s && ' % (env_var, env[env_var]) ] + cmd_line
1392
1393 cmd_line.extend(
1394 [
1395 "setsid %s" % exec_name,
1396 "1>/dev/null 2>/dev/null & exit",
1397 ]
1398 )
1399
1400 self.logger('executing published application %s for %s with command line: %s' % (exec_name, self.profile_name, cmd_line), loglevel=log.loglevel_DEBUG)
1401 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line, timeout=timeout)
1402
1404 """\
1405 X2Go session OK?
1406
1407 @return: Returns C{True} if this X2Go (terminal) session is up and running,
1408 C{False} otherwise.
1409 @rtype: C{bool}
1410
1411 """
1412 _ok = bool(self.session_info.name and self.proxy.ok())
1413 return _ok
1414
1416 """\
1417 X2Go session running?
1418
1419 @return: Returns C{True} if this X2Go (terminal) session is in running state,
1420 C{False} otherwise.
1421 @rtype: C{bool}
1422
1423 """
1424 return self.session_info.is_running()
1425
1427 """\
1428 X2Go session suspended?
1429
1430 @return: Returns C{True} if this X2Go (terminal) session is in suspended state,
1431 C{False} otherwise.
1432 @rtype: C{bool}
1433
1434 """
1435 return self.session_info.is_suspended()
1436
1438 """\
1439 X2Go session connected?
1440
1441 @return: Returns C{True} if this X2Go session's Paramiko/SSH transport is
1442 connected/authenticated, C{False} else.
1443 @rtype: C{bool}
1444
1445 """
1446 return self.control_session.is_connected()
1447
1449 """\
1450 Start a new X2Go session.
1451
1452 @return: C{True} if session startup has been successful and the X2Go proxy is up-and-running
1453 @rtype: C{bool}
1454
1455 @raise X2GoTerminalSessionException: if the session startup failed
1456 @raise X2GoDesktopSharingDenied: if desktop sharing fails because of denial by the user running the desktop to be shared
1457
1458 """
1459 self.params.rewrite_session_type()
1460
1461 if not self.has_command(_rewrite_cmd(self.params.cmd, params=self.params)):
1462 if self.client_instance:
1463 self.client_instance.HOOK_no_such_command(profile_name=self.profile_name, session_name=self.session_info.name, cmd=self.params.cmd)
1464 return False
1465
1466 setkbd = "0"
1467 if self.params.kbtype != "null/null":
1468 setkbd = "1"
1469
1470 if '/' in self.params.cmd:
1471 self.params.cmd = os.path.basename(self.params.cmd)
1472
1473 self.params.rewrite_session_type()
1474
1475 if self.params.geometry == 'maximize':
1476 _geometry = utils.get_workarea_geometry()
1477 if _geometry is None or len(_geometry) != 2:
1478 _geometry = utils.get_desktop_geometry()
1479 if _geometry and len(_geometry) == 2:
1480 self.params.geometry = "%sx%s" % _geometry
1481 else:
1482 self.logger('failed to detect best maximized geometry of your client-side desktop', loglevel=log.loglevel_WARN)
1483 self.params.geometry = "1024x768"
1484
1485 cmd_line = [ "x2gostartagent",
1486 str(self.params.geometry),
1487 str(self.params.link),
1488 str(self.params.pack),
1489 str(self.params.cache_type+'-depth_'+self.params.depth),
1490 str(self.params.kblayout),
1491 str(self.params.kbtype),
1492 str(setkbd),
1493 str(self.params.session_type),
1494 str(self.params.cmd),
1495 ]
1496 if self.params.session_type != 'S':
1497 cmd_line.append(
1498 str(self.params.clipboard),
1499 )
1500
1501 if self.params.cmd == 'XDMCP' and self.params.xdmcp_server:
1502 cmd_line = ['X2GOXDMCP=%s' % self.params.xdmcp_server] + cmd_line
1503
1504 if self.params.dpi:
1505 cmd_line = ['X2GODPI=%s' % self.params.dpi] + cmd_line
1506
1507 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line)
1508
1509 _stdout = stdout.read()
1510 _stderr = stderr.read()
1511
1512
1513
1514 if "ACCESS DENIED" in _stderr and "XSHAD" in _stderr:
1515 raise x2go_exceptions.X2GoDesktopSharingDenied('X2Go desktop sharing has been denied by the remote user')
1516
1517 try:
1518 self.session_info.initialize(_stdout,
1519 username=self.control_session.remote_username(),
1520 hostname=self.control_session.remote_peername(),
1521 )
1522 except ValueError:
1523 raise x2go_exceptions.X2GoTerminalSessionException("failed to start X2Go session")
1524 except IndexError:
1525 raise x2go_exceptions.X2GoTerminalSessionException("failed to start X2Go session")
1526
1527
1528 self.session_info.local_container = os.path.join(self.params.rootdir, 'S-%s' % self.session_info.name)
1529
1530 self.session_info.remote_container = '%s/.x2go/C-%s' % (self.control_session._x2go_remote_home,
1531 self.session_info.name,
1532 )
1533
1534
1535 self.proxy = self.proxy_backend(session_info=self.session_info,
1536 ssh_transport=self.control_session.get_transport(),
1537 sessions_rootdir=self.sessions_rootdir,
1538 session_instance=self.session_instance,
1539 proxy_options=self.proxy_options,
1540 logger=self.logger)
1541 self.proxy_subprocess, proxy_ok = self.proxy.start_proxy()
1542
1543 if proxy_ok:
1544 self.active_threads.append(self.proxy)
1545
1546 if self.params.session_type in ('D', 'S'):
1547 self.find_session_window()
1548 self.auto_session_window_title()
1549 self.raise_session_window()
1550
1551 if self.params.published_applications:
1552 self.control_session.get_published_applications()
1553
1554 else:
1555 raise x2go_exceptions.X2GoTerminalSessionException("failed to start X2Go session")
1556
1557 return proxy_ok
1558
1560 """\
1561 Resume a running/suspended X2Go session.
1562
1563 @return: C{True} if the session could successfully be resumed
1564 @rtype: C{bool}
1565
1566 @raise X2GoTerminalSessionException: if the terminal session failed to update server-side reported port changes
1567
1568 """
1569 setkbd = "0"
1570 if self.params.kbtype != "null/null":
1571 setkbd = "1"
1572
1573 if self.params.geometry == 'maximize':
1574 _geometry = utils.get_workarea_geometry()
1575 if _geometry is None or len(_geometry) != 2:
1576 _geometry = utils.get_desktop_geometry()
1577 if _geometry and len(_geometry) == 2:
1578 self.params.geometry = "%sx%s" % _geometry
1579 else:
1580 self.logger('failed to detect best maxmimized geometry of your client-side desktop, using 1024x768 instead', loglevel=log.loglevel_WARN)
1581 self.params.geometry = "1024x768"
1582
1583 cmd_line = [ "x2goresume-session", self.session_info.name,
1584 self.params.geometry,
1585 self.params.link,
1586 self.params.pack,
1587 self.params.kblayout,
1588 self.params.kbtype,
1589 setkbd,
1590 self.params.clipboard,
1591 ]
1592
1593 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line)
1594
1595
1596 for stdout_line in stdout.read():
1597 try:
1598 _new_value = stdout_line.split("=")[1].strip()
1599 if 'gr_port=' in stdout_line and _new_value != str(self.session_info.graphics_port):
1600 try:
1601 self.session_info.graphics_port = int(_new_value)
1602 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)
1603 except TypeError:
1604
1605 raise x2go_exceptions.X2GoTerminalSessionException('Failed to retrieve new graphics port from server. X2Go Session cannot be resumed.')
1606 elif 'sound_port=' in stdout_line and _new_value != str(self.session_info.snd_port):
1607 try:
1608 self.session_info.snd_port = int(_new_value)
1609 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)
1610 except TypeError:
1611 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)
1612 elif 'fs_port=' in stdout_line and _new_value != str(self.session_info.sshfs_port):
1613 try:
1614 self.session_info.sshfs_port = int(_new_value)
1615 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)
1616 except TypeError:
1617 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)
1618 except IndexError:
1619 continue
1620
1621
1622 self.session_info.local_container = os.path.join(self.params.rootdir, 'S-%s' % self.session_info.name)
1623
1624 self.session_info.remote_container = '%s/.x2go/C-%s' % (self.control_session._x2go_remote_home,
1625 self.session_info.name,
1626 )
1627 self.proxy = self.proxy_backend(session_info=self.session_info,
1628 ssh_transport=self.control_session.get_transport(),
1629 sessions_rootdir=self.sessions_rootdir,
1630 session_instance=self.session_instance,
1631 proxy_options=self.proxy_options,
1632 logger=self.logger
1633 )
1634 self.proxy_subprocess, proxy_ok = self.proxy.start_proxy()
1635
1636 if proxy_ok:
1637 self.params.depth = self.session_info.name.split('_')[2][2:]
1638
1639
1640 self.session_info.username = self.control_session.remote_username()
1641
1642 if self.params.kbtype not in ('null/null', 'auto') and (self.params.kblayout not in ('null', '') or self.params.kbvariant not in ('null', '')):
1643 self.set_keyboard(layout=self.params.kblayout, variant=self.params.kbvariant)
1644
1645 if self.params.session_type in ('D', 'S'):
1646 self.find_session_window()
1647 self.auto_session_window_title()
1648 self.raise_session_window()
1649
1650 if self.is_published_applications_provider():
1651 self.control_session.get_published_applications()
1652 self.published_applications = True
1653 else:
1654 raise x2go_exceptions.X2GoTerminalSessionException("failed to start X2Go session")
1655
1656 return proxy_ok
1657
1659 """\
1660 Suspend this X2Go (terminal) session.
1661
1662 @return: C{True} if the session terminal could be successfully suspended
1663 @rtype: C{bool}
1664
1665 """
1666 self.release_telekinesis()
1667 self.control_session.suspend(session_name=self.session_info.name)
1668 self.release_proxy()
1669
1670
1671 _ret = True
1672
1673 return _ret
1674
1693
1695 """\
1696 Let the X2Go proxy command cleanly die away... (by calling its destructor).
1697
1698 """
1699 if self.proxy is not None:
1700 self.proxy.__del__()
1701 self.proxy = None
1702
1704 """\
1705 Let the attached Telekinesis client cleanly die away... (by calling its destructor).
1706
1707 """
1708 if self.telekinesis_client is not None:
1709 self.telekinesis_client.__del__()
1710 self.telekinesis_client = None
1711
1713 """\
1714 Do some cleanup after this session has terminated.
1715
1716 """
1717
1718
1719
1720 if not self._cleaned_up and self.session_info.name:
1721
1722
1723 self.logger('cleaning up session %s after termination' % self.session_info, loglevel=log.loglevel_NOTICE)
1724
1725
1726 if self.logger.get_loglevel() & log.loglevel_DEBUG != log.loglevel_DEBUG:
1727
1728 self._rm_session_dirtree()
1729 self._rm_desktop_dirtree()
1730
1731 self._cleaned_up = True
1732
1734 """\
1735 Test if this terminal session is a rootless session.
1736
1737 @return: C{True} if this session is of session type rootless ('R').
1738 @rtype: C{bool}
1739
1740 """
1741 self.params.rewrite_session_type()
1742 return self.params.session_type == 'R'
1743
1745 """\
1746 Test if this terminal session is a desktop sharing (aka shadow) session.
1747
1748 @return: C{True} if this session is of session type shadow ('S').
1749 @rtype: C{bool}
1750
1751 """
1752 self.params.rewrite_session_type()
1753 return self.params.session_type == 'S'
1754
1756 """\
1757 Test if this terminal session is a published applications session.
1758
1759 @return: C{True} if this session is of session type published applications ('P').
1760 @rtype: C{bool}
1761
1762 """
1763 self.params.rewrite_session_type()
1764 return self.params.session_type == 'P'
1765