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