Package x2go :: Package backends :: Package terminal :: Module _stdout
[frames] | no frames]

Source Code for Module x2go.backends.terminal._stdout

   1  # -*- coding: utf-8 -*- 
   2   
   3  # Copyright (C) 2010-2012 by Mike Gabriel <mike.gabriel@das-netzwerkteam.de> 
   4  # 
   5  # Python X2Go is free software; you can redistribute it and/or modify 
   6  # it under the terms of the GNU Affero General Public License as published by 
   7  # the Free Software Foundation; either version 3 of the License, or 
   8  # (at your option) any later version. 
   9  # 
  10  # Python X2Go is distributed in the hope that it will be useful, 
  11  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
  12  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
  13  # GNU Affero General Public License for more details. 
  14  # 
  15  # You should have received a copy of the GNU Affero General Public License 
  16  # along with this program; if not, write to the 
  17  # Free Software Foundation, Inc., 
  18  # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. 
  19   
  20  """\ 
  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 STDOUT and use NX3 as graphical proxy. 
  25   
  26  """ 
  27  __NAME__ = 'x2goterminalsession-pylib' 
  28   
  29  # modules 
  30  import os 
  31  import types 
  32  import gevent 
  33  import cStringIO 
  34  import copy 
  35  import shutil 
  36  import threading 
  37   
  38  # Python X2Go modules 
  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.log as log 
  44  import x2go.defaults as defaults 
  45  import x2go.utils as utils 
  46  import x2go.x2go_exceptions as x2go_exceptions 
  47   
  48  # we hide the default values from epydoc (that's why we transform them to _UNDERSCORE variables) 
  49  from x2go.defaults import X2GOCLIENT_OS as _X2GOCLIENT_OS 
  50  from x2go.defaults import LOCAL_HOME as _LOCAL_HOME 
  51  from x2go.defaults import CURRENT_LOCAL_USER as _CURRENT_LOCAL_USER 
  52  from x2go.defaults import X2GO_CLIENT_ROOTDIR as _X2GO_CLIENT_ROOTDIR 
  53  from x2go.defaults import X2GO_SESSIONS_ROOTDIR as _X2GO_SESSIONS_ROOTDIR 
  54  from x2go.defaults import X2GO_GENERIC_APPLICATIONS as _X2GO_GENERIC_APPLICATIONS 
  55  from x2go.defaults import X2GO_DESKTOPSESSIONS as _X2GO_DESKTOPSESSIONS 
  56   
  57  from x2go.backends.info import X2goServerSessionInfo as _X2goServerSessionInfo 
  58  from x2go.backends.info import X2goServerSessionList as _X2goServerSessionList 
  59  from x2go.backends.proxy import X2goProxy as _X2goProxy 
  60  from x2go.backends.printing import X2goClientPrinting as _X2goClientPrinting 
  61   
  62  _local_color_depth = utils.local_color_depth() 
  63   
