Package x2go :: Module utils
[frames] | no frames]

Source Code for Module x2go.utils

  1  # -*- coding: utf-8 -*- 
  2   
  3  # Copyright (C) 2010-2012 by Mike Gabriel <mike.gabriel@das-netzwerkteam.de> 
  4  # 
  5  # Python X2Go is free software; you can redistribute it and/or modify 
  6  # it under the terms of the GNU Affero General Public License as published by 
  7  # the Free Software Foundation; either version 3 of the License, or 
  8  # (at your option) any later version. 
  9  # 
 10  # Python X2Go is distributed in the hope that it will be useful, 
 11  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 12  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 13  # GNU Affero General Public License for more details. 
 14  # 
 15  # You should have received a copy of the GNU Affero General Public License 
 16  # along with this program; if not, write to the 
 17  # Free Software Foundation, Inc., 
 18  # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. 
 19   
 20  """\ 
 21  Python X2Go helper functions, constants etc. 
 22   
 23  """ 
 24  __NAME__ = 'x2goutils-pylib' 
 25   
 26  import sys 
 27  import os 
 28  import locale 
 29  import re 
 30  import types 
 31  import copy 
 32  import socket 
 33  import gevent 
 34  import string 
 35  import subprocess 
 36   
 37  # Python X2Go modules 
 38  from defaults import X2GOCLIENT_OS as _X2GOCLIENT_OS 
 39  from defaults import X2GO_SESSIONPROFILE_DEFAULTS as _X2GO_SESSIONPROFILE_DEFAULTS 
 40  from defaults import X2GO_MIMEBOX_ACTIONS as _X2GO_MIMEBOX_ACTIONS 
 41  from defaults import pack_methods_nx3 
 42   
 43  if _X2GOCLIENT_OS != 'Windows': 
 44      import Xlib 
 45      from defaults import X_DISPLAY as _X_DISPLAY 
 46   
 47  if _X2GOCLIENT_OS == 'Windows': 
 48      import win32api 
 49      import win32gui 
 50   
