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

Source Code for Module x2go.backends.terminal.plain

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