64 -def _rewrite_cmd(cmd, params=None):
65 """\ 66 Mechansim that rewrites X2Go server commands into something that gets understood by 67 the server-side script C{x2goruncommand}. 68 69 @param cmd: the current command for execution (as found in the session profile parameter C{cmd}) 70 @type cmd: C{str} 71 @param params: an session paramter object 72 @type params: L{X2goSessionParams} 73 74 @return: the rewritten command for server-side execution 75 @rtype: C{str} 76 77 """ 78 # start with an empty string 79 cmd = cmd or '' 80 81 # find window manager commands 82 if cmd in _X2GO_DESKTOPSESSIONS.keys(): 83 cmd = _X2GO_DESKTOPSESSIONS[cmd] 84 85 if (cmd == 'RDP') and (type(params) == X2goSessionParams): 86 if params.geometry == 'fullscreen': 87 cmd = 'rdesktop -f -N %s %s -a %s' % (params.rdp_options, params.rdp_server, params.depth) 88 else: 89 cmd = 'rdesktop -g %s -N %s %s -a %s' % (params.geometry, params.rdp_options, params.rdp_server, params.depth) 90 91 # place quot marks around cmd if not empty string 92 if cmd: 93 cmd = '"%s"' % cmd 94 95 if ((type(params) == X2goSessionParams) and params.published_applications and cmd == ''): 96 cmd = 'PUBLISHED' 97 98 return cmd
99 100
101 -def _rewrite_blanks(cmd):
102 """\ 103 In command strings X2Go server scripts expect blanks being rewritten to ,,X2GO_SPACE_CHAR''. 104 105 @param cmd: command that has to be rewritten for passing to the server 106 @type cmd: C{str} 107 108 @return: the command with blanks rewritten to ,,X2GO_SPACE_CHAR'' 109 @rtype: C{str} 110 111 """ 112 # X2Go run command replace X2GO_SPACE_CHAR string with blanks 113 if cmd: 114 cmd = cmd.replace(" ", "X2GO_SPACE_CHAR") 115 return cmd
116 117
118 -class X2goSessionParams(object):
119 """\ 120 The L{X2goSessionParams} class is used to store all parameters that 121 C{X2goTerminalSession} backend objects are constructed with. 122 123 """
124 - def rewrite_session_type(self):
125 """\ 126 Rewrite the X2Go session type, so that the X2Go server 127 can understand it (C{desktop} -> C{D}, etc.). 128 129 Also if the object's C{command} property is a known window 130 manager, the session type will be set to 'D' 131 (i.e. desktop). 132 133 @return: 'D' if session should probably a desktop session, 134 'R' for rootless sessions, 'P' for sessions providing published 135 applications, and 'S' for desktop sharing sessions 136 @rtype: C{str} 137 138 """ 139 cmd = self.cmd 140 published = self.published_applications 141 142 if published and self.cmd in ('', 'PUBLISHED'): 143 self.session_type = 'P' 144 self.cmd = 'PUBLISHED' 145 else: 146 if cmd == 'RDP': 147 self.session_type = 'R' 148 elif cmd.startswith('rdesktop'): 149 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
184 -class X2goTerminalSessionSTDOUT(object):
185 """\ 186 Class for managing X2Go terminal sessions on a remote X2Go server via Paramiko/SSH. 187 188 With the L{X2goTerminalSessionSTDOUT} 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{X2goTerminalSessionSTDOUT} 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{X2goControlSessionSTDOUT}). 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 session_type="application", snd_system='pulse', snd_port=4713, cmd=None, 210 published_applications=False, 211 set_session_title=False, session_title="", applications=[], 212 rdp_server=None, rdp_options=None, 213 xdmcp_server=None, 214 convert_encoding=False, server_encoding='UTF-8', client_encoding='UTF-8', 215 rootdir=None, 216 profile_name='UNKNOWN', profile_id=utils._genSessionProfileId(), 217 print_action=None, print_action_args={}, 218 info_backend=_X2goServerSessionInfo, 219 list_backend=_X2goServerSessionList, 220 proxy_backend=_X2goProxy, proxy_options={}, 221 printing_backend=_X2goClientPrinting, 222 client_rootdir=os.path.join(_LOCAL_HOME, _X2GO_CLIENT_ROOTDIR), 223 sessions_rootdir=os.path.join(_LOCAL_HOME, _X2GO_SESSIONS_ROOTDIR), 224 session_instance=None, 225 logger=None, loglevel=log.loglevel_DEFAULT):
226 """\ 227 Initialize an X2Go session. With the L{X2goTerminalSessionSTDOUT} class you can start 228 new X2Go sessions, resume suspended sessions or suspend resp. terminate 229 currently running sessions on a connected X2Go server. 230 231 @param geometry: screen geometry of the X2Go session. Can be either C{<width>x<height>} 232 or C{fullscreen} 233 @type geometry: C{str} 234 @param depth: color depth in bits (common values: C{16}, C{24}) 235 @type depth: C{int} 236 @param link: network link quality (either one of C{modem}, C{isdn}, C{adsl}, C{wan} or C{lan}) 237 @type link: C{str} 238 @param pack: compression method for NX based session proxying 239 @type pack: C{str} 240 @param dpi: dots-per-inch value for the session screen (has an impact on the font size on screen) 241 @type dpi: C{str} 242 @param cache_type: a dummy parameter that is passed to the L{X2goProxyBASE}. In NX Proxy 243 (class C{X2goProxyNX3}) this originally is the session name. With X2Go it 244 defines the name of the NX cache directory. Best is to leave it untouched. 245 @type cache_type: C{str} 246 @param kbtype: keyboard type, e.g. C{pc105/us} (default), C{pc105/de}, ... 247 @type kbtype: C{str} 248 @param kblayout: keyboard layout, e.g. C{us} (default), C{de}, C{fr}, ... 249 @type kblayout: C{str} 250 @param kbvariant: keyboard variant, e.g. C{nodeadkeys} (for C{de} layout), C{intl} (for C{us} layout), etc. 251 @type kbvariant: C{str} 252 @param session_type: either C{desktop}, C{application} (rootless session) or C{shared} 253 @type session_type: C{str} 254 @param snd_system: sound system to be used on server (C{none}, C{pulse} (default), 255 C{arts} (obsolete) or C{esd}) 256 @type snd_system: C{str} 257 @param snd_port: local sound port for network capable audio system 258 @type snd_port: C{int} 259 @param cmd: command to be run on X2Go server after session start (only used 260 when L{X2goTerminalSessionSTDOUT.start()} is called, ignored on resume, suspend etc. 261 @type cmd: C{str} 262 @param published_applications: session is published applications provider 263 @type published_applications: C{bool} 264 @param set_session_title: modify the session title (i.e. the Window title) of desktop or shared desktop sessions 265 @type set_session_title: C{bool} 266 @param session_title: session title for this (desktop or shared desktop) session 267 @type session_title: C{str} 268 @param applications: applications available for rootless application execution 269 @type applications: C{list} 270 @param rdp_server: host name of server-side RDP server 271 @type rdp_server: C{str} 272 @param rdp_options: options for the C{rdesktop} command executed on the X2Go server (RDP proxy mode of X2Go) 273 @type rdp_options: C{str} 274 @param xdmcp_server: XDMCP server to connect to 275 @type xdmcp_server: C{str} 276 @param convert_encoding: convert file system encodings between server and client (for client-side shared folders) 277 @type convert_encoding: C{bool} 278 @param server_encoding: server-side file system / session encoding 279 @type server_encoding: C{str} 280 @param client_encoding: client-side file system encoding (if client-side is MS Windows, this parameter gets overwritten to WINDOWS-1252) 281 @type client_encoding: C{str} 282 @param rootdir: X2Go session directory, normally C{~/.x2go} 283 @type rootdir: C{str} 284 @param profile_name: the session profile name for this terminal session 285 @type profile_name: C{str} 286 @param profile_id: the session profile ID for this terminal session 287 @type profile_id: C{str} 288 @param print_action: either a print action short name (PDFVIEW, PDFSAVE, PRINT, PRINTCMD) or the 289 resp. C{X2goPrintActionXXX} class (where XXX equals one of the given short names) 290 @type print_action: C{str} or C{class} 291 @param print_action_args: optional arguments for a given print_action (for further info refer to 292 L{X2goPrintActionPDFVIEW}, L{X2goPrintActionPDFSAVE}, L{X2goPrintActionPRINT} and L{X2goPrintActionPRINTCMD}) 293 @type print_action_args: dict 294 @param info_backend: backend for handling storage of server session information 295 @type info_backend: C{X2goServerSessionInfo*} instance 296 @param list_backend: backend for handling storage of session list information 297 @type list_backend: C{X2goServerSessionList*} instance 298 @param proxy_backend: backend for handling the X-proxy connections 299 @type proxy_backend: C{X2goProxy*} instance 300 @param proxy_options: a set of very C{X2goProxy*} backend specific options; any option that is not known 301 to the C{X2goProxy*} backend will simply be ignored 302 @type proxy_options: C{dict} 303 @param client_rootdir: client base dir (default: ~/.x2goclient) 304 @type client_rootdir: C{str} 305 @param sessions_rootdir: sessions base dir (default: ~/.x2go) 306 @type sessions_rootdir: C{str} 307 @param session_instance: the L{X2goSession} instance that is parent to this terminal session 308 @type session_instance: C{obj} 309 @param logger: you can pass an L{X2goLogger} object to the 310 L{X2goTerminalSessionSTDOUT} constructor 311 @type logger: L{X2goLogger} instance 312 @param loglevel: if no L{X2goLogger} object has been supplied a new one will be 313 constructed with the given loglevel 314 @type loglevel: C{int} 315 316 """ 317 self.proxy = None 318 self.proxy_subprocess = None 319 self.proxy_options = proxy_options 320 321 self.active_threads = [] 322 self.reverse_tunnels = {} 323 324 self.print_queue = None 325 self.mimebox_queue = None 326 327 if logger is None: 328 self.logger = log.X2goLogger(loglevel=loglevel) 329 else: 330 self.logger = copy.deepcopy(logger) 331 self.logger.tag = __NAME__ 332 333 self.control_session = control_session 334 self.reverse_tunnels = self.control_session.get_transport().reverse_tunnels 335 336 self.client_rootdir = client_rootdir 337 self.sessions_rootdir = sessions_rootdir 338 339 self.params = X2goSessionParams() 340 341 self.params.geometry = str(geometry) 342 self.params.link = str(link) 343 self.params.pack = str(pack) 344 self.params.dpi = str(dpi) 345 self.params.cache_type = str(cache_type) 346 self.params.session_type = str(session_type) 347 self.params.kbtype = str(kbtype) 348 self.params.kblayout = str(kblayout) 349 self.params.kbvariant = str(kbvariant) 350 self.params.snd_system = str(snd_system) 351 self.params.cmd = str(cmd) 352 self.params.depth = str(depth) 353 354 self.params.published_applications = published_applications 355 self.published_applications = published_applications 356 357 self.params.rdp_server = str(rdp_server) 358 self.params.rdp_options = str(rdp_options) 359 self.params.xdmcp_server = str(xdmcp_server) 360 361 self.params.convert_encoding = convert_encoding 362 self.params.client_encoding = str(client_encoding) 363 self.params.server_encoding = str(server_encoding) 364 365 self.params.rootdir = (type(rootdir) is types.StringType) and rootdir or self.sessions_rootdir 366 self.params.update() 367 368 self.profile_name = profile_name 369 self.set_session_title = set_session_title 370 self.session_title = session_title 371 self.session_window = None 372 self.proxy_backend = proxy_backend 373 374 self.snd_port = snd_port 375 self.print_action = print_action 376 self.print_action_args = print_action_args 377 self.printing_backend = printing_backend 378 self.session_instance = session_instance 379 if self.session_instance: 380 self.client_instance = self.session_instance.client_instance 381 else: 382 self.client_instance = None 383 384 self._share_local_folder_busy = False 385 self._mk_sessions_rootdir(self.params.rootdir) 386 387 self.session_info = session_info 388 if self.session_info is not None: 389 if self.session_info.name: 390 self.session_info.local_container = os.path.join(self.params.rootdir, 'S-%s' % self.session_info.name) 391 else: 392 raise x2go_exceptions.X2goTerminalSessionException('no valid session info availble') 393 else: 394 self.session_info = info_backend() 395 396 self._share_local_folder_lock = threading.Lock() 397 self._cleaned_up = False
398
399 - def __del__(self):
400 """\ 401 Tidy up if terminal session gets destructed. 402 403 """ 404 self._x2go_tidy_up()
405
406 - def _x2go_tidy_up(self):
407 """\ 408 Tidy up this terminal session... 409 - shutdown all forwarding and reverse forwarding tunnels 410 - shutdown the print queue (if running) 411 - shutdown the MIME box queue (if running) 412 - clear the session info 413 414 """ 415 self.release_proxy() 416 417 try: 418 419 if self.control_session.get_transport() is not None: 420 try: 421 for _tunnel in [ _tun[1] for _tun in self.reverse_tunnels[self.session_info.name].values() ]: 422 if _tunnel is not None: 423 _tunnel.__del__() 424 except KeyError: 425 pass 426 427 if self.print_queue is not None: 428 self.print_queue.__del__() 429 430 if self.mimebox_queue is not None: 431 self.mimebox_queue.__del__() 432 433 except AttributeError: 434 pass 435 436 self.session_info.clear()
437
438 - def _mk_sessions_rootdir(self, rootdir):
439 """\ 440 Create the server-side session root dir (normally ~/.x2go). 441 442 @param rootdir: server-side session root directory 443 @type rootdir: C{str} 444 445 """ 446 try: 447 os.makedirs(rootdir) 448 except OSError, e: 449 if e.errno == 17: 450 # file exists 451 pass 452 else: 453 raise OSError, e
454
455 - def _rm_session_dirtree(self):
456 """\ 457 Purge client-side session dir (session cache directory). 458 459 """ 460 if self.session_info.name: 461 shutil.rmtree('%s/S-%s' % (self.params.rootdir, self.session_info), ignore_errors=True)
462
463 - def _rm_desktop_dirtree(self):
464 """\ 465 Purge client-side session dir (C-<display> directory) 466 467 """ 468 if self.session_info.display: 469 shutil.rmtree('%s/S-%s' % (self.params.rootdir, self.session_info.display), ignore_errors=True)
470
471 - def get_session_name(self):
472 """\ 473 Retrieve the X2Go session's name from the session info object. 474 475 @return: the session name 476 @rtype: C{str} 477 478 """ 479 return self.session_info.name
480
481 - def get_session_info(self):
482 """\ 483 Retrieve the X2Go session's session info object. 484 485 @return: the session info object 486 @rtype: C{X2goServerSessionInfo*} 487 488 """ 489 return self.session_info
490
491 - def get_session_cmd(self):
492 """\ 493 Retrieve the X2Go session's command as stored in the session parameter object. 494 495 @return: the session command 496 @rtype: C{str} 497 498 """ 499 return self.params.cmd
500
501 - def get_session_type(self):
502 """\ 503 Retrieve the X2Go session's session type as stored in the session parameter object. 504 505 @return: the session type 506 @rtype: C{str} 507 508 """ 509 return self.params.session_type
510
511 - def start_sound(self):
512 """\ 513 Initialize Paramiko/SSH reverse forwarding tunnel for X2Go sound. 514 515 Currently supported audio protocols: 516 517 - PulseAudio 518 - Esound (not tested very much) 519 520 @raise X2goControlSessionException: if the control session of this terminal session is not connected 521 522 """ 523 _tunnel = None 524 if self.reverse_tunnels[self.session_info.name]['snd'][1] is None: 525 if self.params.snd_system == 'pulse': 526 self.logger('initializing PulseAudio sound support in X2Go session', loglevel=log.loglevel_INFO) 527 ### 528 ### PULSEAUDIO 529 ### 530 if os.path.exists(os.path.normpath('%s/.pulse-cookie' % _LOCAL_HOME)): 531 # setup pulse client config file on X2Go server 532 cmd_line = "echo 'default-server=127.0.0.1:%s'>%s/.pulse-client.conf;" % (self.session_info.snd_port, self.session_info.remote_container) + \ 533 "echo 'cookie-file=%s/.pulse-cookie'>>%s/.pulse-client.conf" % (self.session_info.remote_container, self.session_info.remote_container) 534 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) 535 536 self.control_session._x2go_sftp_put(local_path='%s/.pulse-cookie' % _LOCAL_HOME, remote_path='%s/.pulse-cookie' % self.session_info.remote_container) 537 538 # start reverse SSH tunnel for pulse stream 539 _tunnel = rforward.X2goRevFwTunnel(server_port=self.session_info.snd_port, 540 remote_host='127.0.0.1', 541 remote_port=self.snd_port, 542 ssh_transport=self.control_session.get_transport(), 543 session_instance=self.session_instance, 544 logger=self.logger 545 ) 546 else: 547 if self.client_instance: 548 self.client_instance.HOOK_on_sound_tunnel_failed(profile_name=self.profile_name, session_name=self.session_info.name) 549 elif self.params.snd_system == 'arts': 550 ### 551 ### ARTSD AUDIO 552 ### 553 self.logger('the ArtsD sound server (as in KDE3) is obsolete and will not be supported by Python X2Go...', loglevel=log.loglevel_WARNING) 554 555 elif self.params.snd_system == 'esd': 556 ### 557 ### ESD AUDIO 558 ### 559 560 self.logger('initializing ESD sound support in X2Go session', loglevel=log.loglevel_INFO) 561 self.control_session._x2go_sftp_put(local_path='%s/.esd_auth' % _LOCAL_HOME, remote_path='%s/.esd_auth' % self.control_session._x2go_remote_home) 562 563 # start reverse SSH tunnel for pulse stream 564 _tunnel = rforward.X2goRevFwTunnel(server_port=self.session_info.snd_port, 565 remote_host='127.0.0.1', 566 remote_port=self.snd_port, 567 ssh_transport=self.control_session.get_transport(), 568 session_instance=self.session_instance, 569 logger=self.logger 570 ) 571 572 573 if _tunnel is not None: 574 self.reverse_tunnels[self.session_info.name]['snd'] = (self.session_info.snd_port, _tunnel) 575 _tunnel.start() 576 self.active_threads.append(_tunnel) 577 578 else: 579 # tunnel has already been started and might simply need a resume call 580 self.reverse_tunnels[self.session_info.name]['snd'][1].resume()
581
582 - def start_sshfs(self):
583 """\ 584 Initialize Paramiko/SSH reverse forwarding tunnel for X2Go folder sharing. 585 586 """ 587 if not self.control_session.is_sshfs_available(): 588 raise x2go_exceptions.X2goUserException('Remote user %s is not allowed to share SSHFS resources with the server.' % self.session_info.username) 589 590 # start reverse SSH tunnel for sshfs (folder sharing, printing) 591 ssh_transport = self.control_session.get_transport() 592 if self.reverse_tunnels[self.session_info.name]['sshfs'][1] is None: 593 594 _tunnel = sftpserver.X2goRevFwTunnelToSFTP(server_port=self.session_info.sshfs_port, 595 ssh_transport=ssh_transport, 596 auth_key=self.control_session._x2go_session_auth_rsakey, 597 session_instance=self.session_instance, 598 logger=self.logger 599 ) 600 601 if _tunnel is not None: 602 self.reverse_tunnels[self.session_info.name]['sshfs'] = (self.session_info.sshfs_port, _tunnel) 603 _tunnel.start() 604 self.active_threads.append(_tunnel) 605 while not _tunnel.ready: 606 gevent.sleep(.1) 607 608 else: 609 # tunnel has already been started and might simply need a resume call 610 self.reverse_tunnels[self.session_info.name]['sshfs'][1].resume()
611
612 - def _x2go_pause_rev_fw_tunnel(self, name):
613 """\ 614 Pause reverse SSH tunnel of name <name>. 615 616 @param name: tunnel name (either of C{sshfs}, C{snd}) 617 @type name: C{str} 618 619 """ 620 _tunnel = self.reverse_tunnels[self.session_info.name][name][1] 621 if _tunnel is not None: 622 _tunnel.pause()
623
624 - def stop_sound(self):
625 """\ 626 Shutdown (pause) Paramiko/SSH reverse forwarding tunnel for X2Go sound. 627 628 """ 629 self._x2go_pause_rev_fw_tunnel('snd')
630
631 - def stop_sshfs(self):
632 """\ 633 Shutdown (pause) Paramiko/SSH reverse forwarding tunnel for X2Go folder sharing. 634 635 """ 636 self._x2go_pause_rev_fw_tunnel('sshfs')
637
638 - def start_printing(self):
639 """\ 640 Initialize X2Go print spooling. 641 642 @raise X2goUserException: if the X2Go printing feature is not available to this user 643 644 """ 645 if not self.control_session.is_sshfs_available(): 646 raise x2go_exceptions.X2goUserException('Remote user %s is not allowed to use client-side printing.' % self.session_info.username) 647 648 spool_dir = os.path.join(self.session_info.local_container, 'spool') 649 if not os.path.exists(spool_dir): 650 os.makedirs(spool_dir) 651 self.share_local_folder(local_path=spool_dir, folder_type='spool') 652 self.print_queue = printqueue.X2goPrintQueue(profile_name=self.profile_name, 653 session_name=self.session_info.name, 654 spool_dir=spool_dir, 655 print_action=self.print_action, 656 print_action_args=self.print_action_args, 657 client_instance=self.client_instance, 658 printing_backend=self.printing_backend, 659 logger=self.logger, 660 ) 661 self.print_queue.start() 662 self.active_threads.append(self.print_queue)
663
664 - def set_print_action(self, print_action, **kwargs):
665 """\ 666 Set a print action for the next incoming print jobs. 667 668 This method is a wrapper for L{X2goPrintQueue}C{.set_print_action()}. 669 670 @param print_action: print action name or object (i.e. an instance of C{X2goPrintAction*} classes) 671 @type print_action: C{str} or C{X2goPrintAction*} 672 @param kwargs: print action specific parameters 673 @type kwargs: dict 674 675 """ 676 self.print_queue.set_print_action(print_action, logger=self.logger, **kwargs)
677
678 - def stop_printing(self):
679 """\ 680 Shutdown (pause) the X2Go Print Queue thread. 681 682 """ 683 if self.print_queue is not None: 684 self.print_queue.pause()
685
686 - def get_printing_spooldir(self):
687 """\ 688 Return the server-side printing spooldir path. 689 690 @return: the directory for remote print job spooling 691 @rtype: C{str} 692 693 """ 694 return '%s/%s' % (self.session_info.remote_container, 'spool')
695
696 - def start_mimebox(self, mimebox_extensions=[], mimebox_action=None):
697 """\ 698 Initialize the X2Go MIME box. Open/process incoming files from the server-side locally. 699 700 @param mimebox_extensions: file name extensions that are allowed for local opening/processing 701 @type mimebox_extensions: C{list} 702 @param mimebox_action: MIME box action given as name or object (i.e. an instance of C{X2goMIMEboxAction*} classes). 703 @type mimebox_action: C{str} or C{obj} 704 705 @raise X2goUserException: if the X2Go MIME box feature is not available to this user 706 707 """ 708 if not self.control_session.is_sshfs_available(): 709 raise x2go_exceptions.X2goUserException('Remote user %s is not allowed to use the MIME box.' % self.session_info.username) 710 711 mimebox_dir = os.path.join(self.session_info.local_container, 'mimebox') 712 if not os.path.exists(mimebox_dir): 713 os.makedirs(mimebox_dir) 714 self.share_local_folder(local_path=mimebox_dir, folder_type='mimebox') 715 self.mimebox_queue = mimebox.X2goMIMEboxQueue(profile_name=self.profile_name, 716 session_name=self.session_info.name, 717 mimebox_dir=mimebox_dir, 718 mimebox_extensions=mimebox_extensions, 719 mimebox_action=mimebox_action, 720 client_instance=self.client_instance, 721 logger=self.logger, 722 ) 723 self.mimebox_queue.start() 724 self.active_threads.append(self.mimebox_queue)
725
726 - def set_mimebox_action(self, mimebox_action, **kwargs):
727 """\ 728 Set a MIME box action for the next incoming MIME jobs. 729 730 This method is a wrapper for L{X2goMIMEboxQueue}C{set_mimebox_action()}. 731 732 @param mimebox_action: MIME box action name or object (i.e. an instance of C{X2goMIMEboxAction*} classes) 733 @type mimebox_action: C{str} or C{X2goMIMEboxAction*} 734 @param kwargs: MIME box action specific parameters 735 @type kwargs: dict 736 737 """ 738 self.mimebox_queue.set_mimebox_action(mimebox_action, logger=self.logger, **kwargs)
739
740 - def stop_mimebox(self):
741 """\ 742 Shutdown (pause) the X2Go MIME box Queue thread. 743 744 """ 745 if self.mimebox_queue is not None: 746 self.mimebox_queue.pause()
747
748 - def get_mimebox_spooldir(self):
749 """\ 750 Return the server-side MIME box spooldir path. 751 752 @return: the directory where remote MIME box jobs are placed 753 @rtype: C{str} 754 755 """ 756 return '%s/%s' % (self.session_info.remote_container, 'mimebox')
757
758 - def is_session_info_protected(self):
759 """\ 760 Test if this terminal's session info object is write-protected. 761 762 @return: C{True}, if session info object is read-only, C{False} for read-write. 763 @rtype: C{bool} 764 765 """ 766 self.session_info.is_protected()
767
768 - def session_info_protect(self):
769 """\ 770 Protect this terminal session's info object against updates. 771 772 """ 773 self.session_info.protect()
774
775 - def session_info_unprotect(self):
776 """\ 777 Allow session info updates from within the list_sessions method of the control session. 778 779 """ 780 self.session_info.unprotect()
781
782 - def share_local_folder(self, local_path=None, folder_type='disk'):
783 """\ 784 Share a local folder with the X2Go session. 785 786 @param local_path: the full path to an existing folder on the local 787 file system 788 @type local_path: C{str} 789 @param folder_type: one of 'disk' (a folder on your local hard drive), 'rm' (removeable device), 790 'cdrom' (CD/DVD Rom) or 'spool' (for X2Go print spooling) 791 @type folder_type: C{str} 792 793 @return: returns C{True} if the local folder has been successfully mounted within the X2Go server session 794 @rtype: C{bool} 795 796 @raise X2goUserException: if local folder sharing is not available to this user 797 @raise Exception: any other exception occuring on the way is passed through by this method 798 799 """ 800 if not self.control_session.is_sshfs_available(): 801 raise x2go_exceptions.X2goUserException('Remote user %s is not allowed to share local folders with the server.' % self.session_info.username) 802 803 if local_path is None: 804 self.logger('no folder name given...', log.loglevel_WARN) 805 return False 806 807 if type(local_path) not in (types.StringType, types.UnicodeType): 808 self.logger('folder name needs to be of type StringType...', log.loglevel_WARN) 809 return False 810 811 if not os.path.exists(local_path): 812 self.logger('local folder does not exist: %s' % local_path, log.loglevel_WARN) 813 return False 814 815 local_path = os.path.normpath(local_path) 816 self.logger('sharing local folder: %s' % local_path, log.loglevel_INFO) 817 818 _auth_rsakey = self.control_session._x2go_session_auth_rsakey 819 _host_rsakey = defaults.RSAHostKey 820 821 _tmp_io_object = cStringIO.StringIO() 822 _auth_rsakey.write_private_key(_tmp_io_object) 823 _tmp_io_object.write('----BEGIN RSA IDENTITY----') 824 _tmp_io_object.write('%s %s' % (_host_rsakey.get_name(),_host_rsakey.get_base64(),)) 825 826 # _x2go_key_fname must be a UniX path 827 _x2go_key_fname = '%s/%s/%s' % (os.path.dirname(self.session_info.remote_container), 'ssh', 'key.z%s' % self.session_info.agent_pid) 828 _x2go_key_bundle = _tmp_io_object.getvalue() 829 830 # if there is another call to this method currently being processed, wait for that one to finish 831 self._share_local_folder_lock.acquire() 832 833 try: 834 self.control_session._x2go_sftp_write(_x2go_key_fname, _x2go_key_bundle) 835 836 _convert_encoding = self.params.convert_encoding 837 _client_encoding = self.params.client_encoding 838 _server_encoding = self.params.server_encoding 839 840 if _X2GOCLIENT_OS == 'Windows': 841 local_path = local_path.replace('\\', '/') 842 local_path = local_path.replace(':', '') 843 local_path = '/windrive/%s' % local_path 844 _convert_encoding = True 845 _client_encoding = 'WINDOWS-1252' 846 847 if _convert_encoding: 848 export_iconv_settings = 'export X2GO_ICONV=modules=iconv,from_code=%s,to_code=%s &&' % (_client_encoding, _server_encoding) 849 else: 850 export_iconv_settings = '' 851 852 if folder_type == 'disk': 853 854 cmd_line = [ '%s export HOSTNAME &&' % export_iconv_settings, 855 'x2gomountdirs', 856 'dir', 857 str(self.session_info.name), 858 '"%s"' % _CURRENT_LOCAL_USER, 859 _x2go_key_fname, 860 '%s__REVERSESSH_PORT__%s; ' % (local_path, self.session_info.sshfs_port), 861 'rm -f %s %s.ident' % (_x2go_key_fname, _x2go_key_fname), 862 ] 863 864 elif folder_type == 'spool': 865 866 cmd_line = [ '%s export HOSTNAME &&' % export_iconv_settings, 867 'x2gomountdirs', 868 'dir', 869 str(self.session_info.name), 870 '"%s"' % _CURRENT_LOCAL_USER, 871 _x2go_key_fname, 872 '%s__PRINT_SPOOL___REVERSESSH_PORT__%s; ' % (local_path, self.session_info.sshfs_port), 873 'rm -f %s %s.ident' % (_x2go_key_fname, _x2go_key_fname), 874 ] 875 876 elif folder_type == 'mimebox': 877 878 cmd_line = [ '%s export HOSTNAME &&' % export_iconv_settings, 879 'x2gomountdirs', 880 'dir', 881 str(self.session_info.name), 882 '"%s"' % _CURRENT_LOCAL_USER, 883 _x2go_key_fname, 884 '%s__MIMEBOX_SPOOL___REVERSESSH_PORT__%s; ' % (local_path, self.session_info.sshfs_port), 885 'rm -f %s %s.ident' % (_x2go_key_fname, _x2go_key_fname), 886 ] 887 888 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) 889 _stdout = stdout.read().split('\n') 890 self.logger('x2gomountdirs output is: %s' % _stdout, log.loglevel_NOTICE) 891 892 except: 893 self._share_local_folder_lock.release() 894 raise 895 self._share_local_folder_lock.release() 896 897 if len(_stdout) >= 6 and _stdout[5].endswith('ok'): 898 return True 899 return False
900
901 - def unshare_all_local_folders(self):
902 """\ 903 Unshare all local folders mount in the X2Go session. 904 905 @return: returns C{True} if all local folders could be successfully unmounted from the X2Go server session 906 @rtype: C{bool} 907 908 """ 909 self.logger('unsharing all local folders from session %s' % self.session_info, log.loglevel_INFO) 910 911 cmd_line = [ 'export HOSTNAME &&', 912 'x2goumount-session', 913 self.session_info.name, 914 ] 915 916 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) 917 if not stderr.read(): 918 self.logger('x2goumount-session (all mounts) for session %s has been successful' % self.session_info, log.loglevel_NOTICE) 919 return True 920 else: 921 self.logger('x2goumount-session (all mounts) for session %s failed' % self.session_info, log.loglevel_ERROR) 922 return False
923
924 - def unshare_local_folder(self, local_path):
925 """\ 926 Unshare local folder given as <local_path> from X2Go session. 927 928 @return: returns C{True} if the local folder <local_path> could be successfully unmounted from the X2Go server session 929 @rtype: C{bool} 930 931 """ 932 self.logger('unsharing local folder from session %s' % self.session_info, log.loglevel_INFO) 933 934 cmd_line = [ 'export HOSTNAME &&', 935 'x2goumount-session', 936 self.session_info.name, 937 "'%s'" % local_path, 938 ] 939 940 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) 941 if not stderr.read(): 942 self.logger('x2goumount-session (%s) for session %s has been successful' % (local_path, self.session_info, ), log.loglevel_NOTICE) 943 return True 944 else: 945 self.logger('x2goumount-session (%s) for session %s failed' % (local_path, self.session_info, ), log.loglevel_ERROR) 946 return False
947
948 - def color_depth(self):
949 """\ 950 Retrieve the session's color depth. 951 952 @return: the session's color depth 953 @rtype: C{int} 954 955 """ 956 return self.params.depth
957
958 - def auto_session_window_title(self, dont_set=False):
959 """\ 960 Automatically generate an appropriate human-readable session window title. 961 962 The session window title will be provider in the C{session_title} property of 963 this method. 964 965 @param dont_set: generate the session window title, but do not actually set it 966 @type dont_set: C{bool} 967 968 """ 969 _generic_title = 'X2GO-%s' % self.session_info.name 970 971 # no blanks at beginning or end, no blanks-only... 972 self.session_title = self.session_title.strip() 973 974 if self.params.session_type == 'D': 975 if self.set_session_title: 976 977 if not self.session_title: 978 self.session_title = '%s for %s@%s' % (self.params.cmd, self.control_session.remote_username(), self.control_session.get_hostname()) 979 980 else: 981 # session title fallback... (like X2Go server does it...) 982 self.session_title = _generic_title 983 984 else: 985 # do nothing for rootless sessions 986 self.session_title = _generic_title 987 988 if self.session_title != _generic_title and not dont_set: 989 self.set_session_window_title(title=self.session_title)
990
991 - def find_session_window(self, timeout=30):
992 """\ 993 Try for <timeout> seconds to find the X2Go session window of this 994 terminal session. 995 996 A background thread will get spawned for this operation. 997 998 @param timeout: try for <timeout> seconds to find the session window 999 @type timeout: C{int} 1000 1001 """ 1002 gevent.spawn(self._find_session_window, timeout=timeout)
1003
1004 - def _find_session_window(self, timeout=0):
1005 """\ 1006 Try for <timeout> seconds to find the X2Go session window of this 1007 terminal session. 1008 1009 @param timeout: try for <timeout> seconds to find the session window 1010 @type timeout: C{int} 1011 1012 """ 1013 self.session_window = None 1014 1015 # search for the window of our focus, do this in a loop till the window as been found 1016 # or timeout forces us to give up... 1017 timeout += 1 1018 while timeout: 1019 1020 timeout -= 1 1021 1022 window = utils.find_session_window(self.session_info.name) 1023 1024 if window is not None: 1025 self.session_window = window 1026 break 1027 1028 gevent.sleep(1)
1029
1030 - def set_session_window_title(self, title, timeout=30):
1031 """\ 1032 Modify the session window title. 1033 1034 A background thread will get spawned for this operation. 1035 1036 @param title: new title for the terminal session's session window 1037 @type title: C{str} 1038 @param timeout: try for <timeout> seconds to find the session window 1039 @type timeout: C{int} 1040 1041 """ 1042 gevent.spawn(self._set_session_window_title, title=title.strip(), timeout=timeout)
1043
1044 - def _set_session_window_title(self, title, timeout=0):
1045 """\ 1046 Modify the session window title. 1047 1048 @param title: new title for the terminal session's session window 1049 @type title: C{str} 1050 @param timeout: try for <timeout> seconds to find the session window 1051 @type timeout: C{int} 1052 1053 """ 1054 self.session_title = title 1055 1056 if not self.session_title: 1057 self.auto_session_title(dont_set=True) 1058 1059 timeout += 1 1060 while timeout: 1061 1062 timeout -= 1 1063 1064 if self.session_window is not None: 1065 utils.set_session_window_title(self.session_window, self.session_title) 1066 break 1067 1068 gevent.sleep(1)
1069
1070 - def raise_session_window(self, timeout=30):
1071 """\ 1072 Try for <timeout> seconds to raise the X2Go session window of this 1073 terminal session to the top and bring it to focus. 1074 1075 A background thread will get spawned for this operation. 1076 1077 @param timeout: try for <timeout> seconds to raise the session window 1078 @type timeout: C{int} 1079 1080 """ 1081 gevent.spawn(self._raise_session_window, timeout=timeout)
1082
1083 - def _raise_session_window(self, timeout=0):
1084 """ 1085 Try for <timeout> seconds to raise the X2Go session window of this 1086 terminal session to the top and bring it to focus. 1087 1088 @param timeout: try for <timeout> seconds to raise the session window 1089 @type timeout: C{int} 1090 1091 """ 1092 timeout += 1 1093 while timeout: 1094 1095 timeout -= 1 1096 1097 if self.session_window is not None: 1098 1099 utils.raise_session_window(self.session_window) 1100 break 1101 1102 gevent.sleep(1)
1103
1104 - def has_command(self, cmd):
1105 """\ 1106 ,,Guess'' if the command C{<cmd>} exists on the X2Go server and is executable. 1107 The expected result is not 100% safe, however, it comes with a high probability to 1108 be correct. 1109 1110 @param cmd: session command 1111 @type cmd: C{str} 1112 1113 @return: C{True} if this method reckons that the command is executable on the remote X2Go server 1114 @rtype: C{bool} 1115 1116 """ 1117 test_cmd = None; 1118 1119 cmd = cmd.strip('"').strip('"') 1120 if cmd.find('RDP') != -1: 1121 cmd = 'rdesktop' 1122 1123 if cmd in _X2GO_GENERIC_APPLICATIONS: 1124 return True 1125 if cmd in _X2GO_DESKTOPSESSIONS.keys(): 1126 return True 1127 elif 'XSHAD' in cmd: 1128 return True 1129 elif 'PUBLISHED' in cmd and 'X2GO_PUBLISHED_APPLICATIONS' in self.control_session.get_server_features(): 1130 return True 1131 elif cmd and cmd.startswith('/'): 1132 # check if full path is correct _and_ if application is in server path 1133 test_cmd = 'test -x %s && which %s && echo OK' % (cmd, os.path.basename(cmd.split()[0])) 1134 elif cmd and '/' not in cmd.split()[0]: 1135 # check if application is in server path only 1136 test_cmd = 'which %s && echo OK' % os.path.basename(cmd.split()[0]) 1137 1138 if test_cmd: 1139 (stdin, stdout, stderr) = self.control_session._x2go_exec_command([test_cmd]) 1140 _stdout = stdout.read() 1141 return _stdout.find('OK') != -1 1142 else: 1143 return False
1144
1145 - def run_command(self, cmd=None, env={}):
1146 """\ 1147 Run a command in this session. 1148 1149 After L{X2goTerminalSessionSTDOUT.start()} has been called 1150 one or more commands can be executed with L{X2goTerminalSessionSTDOUT.run_command()} 1151 within the current X2Go session. 1152 1153 @param cmd: Command to be run 1154 @type cmd: C{str} 1155 @param env: add server-side environment variables 1156 @type env: C{dict} 1157 1158 @return: stdout.read() and stderr.read() as returned by the run command 1159 on the X2Go server 1160 @rtype: C{tuple} of C{str} 1161 1162 """ 1163 if not self.has_command(_rewrite_cmd(self.params.cmd, params=self.params)): 1164 if self.client_instance: 1165 self.client_instance.HOOK_no_such_command(profile_name=self.profile_name, session_name=self.session_info.name, cmd=self.params.cmd) 1166 return False 1167 1168 if cmd in ("", None): 1169 if self.params.cmd is None: 1170 cmd = 'TERMINAL' 1171 else: 1172 cmd = self.params.cmd 1173 1174 if cmd == 'XDMCP': 1175 # do not run command when in XDMCP mode... 1176 return None 1177 1178 if 'XSHAD' in cmd: 1179 # do not run command when in DESKTOP SHARING mode... 1180 return None 1181 1182 self.params.update(cmd=cmd) 1183 1184 # do not allow the execution of full path names 1185 if '/' in cmd: 1186 cmd = os.path.basename(cmd) 1187 1188 cmd_line = [ "setsid x2goruncommand", 1189 str(self.session_info.display), 1190 str(self.session_info.agent_pid), 1191 str(self.session_info.name), 1192 str(self.session_info.snd_port), 1193 _rewrite_blanks(_rewrite_cmd(cmd, params=self.params)), 1194 str(self.params.snd_system), 1195 str(self.params.session_type), 1196 "&> /dev/null & exit", 1197 ] 1198 1199 if self.params.snd_system == 'pulse': 1200 cmd_line = [ 'PULSE_CLIENTCONFIG=%s/.pulse-client.conf' % self.session_info.remote_container ] + cmd_line 1201 1202 if env: 1203 for env_var in env.keys(): 1204 cmd_line = [ '%s=%s' % (env_var, env[env_var]) ] + cmd_line 1205 1206 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) 1207 1208 if self.params.kbtype not in ('null/null', 'auto') and (self.params.kblayout not in ('null', '') or self.params.kbvariant not in ('null', '')): 1209 self.set_keyboard(layout=self.params.kblayout, variant=self.params.kbvariant) 1210 1211 return stdout.read(), stderr.read()
1212
1213 - def is_desktop_session(self):
1214 """\ 1215 Is this (terminal) session a desktop session? 1216 1217 @return: Returns C{True} is this session is a desktop session. 1218 @rtype: C{bool} 1219 1220 """ 1221 if self.session_info: 1222 return self.session_info.is_desktop_session() 1223 return False
1224
1226 """\ 1227 Is this (terminal) session a published applications provider? 1228 1229 @return: Returns C{True} is this session is a provider session for published applications. 1230 @rtype: C{bool} 1231 1232 """ 1233 if self.session_info and self.is_running(): 1234 return self.session_info.is_published_applications_provider() 1235 return False
1236
1237 - def set_keyboard(self, layout='null', variant='null'):
1238 """\ 1239 Set the keyboard layout and variant for this (running) session. 1240 1241 @param layout: keyboard layout to be set 1242 @type layout: C{str} 1243 @param variant: keyboard variant to be set 1244 @type variant: C{str} 1245 1246 @return: returns C{True} if the {setxkbmap} command could be executed successfully. 1247 @rtype: C{bool} 1248 1249 """ 1250 if not self.is_running(): 1251 return False 1252 1253 cmd_line = [ 'export DISPLAY=:%s && ' % str(self.session_info.display), 1254 'setxkbmap ' 1255 ] 1256 1257 if layout != 'null': 1258 self.logger('setting keyboad layout ,,%s\'\' for session %s' % (layout, self.session_info), log.loglevel_INFO) 1259 cmd_line.append('-layout %s' % layout) 1260 if variant != 'null': 1261 self.logger('setting keyboad variant ,,%s\'\' for session %s' % (variant, self.session_info), log.loglevel_INFO) 1262 cmd_line.append('-variant %s' % variant) 1263 1264 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) 1265 _stderr = stderr.read() 1266 if not _stderr: 1267 self.logger('setting keyboard layout ,,%s\'\' and variant ,,%s\'\' for session %s has been successful' % (layout, variant, self.session_info), log.loglevel_NOTICE) 1268 return True 1269 else: 1270 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) 1271 return False
1272
1273 - def exec_published_application(self, exec_name, timeout=20, env={}):
1274 """\ 1275 Executed a published application. 1276 1277 @param exec_name: application to be executed 1278 @type exec_name: C{str} 1279 @param timeout: execution timeout 1280 @type timeout: C{int} 1281 @param env: session environment dictionary 1282 @type env: C{dict} 1283 1284 """ 1285 cmd_line = [ 1286 "export DISPLAY=:%s && " % str(self.session_info.display), 1287 "export X2GO_SESSION=%s && " % str(self.get_session_name()), 1288 ] 1289 1290 if self.params.snd_system == 'pulse': 1291 cmd_line.append("export PULSE_CLIENTCONFIG=%s/.pulse-client.conf && " % self.session_info.remote_container) 1292 1293 if env: 1294 for env_var in env.keys(): 1295 cmd_line = [ 'export %s=%s && ' % (env_var, env[env_var]) ] + cmd_line 1296 1297 cmd_line.extend( 1298 [ 1299 "setsid %s" % exec_name, 1300 "&> /dev/null & exit", 1301 ] 1302 ) 1303 1304 self.logger('executing published application %s for %s with command line: %s' % (exec_name, self.profile_name, cmd_line), loglevel=log.loglevel_DEBUG) 1305 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line, timeout=timeout)
1306
1307 - def ok(self):
1308 """\ 1309 X2Go session OK? 1310 1311 @return: Returns C{True} if this X2Go (terminal) session is up and running, 1312 C{False} otherwise. 1313 @rtype: C{bool} 1314 1315 """ 1316 _ok = bool(self.session_info.name and self.proxy.ok()) 1317 return _ok
1318
1319 - def is_running(self):
1320 """\ 1321 X2Go session running? 1322 1323 @return: Returns C{True} if this X2Go (terminal) session is in running state, 1324 C{False} otherwise. 1325 @rtype: C{bool} 1326 1327 """ 1328 return self.session_info.is_running()
1329
1330 - def is_suspended(self):
1331 """\ 1332 X2Go session suspended? 1333 1334 @return: Returns C{True} if this X2Go (terminal) session is in suspended state, 1335 C{False} otherwise. 1336 @rtype: C{bool} 1337 1338 """ 1339 return self.session_info.is_suspended()
1340
1341 - def is_connected(self):
1342 """\ 1343 X2Go session connected? 1344 1345 @return: Returns C{True} if this X2Go session's Paramiko/SSH transport is 1346 connected/authenticated, C{False} else. 1347 @rtype: C{bool} 1348 1349 """ 1350 return self.control_session.is_connected()
1351
1352 - def start(self):
1353 """\ 1354 Start a new X2Go session. 1355 1356 @return: C{True} if session startup has been successful and the X2Go proxy is up-and-running 1357 @rtype: C{bool} 1358 1359 @raise X2goTerminalSessionException: if the session startup failed 1360 1361 """ 1362 self.params.rewrite_session_type() 1363 1364 if not self.has_command(_rewrite_cmd(self.params.cmd, params=self.params)): 1365 if self.client_instance: 1366 self.client_instance.HOOK_no_such_command(profile_name=self.profile_name, session_name=self.session_info.name, cmd=self.params.cmd) 1367 return False 1368 1369 setkbd = "0" 1370 if self.params.kbtype != "null/null": 1371 setkbd = "1" 1372 1373 if '/' in self.params.cmd: 1374 self.params.cmd = os.path.basename(self.params.cmd) 1375 1376 self.params.rewrite_session_type() 1377 1378 cmd_line = [ "x2gostartagent", 1379 str(self.params.geometry), 1380 str(self.params.link), 1381 str(self.params.pack), 1382 str(self.params.cache_type+'-depth_'+self.params.depth), 1383 str(self.params.kblayout), 1384 str(self.params.kbtype), 1385 str(setkbd), 1386 str(self.params.session_type), 1387 self.params.cmd, 1388 ] 1389 1390 if self.params.cmd == 'XDMCP' and self.params.xdmcp_server: 1391 cmd_line = ['X2GOXDMCP=%s' % self.params.xdmcp_server] + cmd_line 1392 1393 if self.params.dpi: 1394 cmd_line = ['X2GODPI=%s' % self.params.dpi] + cmd_line 1395 1396 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) 1397 1398 _stdout = stdout.read() 1399 _stderr = stderr.read() 1400 1401 # if the first line of stdout is a "DEN(Y)" string then we will presume that 1402 # we tried to use X2Go desktop sharing and the sharing was rejected 1403 if "ACCESS DENIED" in _stderr and "XSHAD" in _stderr: 1404 raise x2go_exceptions.X2goDesktopSharingException('X2Go desktop sharing has been denied by the remote user') 1405 1406 try: 1407 self.session_info.initialize(_stdout, 1408 username=self.control_session.remote_username(), 1409 hostname=self.control_session.remote_peername(), 1410 ) 1411 except ValueError: 1412 raise x2go_exceptions.X2goTerminalSessionException("failed to start X2Go session") 1413 except IndexError: 1414 raise x2go_exceptions.X2goTerminalSessionException("failed to start X2Go session") 1415 1416 # local path may be a Windows path, so we use the path separator of the local system 1417 self.session_info.local_container = os.path.join(self.params.rootdir, 'S-%s' % self.session_info.name) 1418 # remote path is always a UniX path... 1419 self.session_info.remote_container = '%s/.x2go/C-%s' % (self.control_session._x2go_remote_home, 1420 self.session_info.name, 1421 ) 1422 1423 # set up SSH tunnel for X11 graphical elements 1424 self.proxy = self.proxy_backend(session_info=self.session_info, 1425 ssh_transport=self.control_session.get_transport(), 1426 sessions_rootdir=self.sessions_rootdir, 1427 session_instance=self.session_instance, 1428 proxy_options=self.proxy_options, 1429 logger=self.logger) 1430 self.proxy_subprocess, proxy_ok = self.proxy.start_proxy() 1431 1432 if proxy_ok: 1433 self.active_threads.append(self.proxy) 1434 1435 if self.params.session_type in ('D', 'S'): 1436 self.find_session_window() 1437 self.auto_session_window_title() 1438 self.raise_session_window() 1439 1440 if self.params.published_applications: 1441 self.control_session.get_published_applications() 1442 1443 else: 1444 raise x2go_exceptions.X2goTerminalSessionException("failed to start X2Go session") 1445 1446 return proxy_ok
1447
1448 - def resume(self):
1449 """\ 1450 Resume a running/suspended X2Go session. 1451 1452 @return: C{True} if the session could successfully be resumed 1453 @rtype: C{bool} 1454 1455 @raise X2goTerminalSessionException: if the terminal session failed to update server-side reported port changes 1456 1457 """ 1458 setkbd = "0" 1459 if self.params.kbtype != "null/null": 1460 setkbd = "1" 1461 1462 cmd_line = [ "x2goresume-session", self.session_info.name, 1463 self.params.geometry, 1464 self.params.link, 1465 self.params.pack, 1466 self.params.kblayout, 1467 self.params.kbtype, 1468 setkbd, 1469 ] 1470 1471 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) 1472 1473 # re-allocate (if needed) server-side ports for graphics, sound and sshfs 1474 for stdout_line in stdout.read(): 1475 try: 1476 _new_value = stdout_line.split("=")[1].strip() 1477 if 'gr_port=' in stdout_line and _new_value != str(self.session_info.graphics_port): 1478 try: 1479 self.session_info.graphics_port = int(_new_value) 1480 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) 1481 except TypeError: 1482 # if the re-allocation fails, this is fatal!!! 1483 raise x2go_exceptions.X2goTerminalSessionException('Failed to retrieve new graphics port from server. X2Go Session cannot be resumed.') 1484 elif 'sound_port=' in stdout_line and _new_value != str(self.session_info.snd_port): 1485 try: 1486 self.session_info.snd_port = int(_new_value) 1487 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) 1488 except TypeError: 1489 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) 1490 elif 'fs_port=' in stdout_line and _new_value != str(self.session_info.sshfs_port): 1491 try: 1492 self.session_info.sshfs_port = int(_new_value) 1493 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) 1494 except TypeError: 1495 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) 1496 except IndexError: 1497 continue 1498 1499 self.proxy = self.proxy_backend(session_info=self.session_info, 1500 ssh_transport=self.control_session.get_transport(), 1501 sessions_rootdir=self.sessions_rootdir, 1502 session_instance=self.session_instance, 1503 logger=self.logger 1504 ) 1505 self.proxy_subprocess, proxy_ok = self.proxy.start_proxy() 1506 1507 if proxy_ok: 1508 # local path may be a Windows path, so we use the path separator of the local system 1509 self.session_info.local_container = os.path.join(self.params.rootdir, 'S-%s' % self.session_info.name) 1510 # remote path is always a UniX path... 1511 self.session_info.remote_container = '%s/.x2go/C-%s' % (self.control_session._x2go_remote_home, 1512 self.session_info.name, 1513 ) 1514 self.params.depth = self.session_info.name.split('_')[2][2:] 1515 1516 # on a session resume the user name comes in as a user ID. We have to translate this... 1517 self.session_info.username = self.control_session.remote_username() 1518 1519 if self.params.kbtype not in ('null/null', 'auto') and (self.params.kblayout not in ('null', '') or self.params.kbvariant not in ('null', '')): 1520 self.set_keyboard(layout=self.params.kblayout, variant=self.params.kbvariant) 1521 1522 if self.params.session_type in ('D', 'S'): 1523 self.find_session_window() 1524 self.auto_session_window_title() 1525 self.raise_session_window() 1526 1527 if self.is_published_applications_provider(): 1528 self.control_session.get_published_applications() 1529 self.published_applications = True 1530 else: 1531 raise x2go_exceptions.X2goTerminalSessionException("failed to start X2Go session") 1532 1533 return proxy_ok
1534
1535 - def suspend(self):
1536 """\ 1537 Suspend this X2Go (terminal) session. 1538 1539 @return: C{True} if the session terminal could be successfully suspended 1540 @rtype: C{bool} 1541 1542 """ 1543 self.control_session.suspend(session_name=self.session_info.name) 1544 self.release_proxy() 1545 1546 # TODO: check if session has really suspended 1547 _ret = True 1548 1549 return _ret
1550
1551 - def terminate(self):
1552 """\ 1553 Terminate this X2Go (terminal) session. 1554 1555 @return: C{True} if the session could be successfully terminated 1556 @rtype: C{bool} 1557 1558 """ 1559 self.control_session.terminate(session_name=self.session_info.name, destroy_terminals=False) 1560 self.release_proxy() 1561 self.post_terminate_cleanup() 1562 self.__del__() 1563 1564 # TODO: check if session has really suspended 1565 _ret = True 1566 1567 return _ret
1568
1569 - def release_proxy(self):
1570 """\ 1571 Let the X2Go proxy command cleanly die away... (by calling its destructor). 1572 1573 """ 1574 if self.proxy is not None: 1575 self.proxy.__del__()
1576
1577 - def post_terminate_cleanup(self):
1578 """\ 1579 Do some cleanup after this session has terminated. 1580 1581 """ 1582 # this method might be called twice (directly and from update_status in the session 1583 # registry instance. So we have to make sure, that this code will not fail 1584 # if called twice. 1585 if not self._cleaned_up and self.session_info.name: 1586 1587 # otherwise we wipe the session files locally 1588 self.logger('cleaning up session %s after termination' % self.session_info, loglevel=log.loglevel_NOTICE) 1589 1590 # if we run in debug mode, we keep local session directories 1591 if self.logger.get_loglevel() & log.loglevel_DEBUG != log.loglevel_DEBUG: 1592 1593 self._rm_session_dirtree() 1594 self._rm_desktop_dirtree() 1595 1596 self._cleaned_up = True
1597