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