51 -def is_in_nx3packmethods(method):
52 53 """\ 54 Test if a given compression method is valid for NX3 Proxy. 55 56 @return: C{True} if C{method} is in the hard-coded list of NX3 compression methods. 57 @rtype: C{bool} 58 59 """ 60 return method in pack_methods_nx3
61 62
63 -def find_session_line_in_x2golistsessions(session_name, stdout):
64 """\ 65 Return the X2Go session meta information as returned by the 66 C{x2golistsessions} server command for session C{session_name}. 67 68 @param session_name: name of a session 69 @type session_name: C{str} 70 @param stdout: raw output from the ,,x2golistsessions'' command, as list of strings 71 @type stdout: C{list} 72 73 @return: the output line that contains C{<session_name>} 74 @rtype: C{str} or C{None} 75 76 """ 77 sessions = stdout.read().split("\n") 78 for line in sessions: 79 # skip empty lines 80 if not line: 81 continue 82 if session_name == line.split("|")[1]: 83 return line 84 return None
85 86
87 -def slugify(value):
88 """\ 89 Normalizes string, converts to lowercase, removes non-alpha characters, 90 converts spaces to hyphens and replaces round brackets by pointed brackets. 91 92 @param value: a string that shall be sluggified 93 @type value: C{str} 94 95 @return: the sluggified string 96 @rtype: C{str} 97 98 """ 99 import unicodedata 100 value = unicodedata.normalize('NFKD', unicode(value)).encode('ascii', 'ignore') 101 value = re.sub('[^\w\s-]', '', value).strip().lower() 102 value = re.sub('[(]', '<', value).strip().lower() 103 value = re.sub('[)]', '>', value).strip().lower() 104 return value
105
106 -def _genSessionProfileId():
107 """\ 108 Generate a session profile ID as used in x2goclient's sessions config file. 109 110 @return: profile ID 111 @rtype: C{str} 112 113 """ 114 import datetime 115 return datetime.datetime.utcnow().strftime('%Y%m%d%H%m%S%f')
116 117
118 -def _checkIniFileDefaults(data_structure):
119 """\ 120 Check an ini file data structure passed on by a user app or class. 121 122 @param data_structure: an ini file date structure 123 @type data_structure: C{dict} of C{dict}s 124 125 @return: C{True} if C{data_structure} matches that of an ini file data structure 126 @rtype: C{bool} 127 128 """ 129 if data_structure is None: 130 return False 131 if type(data_structure) is not types.DictType: 132 return False 133 for sub_dict in data_structure.values(): 134 if type(sub_dict) is not types.DictType: 135 return False 136 return True
137 138
139 -def _checkSessionProfileDefaults(data_structure):
140 """\ 141 Check the data structure of a default session profile passed by a user app. 142 143 @param data_structure: an ini file date structure 144 @type data_structure: C{dict} of C{dict}s 145 146 @return: C{True} if C{data_structure} matches that of an ini file data structure 147 @rtype: C{bool} 148 149 """ 150 if data_structure is None: 151 return False 152 if type(data_structure) is not types.DictType: 153 return False 154 return True
155 156
157 -def _convert_SessionProfileOptions_2_SessionParams(options):
158 """\ 159 Convert session profile options as used in x2goclient's sessions file to 160 Python X2Go session parameters. 161 162 @param options: a dictionary of options, parameter names as in the X2Go ,,sessions'' file 163 @type options: C{dict} 164 165 @return: session options as used in C{X2goSession} instances 166 @rtype: C{dict} 167 168 """ 169 _params = copy.deepcopy(options) 170 171 # get rid of unknown session profile options 172 _known_options = _X2GO_SESSIONPROFILE_DEFAULTS.keys() 173 for p in _params.keys(): 174 if p not in _known_options: 175 del _params[p] 176 177 _rename_dict = { 178 'host': 'server', 179 'user': 'username', 180 'soundsystem': 'snd_system', 181 'sndport': 'snd_port', 182 'type': 'kbtype', 183 'layout': 'kblayout', 184 'variant': 'kbvariant', 185 'speed': 'link', 186 'sshport': 'port', 187 'useexports': 'allow_share_local_folders', 188 'usemimebox': 'allow_mimebox', 189 'mimeboxextensions': 'mimebox_extensions', 190 'mimeboxaction': 'mimebox_action', 191 'print': 'printing', 192 'name': 'profile_name', 193 'key': 'key_filename', 194 'command': 'cmd', 195 'rdpserver': 'rdp_server', 196 'rdpoptions': 'rdp_options', 197 'xdmcpserver': 'xdmcp_server', 198 'useiconv': 'convert_encoding', 199 'iconvto': 'server_encoding', 200 'iconvfrom': 'client_encoding', 201 'usesshproxy': 'use_sshproxy', 202 'sshproxyhost': 'sshproxy_host', 203 'sshproxyuser': 'sshproxy_user', 204 'sshproxykeyfile': 'sshproxy_key_filename', 205 'sshproxytunnel': 'sshproxy_tunnel', 206 'sessiontitle': 'session_title', 207 'setsessiontitle': 'set_session_title', 208 'published': 'published_applications', 209 'autostart': 'auto_start_or_resume', 210 'autologin': 'auto_connect', 211 212 } 213 _speed_dict = { 214 '0': 'modem', 215 '1': 'isdn', 216 '2': 'adsl', 217 '3': 'wan', 218 '4': 'lan', 219 } 220 221 for opt, val in options.iteritems(): 222 223 # rename options if necessary 224 if opt in _rename_dict.keys(): 225 del _params[opt] 226 opt = _rename_dict[opt] 227 if opt in _known_options: 228 _type = type(_known_options[opt]) 229 _params[opt] = _type(val) 230 else: 231 _params[opt] = val 232 233 # translate integer values for connection speed to readable strings 234 if opt == 'link': 235 val = str(val).lower() 236 if val in _speed_dict.keys(): 237 val = _speed_dict[val] 238 val = val.lower() 239 _params['link'] = val 240 241 # share_local_folders is a list 242 if opt in ('share_local_folders', 'mimebox_extensions'): 243 if type(val) is types.StringType: 244 if val: 245 _params[opt] = val.split(',') 246 else: 247 _params[opt] = [] 248 249 # append value for quality to value for pack method 250 if _params['quality']: 251 _params['pack'] = '%s-%s' % (_params['pack'], _params['quality']) 252 # delete quality in any case... 253 del _params['quality'] 254 255 del _params['fstunnel'] 256 257 if _params.has_key('export'): 258 259 _export = _params['export'] 260 del _params['export'] 261 # fix for wrong export field usage in PyHoca-GUI/CLI and python-x2go before 20110923 262 _export = _export.replace(",", ";") 263 264 _export = _export.strip().strip('"').strip().strip(';').strip() 265 _export_list = [ f for f in _export.split(';') if f ] 266 267 _params['share_local_folders'] = [] 268 for _shared_folder in _export_list: 269 # fix for wrong export field usage in PyHoca-GUI/CLI and python-x2go before 20110923 270 if not ":" in _shared_folder: _shared_folder = "%s:1" % _shared_folder 271 if _shared_folder.split(":")[-1] == "1": 272 _params['share_local_folders'].append(":".join(_shared_folder.split(":")[:-1])) 273 274 if not options['fullscreen']: 275 _params['geometry'] = '%sx%s' % (options['width'], options['height']) 276 else: 277 _params['geometry'] = 'fullscreen' 278 del _params['width'] 279 del _params['height'] 280 del _params['fullscreen'] 281 282 if not options['sound']: 283 _params['snd_system'] = 'none' 284 del _params['sound'] 285 286 if not options['rootless']: 287 _params['session_type'] = 'desktop' 288 else: 289 _params['session_type'] = 'application' 290 del _params['rootless'] 291 292 if _params['mimebox_action'] not in _X2GO_MIMEBOX_ACTIONS.keys(): 293 _params['mimebox_action'] = 'OPEN' 294 295 if not options['usekbd']: 296 _params['kbtype'] = 'null/null' 297 _params['kblayout'] = 'null' 298 _params['kbvariant'] = 'null' 299 del _params['usekbd'] 300 301 if not _params['kbtype'].strip(): _params['kbtype'] = 'null/null' 302 if not _params['kblayout'].strip(): _params['kblayout'] = 'null' 303 if not _params['kbvariant'].strip(): _params['kbvariant'] = 'null' 304 305 if not options['setdpi']: 306 del _params['dpi'] 307 del _params['setdpi'] 308 309 # currently known but ignored in Python X2go 310 _ignored_options = [ 311 'startsoundsystem', 312 'soundtunnel', 313 'defsndport', 314 'icon', 315 'xinerama', 316 'multidisp', 317 'krblogin', 318 ] 319 for i in _ignored_options: 320 del _params[i] 321 322 return _params
323 324
325 -def session_names_by_timestamp(session_infos):
326 """\ 327 Sorts session profile names by their timestamp (as used in the file format's section name). 328 329 @param session_infos: a dictionary of session infos as reported by L{X2goClient.list_sessions()} 330 @type session_infos: C{dict} 331 332 @return: a timestamp-sorted list of session names found in C{session_infos} 333 @rtype: C{list} 334 335 """ 336 session_names = session_infos.keys() 337 sortable_session_names = [ '%s|%s' % (session_name.split('-')[-1].split('_')[0], session_name) for session_name in session_names ] 338 sortable_session_names.sort() 339 return [ session_name.split('|')[1] for session_name in sortable_session_names ]
340 341
342 -def touch_file(filename, mode='a'):
343 """\ 344 Imitates the behaviour of the GNU/touch command. 345 346 @param filename: name of the file to touch 347 @type filename: C{str} 348 @param mode: the file mode (as used for Python file objects) 349 @type mode: C{str} 350 351 """ 352 if not os.path.isdir(os.path.dirname(filename)): 353 os.makedirs(os.path.dirname(filename), mode=00700) 354 f = open(filename, mode=mode) 355 f.close()
356 357
358 -def unique(seq):
359 """\ 360 Imitates the behaviour of the GNU/uniq command. 361 362 @param seq: a list/sequence containing consecutive duplicates. 363 @type seq: C{list} 364 365 @return: list that has been clean up from the consecutive duplicates 366 @rtype: C{list} 367 368 """ 369 # order preserving 370 noDupes = [] 371 [noDupes.append(i) for i in seq if not noDupes.count(i)] 372 return noDupes
373 374
375 -def known_encodings():
376 """\ 377 Render a list of all-known-to-Python character encodings (including 378 all known aliases) 379 380 """ 381 from encodings.aliases import aliases 382 _raw_encname_list = [] 383 _raw_encname_list.extend(aliases.keys()) 384 _raw_encname_list.extend(aliases.values()) 385 _raw_encname_list.sort() 386 _encname_list = [] 387 for _raw_encname in _raw_encname_list: 388 _encname = _raw_encname.upper() 389 _encname = _encname.replace('_', '-') 390 _encname_list.append(_encname) 391 _encname_list.sort() 392 _encname_list = unique(_encname_list) 393 return _encname_list
394 395
396 -def patiently_remove_file(dirname, filename):
397 """\ 398 Try to remove a file, wait for unlocking, remove it once removing is possible... 399 400 @param dirname: directory name the file is in 401 @type dirname: C{str} 402 @param filename: name of the file to be removed 403 @type filename: C{str} 404 405 """ 406 _not_removed = True 407 while _not_removed: 408 try: 409 os.remove(os.path.join(dirname, filename)) 410 _not_removed = False 411 except: 412 # file is probably locked 413 gevent.sleep(5)
414 415
416 -def detect_unused_port(bind_address='127.0.0.1', preferred_port=None):
417 """\ 418 Detect an unused IP socket. 419 420 @param bind_address: IP address to bind to 421 @type bind_address: C{str} 422 @param preferred_port: IP socket port that shall be tried first for availability 423 @type preferred_port: C{str} 424 425 @return: free local IP socket port that can be used for binding 426 @rtype: C{str} 427 428 """ 429 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) 430 try: 431 if preferred_port: 432 sock.bind((bind_address, preferred_port)) 433 ipaddr, port = sock.getsockname() 434 else: 435 raise 436 except: 437 sock.bind(('', 0)) 438 ipaddr, port = sock.getsockname() 439 return port
440 441
442 -def get_encoding():
443 """\ 444 Detect systems default character encoding. 445 446 @return: The system's local character encoding. 447 @rtype: C{str} 448 449 """ 450 try: 451 encoding = locale.getdefaultlocale()[1] 452 if encoding is None: 453 raise BaseException 454 except: 455 try: 456 encoding = sys.getdefaultencoding() 457 except: 458 encoding = 'ascii' 459 return encoding
460 461
462 -def is_abs_path(path):
463 """\ 464 Test if a given path is an absolute path name. 465 466 @param path: test this path for absolutism... 467 @type path: C{str} 468 469 @return: Returns C{True} if path is an absolute path name 470 @rtype: C{bool} 471 472 """ 473 return bool((path.startswith('/') or re.match('^[%s]\:\\\\' % string.ascii_letters, path)))
474 475
476 -def xkb_rules_names():
477 """\ 478 Wrapper for: xprop -root _XKB_RULES_NAMES 479 480 @return: A Python dictionary that contains the current X11 keyboard rules. 481 @rtype: C{dict} 482 483 """ 484 p = subprocess.Popen(['xprop', '-root', '_XKB_RULES_NAMES',], stdout=subprocess.PIPE, ) 485 _rn_list = p.stdout.read().split('"') 486 _rn_dict = { 487 'rules': _rn_list[1], 488 'model': _rn_list[3], 489 'layout': _rn_list[5], 490 'variant': _rn_list[7], 491 'options': _rn_list[9], 492 } 493 return _rn_dict
494 495
496 -def local_color_depth():
497 """\ 498 Detect the current local screen's color depth. 499 500 @return: the local color depth in bits 501 @rtype: C{int} 502 503 """ 504 if _X2GOCLIENT_OS != 'Windows': 505 try: 506 p = subprocess.Popen(['xwininfo', '-root',], stdout=subprocess.PIPE, ) 507 _depth_line = [ _info.strip() for _info in p.stdout.read().split('\n') if 'Depth:' in _info ][0] 508 _depth = _depth_line.split(' ')[1] 509 return int(_depth) 510 except IndexError: 511 # a sensible default value 512 return 24 513 except OSError: 514 # for building your package... 515 return 24 516 517 else: 518 return win32api.GetSystemMetrics(2)
519 520
521 -def is_color_depth_ok(depth_session, depth_local):
522 """\ 523 Test if color depth of this session is compatible with the 524 local screen's color depth. 525 526 @param depth_session: color depth of the session 527 @type depth_session: C{int} 528 @param depth_local: color depth of local screen 529 @type depth_local: C{int} 530 531 @return: Does the session color depth work with the local display? 532 @rtype: C{bool} 533 534 """ 535 if depth_session == 0: 536 return True 537 if depth_session == depth_local: 538 return True 539 if ( ( depth_session == 24 or depth_session == 32 ) and ( depth_local == 24 or depth_local == 32 ) ): 540 return True; 541 return False
542 543
544 -def find_session_window(session_name):
545 """\ 546 Find a session window by its X2GO session ID. 547 548 @param session_name: session name/ID of an X2Go session window 549 @type session_name: C{str} 550 551 @return: the window object (or ID) of the searched for session window 552 @rtype: C{obj} on Unix, C{int} on Windows 553 554 """ 555 if _X2GOCLIENT_OS != 'Windows': 556 # establish connection to the win API in use... 557 display = _X_DISPLAY 558 root = display.screen().root 559 560 success = False 561 windowIDs = root.get_full_property(display.intern_atom('_NET_CLIENT_LIST'), Xlib.X.AnyPropertyType).value 562 for windowID in windowIDs: 563 window = display.create_resource_object('window', windowID) 564 name = window.get_wm_name() 565 if name is not None and session_name in name: 566 success = True 567 break 568 569 if success: 570 return window 571 572 else: 573 574 windows = [] 575 window = None 576 577 def _callback(hwnd, extra): 578 if win32gui.GetWindowText(hwnd) == "X2GO-%s" % session_name: 579 windows.append(hwnd)
580 581 win32gui.EnumWindows(_callback, None) 582 if len(windows): window = windows[0] 583 584 return window 585 586
587 -def set_session_window_title(session_window, session_title):
588 """\ 589 Set title of session window. 590 591 @param session_window: session window instance 592 @type session_window: C{obj} 593 @param session_title: session title to be set for C{session_window} 594 @type session_title: C{str} 595 596 """ 597 if _X2GOCLIENT_OS != 'Windows': 598 try: 599 session_window.set_wm_name(str(session_title)) 600 session_window.set_wm_icon_name(str(session_title)) 601 _X_DISPLAY.sync() 602 except Xlib.error.BadWindow: 603 pass 604 605 else: 606 win32gui.SetWindowText(session_window, session_title)
607 608
609 -def raise_session_window(session_window):
610 """\ 611 Raise session window. Not functional for Unix-like operating systems. 612 613 @param session_window: session window instance 614 @type session_window: C{obj} 615 616 """ 617 if _X2GOCLIENT_OS != 'Windows': 618 pass 619 else: 620 if session_window is not None: 621 win32gui.SetForegroundWindow(session_window)
622 623
624 -def merge_ordered_lists(l1, l2):
625 """\ 626 Merge sort two sorted lists 627 628 @param l1: first sorted list 629 @type l1: C{list} 630 @param l2: second sorted list 631 @type l2: C{list} 632 633 @return: the merge result of both sorted lists 634 @rtype: C{list} 635 636 """ 637 ordered_list = [] 638 639 # Copy both the args to make sure the original lists are not 640 # modified 641 l1 = l1[:] 642 l2 = l2[:] 643 644 while (l1 and l2): 645 if l1[0] not in l2: 646 item = l1.pop(0) 647 elif l2[0] not in l1: 648 item = l2.pop(0) 649 elif l1[0] in l2: 650 item = l1.pop(0) 651 l2.remove(item) 652 if item not in ordered_list: 653 ordered_list.append(item) 654 655 # Add the remaining of the lists 656 ordered_list.extend(l1 if l1 else l2) 657 658 return ordered_list
659