1# This file is part of Xpra. 2# Copyright (C) 2008, 2009 Nathaniel Smith <njs@pobox.com> 3# Copyright (C) 2013-2021 Antoine Martin <antoine@xpra.org> 4# Xpra is released under the terms of the GNU GPL v2, or, at your option, any 5# later version. See the file COPYING for details. 6 7import os 8import re 9import sys 10import binascii 11import traceback 12import threading 13from itertools import chain 14 15 16XPRA_APP_ID = 0 17 18XPRA_GUID1 = 0x67b3efa2 19XPRA_GUID2 = 0xe470 20XPRA_GUID3 = 0x4a5f 21XPRA_GUID4 = (0xb6, 0x53, 0x6f, 0x6f, 0x98, 0xfe, 0x60, 0x81) 22XPRA_GUID_STR = "67B3EFA2-E470-4A5F-B653-6F6F98FE6081" 23XPRA_GUID_BYTES = binascii.unhexlify(XPRA_GUID_STR.replace("-","")) 24 25 26XPRA_NOTIFICATIONS_OFFSET = 2**24 27XPRA_BANDWIDTH_NOTIFICATION_ID = XPRA_NOTIFICATIONS_OFFSET+1 28XPRA_IDLE_NOTIFICATION_ID = XPRA_NOTIFICATIONS_OFFSET+2 29XPRA_WEBCAM_NOTIFICATION_ID = XPRA_NOTIFICATIONS_OFFSET+3 30XPRA_AUDIO_NOTIFICATION_ID = XPRA_NOTIFICATIONS_OFFSET+4 31XPRA_OPENGL_NOTIFICATION_ID = XPRA_NOTIFICATIONS_OFFSET+5 32XPRA_SCALING_NOTIFICATION_ID = XPRA_NOTIFICATIONS_OFFSET+6 33XPRA_NEW_USER_NOTIFICATION_ID = XPRA_NOTIFICATIONS_OFFSET+7 34XPRA_CLIPBOARD_NOTIFICATION_ID = XPRA_NOTIFICATIONS_OFFSET+8 35XPRA_FAILURE_NOTIFICATION_ID = XPRA_NOTIFICATIONS_OFFSET+9 36XPRA_DPI_NOTIFICATION_ID = XPRA_NOTIFICATIONS_OFFSET+10 37XPRA_DISCONNECT_NOTIFICATION_ID = XPRA_NOTIFICATIONS_OFFSET+11 38XPRA_DISPLAY_NOTIFICATION_ID = XPRA_NOTIFICATIONS_OFFSET+12 39XPRA_STARTUP_NOTIFICATION_ID = XPRA_NOTIFICATIONS_OFFSET+13 40XPRA_FILETRANSFER_NOTIFICATION_ID = XPRA_NOTIFICATIONS_OFFSET+14 41XPRA_SHADOWWAYLAND_NOTIFICATION_ID = XPRA_NOTIFICATIONS_OFFSET+15 42 43 44#constants shared between client and server: 45#(do not modify the values, see also disconnect_is_an_error) 46#timeouts: 47CLIENT_PING_TIMEOUT = "client ping timeout" 48LOGIN_TIMEOUT = "login timeout" 49CLIENT_EXIT_TIMEOUT = "client exit timeout" 50#errors: 51PROTOCOL_ERROR = "protocol error" 52VERSION_ERROR = "version error" 53CONTROL_COMMAND_ERROR = "control command error" 54AUTHENTICATION_ERROR = "authentication error" 55PERMISSION_ERROR = "permission error" 56SERVER_ERROR = "server error" 57SESSION_NOT_FOUND = "session not found error" 58#informational (not a problem): 59DONE = "done" 60SERVER_EXIT = "server exit" 61SERVER_UPGRADE = "server upgrade" 62SERVER_SHUTDOWN = "server shutdown" 63CLIENT_REQUEST = "client request" 64DETACH_REQUEST = "detach request" 65NEW_CLIENT = "new client" 66IDLE_TIMEOUT = "idle timeout" 67SESSION_BUSY = "session busy" 68#client telling the server: 69CLIENT_EXIT = "client exit" 70 71 72DEFAULT_PORT = 14500 73 74DEFAULT_PORTS = { 75 "ws" : 80, 76 "wss" : 443, 77 "ssl" : DEFAULT_PORT, #could also default to 443? 78 "ssh" : 22, 79 "tcp" : DEFAULT_PORT, 80 "vnc" : 5900, 81 } 82 83 84#magic value for "workspace" window property, means unset 85WORKSPACE_UNSET = 65535 86WORKSPACE_ALL = 0xffffffff 87 88WORKSPACE_NAMES = { 89 WORKSPACE_UNSET : "unset", 90 WORKSPACE_ALL : "all", 91 } 92 93#this default value is based on 0.14.19 clients, 94#later clients should provide the 'metadata.supported" capability instead 95DEFAULT_METADATA_SUPPORTED = ("title", "icon-title", "pid", "iconic", 96 "size-hints", "class-instance", "client-machine", 97 "transient-for", "window-type", 98 "fullscreen", "maximized", "decorations", "skip-taskbar", "skip-pager", 99 "has-alpha", "override-redirect", "tray", "modal", 100 "role", "opacity", "xid", "group-leader", 101 "opaque-region", 102 ) 103 104 105#initiate-moveresize X11 constants 106MOVERESIZE_SIZE_TOPLEFT = 0 107MOVERESIZE_SIZE_TOP = 1 108MOVERESIZE_SIZE_TOPRIGHT = 2 109MOVERESIZE_SIZE_RIGHT = 3 110MOVERESIZE_SIZE_BOTTOMRIGHT = 4 111MOVERESIZE_SIZE_BOTTOM = 5 112MOVERESIZE_SIZE_BOTTOMLEFT = 6 113MOVERESIZE_SIZE_LEFT = 7 114MOVERESIZE_MOVE = 8 115MOVERESIZE_SIZE_KEYBOARD = 9 116MOVERESIZE_MOVE_KEYBOARD = 10 117MOVERESIZE_CANCEL = 11 118MOVERESIZE_DIRECTION_STRING = { 119 MOVERESIZE_SIZE_TOPLEFT : "SIZE_TOPLEFT", 120 MOVERESIZE_SIZE_TOP : "SIZE_TOP", 121 MOVERESIZE_SIZE_TOPRIGHT : "SIZE_TOPRIGHT", 122 MOVERESIZE_SIZE_RIGHT : "SIZE_RIGHT", 123 MOVERESIZE_SIZE_BOTTOMRIGHT : "SIZE_BOTTOMRIGHT", 124 MOVERESIZE_SIZE_BOTTOM : "SIZE_BOTTOM", 125 MOVERESIZE_SIZE_BOTTOMLEFT : "SIZE_BOTTOMLEFT", 126 MOVERESIZE_SIZE_LEFT : "SIZE_LEFT", 127 MOVERESIZE_MOVE : "MOVE", 128 MOVERESIZE_SIZE_KEYBOARD : "SIZE_KEYBOARD", 129 MOVERESIZE_MOVE_KEYBOARD : "MOVE_KEYBOARD", 130 MOVERESIZE_CANCEL : "CANCEL", 131 } 132SOURCE_INDICATION_UNSET = 0 133SOURCE_INDICATION_NORMAL = 1 134SOURCE_INDICATION_PAGER = 2 135SOURCE_INDICATION_STRING = { 136 SOURCE_INDICATION_UNSET : "UNSET", 137 SOURCE_INDICATION_NORMAL : "NORMAL", 138 SOURCE_INDICATION_PAGER : "PAGER", 139 } 140 141 142util_logger = None 143def get_util_logger(): 144 global util_logger 145 if not util_logger: 146 from xpra.log import Logger 147 util_logger = Logger("util") 148 return util_logger 149 150 151#convenience method based on the strings above: 152def disconnect_is_an_error(reason): 153 return reason.find("error")>=0 or (reason.find("timeout")>=0 and reason!=IDLE_TIMEOUT) 154 155 156def dump_exc(): 157 """Call this from a except: clause to print a nice traceback.""" 158 print("".join(traceback.format_exception(*sys.exc_info()))) 159 160def noerr(fn, *args): 161 try: 162 return fn(*args) 163 except Exception: 164 return None 165 166 167def net_utf8(value): 168 """ 169 Given a value received by the network layer, 170 convert it to a string. 171 Gymnastics are involved if the rencode packet encoder is used 172 as it ends up giving us a string which is actually utf8 bytes. 173 """ 174 #with 'rencodeplus' or 'bencode', we just get the unicode string directly: 175 if isinstance(value, str): 176 return value 177 #with rencode v1, we have to decode the value: 178 #(after converting it to 'bytes' if necessary) 179 return u(strtobytes(value)) 180 181 182def u(v): 183 if isinstance(v, str): 184 return v 185 try: 186 return v.decode("utf8") 187 except (AttributeError, UnicodeDecodeError): 188 return bytestostr(v) 189 190 191# A simple little class whose instances we can stick random bags of attributes 192# on. 193class AdHocStruct: 194 def __repr__(self): 195 return ("<%s object, contents: %r>" 196 % (type(self).__name__, self.__dict__)) 197 198 199def remove_dupes(seq): 200 seen = set() 201 seen_add = seen.add 202 return [x for x in seq if not (x in seen or seen_add(x))] 203 204def merge_dicts(a, b, path=None): 205 """ merges b into a """ 206 if path is None: 207 path = [] 208 for key in b: 209 if key in a: 210 if isinstance(a[key], dict) and isinstance(b[key], dict): 211 merge_dicts(a[key], b[key], path + [str(key)]) 212 elif a[key] == b[key]: 213 pass # same leaf value 214 else: 215 raise Exception('Conflict at %s: existing value is %s, new value is %s' % ( 216 '.'.join(path + [str(key)]), a[key], b[key])) 217 else: 218 a[key] = b[key] 219 return a 220 221def make_instance(class_options, *args): 222 log = get_util_logger() 223 log("make_instance%s", tuple([class_options]+list(args))) 224 for c in class_options: 225 if c is None: 226 continue 227 try: 228 v = c(*args) 229 log("make_instance(..) %s()=%s", c, v) 230 if v: 231 return v 232 except Exception: 233 log.error("make_instance(%s, %s)", class_options, args, exc_info=True) 234 log.error("Error: cannot instantiate %s:", c) 235 log.error(" with arguments %s", tuple(args)) 236 return None 237 238 239def roundup(n, m): 240 return (n + m - 1) & ~(m - 1) 241 242 243class AtomicInteger: 244 __slots__ = ("counter", "lock") 245 def __init__(self, integer = 0): 246 self.counter = integer 247 self.lock = threading.RLock() 248 249 def increase(self, inc = 1): 250 with self.lock: 251 self.counter = self.counter + inc 252 return self.counter 253 254 def decrease(self, dec = 1): 255 with self.lock: 256 self.counter = self.counter - dec 257 return self.counter 258 259 def get(self): 260 return self.counter 261 262 def __str__(self): 263 return str(self.counter) 264 265 def __repr__(self): 266 return "AtomicInteger(%s)" % self.counter 267 268 269 def __int__(self): 270 return self.counter 271 272 def __eq__(self, other): 273 try: 274 return self.counter==int(other) 275 except ValueError: 276 return -1 277 278 def __cmp__(self, other): 279 try: 280 return self.counter-int(other) 281 except ValueError: 282 return -1 283 284 285class MutableInteger(object): 286 __slots__ = ("counter") 287 def __init__(self, integer = 0): 288 self.counter = integer 289 290 def increase(self, inc = 1): 291 self.counter = self.counter + inc 292 return self.counter 293 294 def decrease(self, dec = 1): 295 self.counter = self.counter - dec 296 return self.counter 297 298 def get(self): 299 return self.counter 300 301 def __str__(self): 302 return str(self.counter) 303 304 def __repr__(self): 305 return "MutableInteger(%s)" % self.counter 306 307 308 def __int__(self): 309 return self.counter 310 311 def __eq__(self, other): 312 return self.counter==int(other) 313 def __ne__(self, other): 314 return self.counter!=int(other) 315 def __lt__(self, other): 316 return self.counter<int(other) 317 def __le__(self, other): 318 return self.counter<=int(other) 319 def __gt__(self, other): 320 return self.counter>int(other) 321 def __ge__(self, other): 322 return self.counter>=int(other) 323 def __cmp__(self, other): 324 return self.counter-int(other) 325 326 327def strtobytes(x) -> bytes: 328 if isinstance(x, bytes): 329 return x 330 return str(x).encode("latin1") 331def bytestostr(x) -> str: 332 if isinstance(x, (bytes, bytearray)): 333 return x.decode("latin1") 334 return str(x) 335 336def decode_str(x, try_encoding="utf8"): 337 """ 338 When we want to decode something (usually a byte string) no matter what. 339 Try with utf8 first then fallback to just bytestostr(). 340 """ 341 try: 342 return x.decode(try_encoding) 343 except (AttributeError, UnicodeDecodeError): 344 return bytestostr(x) 345 346 347_RaiseKeyError = object() 348 349class typedict(dict): 350 __slots__ = ("warn", ) # no __dict__ - that would be redundant 351 @staticmethod # because this doesn't make sense as a global function. 352 def _process_args(mapping=(), **kwargs): 353 if hasattr(mapping, "items"): 354 mapping = getattr(mapping, "items")() 355 return ((bytestostr(k), v) for k, v in chain(mapping, getattr(kwargs, "items")())) 356 def __init__(self, mapping=(), **kwargs): 357 super().__init__(self._process_args(mapping, **kwargs)) 358 self.warn = self._warn 359 def __getitem__(self, k): 360 return super().__getitem__(bytestostr(k)) 361 def __setitem__(self, k, v): 362 return super().__setitem__(bytestostr(k), v) 363 def __delitem__(self, k): 364 return super().__delitem__(bytestostr(k)) 365 def get(self, k, default=None): 366 return super().get(bytestostr(k), default) 367 def setdefault(self, k, default=None): 368 return super().setdefault(bytestostr(k), default) 369 def pop(self, k, v=_RaiseKeyError): 370 if v is _RaiseKeyError: 371 return super().pop(bytestostr(k)) 372 return super().pop(bytestostr(k), v) 373 def update(self, mapping=(), **kwargs): 374 super().update(self._process_args(mapping, **kwargs)) 375 def __contains__(self, k): 376 return super().__contains__(bytestostr(k)) 377 @classmethod 378 def fromkeys(cls, keys, v=None): 379 return super().fromkeys((bytestostr(k) for k in keys), v) 380 def __repr__(self): 381 return '{0}({1})'.format(type(self).__name__, super().__repr__()) 382 383 def _warn(self, msg, *args): 384 get_util_logger().warn(msg, *args) 385 386 def conv_get(self, k, default=None, conv=None): 387 if not super().__contains__(bytestostr(k)): 388 return default 389 v = self.get(k, default) 390 try: 391 return conv(v) 392 except (TypeError, ValueError, AssertionError) as e: 393 self._warn("Warning: failed to convert %s using %s: %s", type(v), conv, e) 394 return default 395 396 def uget(self, k, default=None): 397 return self.conv_get(k, default, u) 398 399 def strget(self, k, default=None): 400 return self.conv_get(k, default, bytestostr) 401 402 def bytesget(self, k : str, default=None): 403 return self.conv_get(k, default, strtobytes) 404 405 def intget(self, k : str, default=0): 406 return self.conv_get(k, default, int) 407 408 def boolget(self, k : str, default=False): 409 return self.conv_get(k, default, bool) 410 411 def dictget(self, k : str, default=None): 412 def checkdict(v): 413 assert isinstance(v, dict) 414 return v 415 return self.conv_get(k, default, checkdict) 416 417 def intpair(self, k : str, default_value=None): 418 v = self.inttupleget(k, default_value) 419 if v is None: 420 return default_value 421 if len(v)!=2: 422 #"%s is not a pair of numbers: %s" % (k, len(v)) 423 return default_value 424 try: 425 return int(v[0]), int(v[1]) 426 except ValueError: 427 return default_value 428 429 def strtupleget(self, k : str, default_value=(), min_items=None, max_items=None): 430 return self.tupleget(k, default_value, str, min_items, max_items) 431 432 def inttupleget(self, k : str, default_value=(), min_items=None, max_items=None): 433 return self.tupleget(k, default_value, int, min_items, max_items) 434 435 def tupleget(self, k : str, default_value=(), item_type=None, min_items=None, max_items=None): 436 v = self._listget(k, default_value, item_type, min_items, max_items) 437 if isinstance(v, list): 438 v = tuple(v) 439 return v 440 441 def _listget(self, k : str, default_value, item_type=None, min_items=None, max_items=None): 442 v = self.get(k) 443 if v is None: 444 return default_value 445 if not isinstance(v, (list, tuple)): 446 self._warn("listget%s", (k, default_value, item_type, max_items)) 447 self._warn("expected a list or tuple value for %s but got %s", k, type(v)) 448 return default_value 449 if min_items is not None: 450 if len(v)<min_items: 451 self._warn("too few items in %s %s: minimum %s allowed, but got %s", type(v), k, max_items, len(v)) 452 return default_value 453 if max_items is not None: 454 if len(v)>max_items: 455 self._warn("too many items in %s %s: maximum %s allowed, but got %s", type(v), k, max_items, len(v)) 456 return default_value 457 aslist = list(v) 458 if item_type: 459 for i, x in enumerate(aslist): 460 if isinstance(x, bytes) and item_type==str: 461 x = bytestostr(x) 462 aslist[i] = x 463 elif isinstance(x, str) and item_type==str: 464 x = str(x) 465 aslist[i] = x 466 if not isinstance(x, item_type): 467 if callable(item_type): 468 try: 469 return item_type(x) 470 except Exception: 471 self._warn("invalid item type for %s %s: %s cannot be used with %s", 472 type(v), k, item_type, type(x)) 473 return default_value 474 self._warn("invalid item type for %s %s: expected %s but got %s", 475 type(v), k, item_type, type(x)) 476 return default_value 477 return aslist 478 479 480def parse_scaling_value(v): 481 if not v: 482 return None 483 if v.endswith("%"): 484 return float(v[:1]).as_integer_ratio() 485 values = v.replace("/", ":").replace(",", ":").split(":", 1) 486 values = [int(x) for x in values] 487 for x in values: 488 assert x>0, "invalid scaling value %s" % x 489 if len(values)==1: 490 ret = 1, values[0] 491 else: 492 assert values[0]<=values[1], "cannot upscale" 493 ret = values[0], values[1] 494 return ret 495 496def from0to100(v): 497 return intrangevalidator(v, 0, 100) 498 499def intrangevalidator(v, min_value=None, max_value=None): 500 v = int(v) 501 if min_value is not None and v<min_value: 502 raise ValueError("value must be greater than %i" % min_value) 503 if max_value is not None and v>max_value: 504 raise ValueError("value must be lower than %i" % max_value) 505 return v 506 507 508def log_screen_sizes(root_w, root_h, sizes): 509 try: 510 do_log_screen_sizes(root_w, root_h, sizes) 511 except Exception as e: 512 get_util_logger().warn("failed to parse screen size information: %s", e, exc_info=True) 513 514def prettify_plug_name(s, default=""): 515 if not s: 516 return default 517 try: 518 s = s.decode("utf8") 519 except (AttributeError, UnicodeDecodeError): 520 pass 521 #prettify strings on win32 522 s = re.sub(r"[0-9\.]*\\", "-", s).lstrip("-") 523 if s.startswith("WinSta-"): 524 s = s[len("WinSta-"):] 525 if s.startswith("(Standard monitor types) "): 526 s = s[len("(Standard monitor types) "):] 527 if s=="0": 528 s = default 529 return s 530 531def do_log_screen_sizes(root_w, root_h, sizes): 532 from xpra.log import Logger 533 log = Logger("screen") 534 #old format, used by some clients (android): 535 if not isinstance(sizes, (tuple, list)): 536 return 537 if any(True for x in sizes if not isinstance(x, (tuple, list))): 538 return 539 def dpi(size_pixels, size_mm): 540 if size_mm==0: 541 return 0 542 return round(size_pixels * 254 / size_mm / 10) 543 def add_workarea(info, wx, wy, ww, wh): 544 info.append("workarea: %4ix%-4i" % (ww, wh)) 545 if wx!=0 or wy!=0: 546 #log position if not (0, 0) 547 info.append("at %4ix%-4i" % (wx, wy)) 548 for s in sizes: 549 if len(s)<10: 550 log.info(" %s", s) 551 continue 552 #more detailed output: 553 display_name, width, height, width_mm, height_mm, \ 554 monitors, work_x, work_y, work_width, work_height = s[:10] 555 #always log plug name: 556 info = ["%s" % prettify_plug_name(display_name)] 557 if width!=root_w or height!=root_h: 558 #log plug dimensions if not the same as display (root): 559 info.append("%ix%i" % (width, height)) 560 sdpix = dpi(width, width_mm) 561 sdpiy = dpi(height, height_mm) 562 info.append("(%ix%i mm - DPI: %ix%i)" % (width_mm, height_mm, sdpix, sdpiy)) 563 564 if work_width!=width or work_height!=height or work_x!=0 or work_y!=0: 565 add_workarea(info, work_x, work_y, work_width, work_height) 566 log.info(" "+" ".join(info)) 567 #sort monitors from left to right, top to bottom: 568 monitors_distances = [] 569 for m in monitors: 570 plug_x, plug_y = m[1:3] 571 monitors_distances.append((plug_x+plug_y*width, m)) 572 sorted_monitors = [x[1] for x in sorted(monitors_distances)] 573 for i, m in enumerate(sorted_monitors, start=1): 574 if len(m)<7: 575 log.info(" %s", m) 576 continue 577 plug_name, plug_x, plug_y, plug_width, plug_height, plug_width_mm, plug_height_mm = m[:7] 578 default_name = "monitor %i" % i 579 info = ['%-16s' % prettify_plug_name(plug_name, default_name)] 580 if plug_width!=width or plug_height!=height or plug_x!=0 or plug_y!=0: 581 info.append("%4ix%-4i" % (plug_width, plug_height)) 582 if plug_x!=0 or plug_y!=0 or len(sorted_monitors)>1: 583 info.append("at %4ix%-4i" % (plug_x, plug_y)) 584 if (plug_width_mm!=width_mm or plug_height_mm!=height_mm) and (plug_width_mm>0 or plug_height_mm>0): 585 dpix = dpi(plug_width, plug_width_mm) 586 dpiy = dpi(plug_height, plug_height_mm) 587 dpistr = "" 588 if sdpix!=dpix or sdpiy!=dpiy or len(sorted_monitors)>1: 589 dpistr = " - DPI: %ix%i" % (dpix, dpiy) 590 info.append("(%3ix%-3i mm%s)" % (plug_width_mm, plug_height_mm, dpistr)) 591 if len(m)>=11: 592 dwork_x, dwork_y, dwork_width, dwork_height = m[7:11] 593 #only show it again if different from the screen workarea 594 if dwork_x!=work_x or dwork_y!=work_y or dwork_width!=work_width or dwork_height!=work_height: 595 add_workarea(info, dwork_x, dwork_y, dwork_width, dwork_height) 596 istr = (" ".join(info)).rstrip(" ") 597 if len(monitors)==1 and istr.lower() in ("unknown unknown", "0", "1", default_name, "screen", "monitor"): 598 #a single monitor with no real name, 599 #so don't bother showing it: 600 continue 601 log.info(" "+istr) 602 603def get_screen_info(screen_sizes): 604 #same format as above 605 if not screen_sizes: 606 return {} 607 info = { 608 "screens" : len(screen_sizes) 609 } 610 for i, x in enumerate(screen_sizes): 611 if not isinstance(x, (tuple, list)): 612 continue 613 sinfo = info.setdefault("screen", {}).setdefault(i, {}) 614 sinfo["display"] = x[0] 615 if len(x)>=3: 616 sinfo["size"] = x[1], x[2] 617 if len(x)>=5: 618 sinfo["size_mm"] = x[3], x[4] 619 if len(x)>=6: 620 monitors = x[5] 621 for j, monitor in enumerate(monitors): 622 if len(monitor)>=7: 623 minfo = sinfo.setdefault("monitor", {}).setdefault(j, {}) 624 for k,v in { 625 "name" : monitor[0], 626 "geometry" : monitor[1:5], 627 "size_mm" : monitor[5:7], 628 }.items(): 629 minfo[k] = v 630 if len(x)>=10: 631 sinfo["workarea"] = x[6:10] 632 return info 633 634def dump_all_frames(logger=None): 635 try: 636 frames = sys._current_frames() #pylint: disable=protected-access 637 except AttributeError: 638 return 639 else: 640 dump_frames(frames.items(), logger) 641 642def dump_gc_frames(logger=None): 643 import gc 644 #import types 645 import inspect 646 gc.collect() 647 #frames = tuple(x for x in gc.get_objects() if isinstance(x, types.FrameType)) 648 frames = tuple((None, x) for x in gc.get_objects() if inspect.isframe(x)) 649 dump_frames(frames, logger) 650 651def dump_frames(frames, logger=None): 652 if not logger: 653 logger = get_util_logger() 654 logger("found %s frames:", len(frames)) 655 for i,(fid,frame) in enumerate(frames): 656 fidstr = "" 657 if fid is not None: 658 try: 659 fidstr = hex(fid) 660 except TypeError: 661 fidstr = str(fid) 662 logger("%i: %s %s:", i, fidstr, frame) 663 for x in traceback.format_stack(frame): 664 for l in x.splitlines(): 665 logger("%s", l) 666 667 668def detect_leaks(): 669 import tracemalloc 670 tracemalloc.start() 671 last_snapshot = [tracemalloc.take_snapshot()] 672 def print_leaks(): 673 s1 = last_snapshot[0] 674 s2 = tracemalloc.take_snapshot() 675 last_snapshot[0] = s2 676 top_stats = s2.compare_to(s1, 'lineno') 677 print("[ Top 20 differences ]") 678 for stat in top_stats[:20]: 679 print(stat) 680 for i, stat in enumerate(top_stats[:20]): 681 print() 682 print("top %i:" % i) 683 print("%s memory blocks: %.1f KiB" % (stat.count, stat.size / 1024)) 684 for line in stat.traceback.format(): 685 print(line) 686 return True 687 return print_leaks 688 689def start_mem_watcher(ms): 690 from xpra.make_thread import start_thread 691 start_thread(mem_watcher, name="mem-watcher", daemon=True, args=(ms,)) 692 693def mem_watcher(ms, pid=os.getpid()): 694 import time 695 import psutil 696 process = psutil.Process(pid) 697 while True: 698 mem = process.memory_full_info() 699 #get_util_logger().info("memory usage: %s", mem.mem//1024//1024) 700 get_util_logger().info("memory usage for %s: %s", pid, mem) 701 time.sleep(ms/1000.0) 702 703def log_mem_info(prefix="memory usage: ", pid=os.getpid()): 704 import psutil 705 process = psutil.Process(pid) 706 mem = process.memory_full_info() 707 print("%i %s%s" % (pid, prefix, mem)) 708 709 710class ellipsizer: 711 __slots__ = ("obj", "limit") 712 def __init__(self, obj, limit=100): 713 self.obj = obj 714 self.limit = limit 715 def __str__(self): 716 if self.obj is None: 717 return "None" 718 return repr_ellipsized(self.obj, self.limit) 719 def __repr__(self): 720 if self.obj is None: 721 return "None" 722 return repr_ellipsized(self.obj, self.limit) 723 724def repr_ellipsized(obj, limit=100): 725 if isinstance(obj, str): 726 if len(obj)>limit>6: 727 return nonl(obj[:limit//2-2]+" .. "+obj[2-limit//2:]) 728 return nonl(obj) 729 if isinstance(obj, memoryview): 730 obj = obj.tobytes() 731 if isinstance(obj, bytes): 732 try: 733 s = nonl(repr(obj)) 734 except Exception: 735 s = binascii.hexlify(obj).decode() 736 if len(s)>limit>6: 737 return nonl(s[:limit//2-2]+" .. "+s[2-limit//2:]) 738 return s 739 return repr_ellipsized(repr(obj), limit) 740 741 742def rindex(alist, avalue): 743 return len(alist) - alist[::-1].index(avalue) - 1 744 745 746def notypedict(d): 747 for k in list(d.keys()): 748 v = d[k] 749 if isinstance(v, dict): 750 d[k] = notypedict(v) 751 return dict(d) 752 753def flatten_dict(info, sep="."): 754 to = {} 755 _flatten_dict(to, sep, None, info) 756 return to 757 758def _flatten_dict(to, sep, path, d): 759 for k,v in d.items(): 760 if path: 761 if k: 762 npath = path+sep+bytestostr(k) 763 else: 764 npath = path 765 else: 766 npath = bytestostr(k) 767 if isinstance(v, dict): 768 _flatten_dict(to, sep, npath, v) 769 elif v is not None: 770 to[npath] = v 771 772def parse_simple_dict(s="", sep=","): 773 #parse the options string and add the pairs: 774 d = {} 775 for el in s.split(sep): 776 if not el: 777 continue 778 try: 779 k, v = el.split("=", 1) 780 cur = d.get(k) 781 if cur: 782 if not isinstance(cur, list): 783 cur = [cur] 784 cur.append(v) 785 v = cur 786 d[k] = v 787 except Exception as e: 788 log = get_util_logger() 789 log.warn("Warning: failed to parse dictionary option '%s':", s) 790 log.warn(" %s", e) 791 return d 792 793#used for merging dicts with a prefix and suffix 794#non-None values get added to <todict> with a prefix and optional suffix 795def updict(todict, prefix, d, suffix="", flatten_dicts=False): 796 if not d: 797 return todict 798 for k,v in d.items(): 799 if v is not None: 800 if k: 801 k = prefix+"."+str(k) 802 else: 803 k = prefix 804 if suffix: 805 k = k+"."+suffix 806 if flatten_dicts and isinstance(v, dict): 807 updict(todict, k, v) 808 else: 809 todict[k] = v 810 return todict 811 812def pver(v, numsep=".", strsep=", "): 813 #print for lists with version numbers, or CSV strings 814 if isinstance(v, (list, tuple)): 815 types = list(set(type(x) for x in v)) 816 if len(types)==1: 817 if types[0]==int: 818 return numsep.join(str(x) for x in v) 819 if types[0]==str: 820 return strsep.join(str(x) for x in v) 821 if types[0]==bytes: 822 def s(x): 823 try: 824 return x.decode("utf8") 825 except UnicodeDecodeError: 826 return bytestostr(x) 827 return strsep.join(s(x) for x in v) 828 return bytestostr(v) 829 830def sorted_nicely(l): 831 """ Sort the given iterable in the way that humans expect.""" 832 def convert(text): 833 if text.isdigit(): 834 return int(text) 835 return text 836 alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', bytestostr(key))] 837 return sorted(l, key = alphanum_key) 838 839def print_nested_dict(d, prefix="", lchar="*", pad=32, vformat=None, print_fn=None, 840 version_keys=("version", "revision"), hex_keys=("data", )): 841 #"smart" value formatting function: 842 def sprint(arg): 843 if print_fn: 844 print_fn(arg) 845 else: 846 print(arg) 847 def vf(k, v): 848 if vformat: 849 fmt = vformat 850 if isinstance(vformat, dict): 851 fmt = vformat.get(k) 852 if fmt is not None: 853 return nonl(fmt(v)) 854 try: 855 if any(k.find(x)>=0 for x in version_keys): 856 return nonl(pver(v)).lstrip("v") 857 if any(k.find(x)>=0 for x in hex_keys): 858 return binascii.hexlify(v) 859 except Exception: 860 pass 861 return nonl(pver(v, ", ", ", ")) 862 l = pad-len(prefix)-len(lchar) 863 for k in sorted_nicely(d.keys()): 864 v = d[k] 865 if isinstance(v, dict): 866 nokey = v.get("", (v.get(None))) 867 if nokey is not None: 868 sprint("%s%s %s : %s" % (prefix, lchar, bytestostr(k).ljust(l), vf(k, nokey))) 869 for x in ("", None): 870 v.pop(x, None) 871 else: 872 sprint("%s%s %s" % (prefix, lchar, bytestostr(k))) 873 print_nested_dict(v, prefix+" ", "-", vformat=vformat, print_fn=print_fn, 874 version_keys=version_keys, hex_keys=hex_keys) 875 else: 876 sprint("%s%s %s : %s" % (prefix, lchar, bytestostr(k).ljust(l), vf(k, v))) 877 878def reverse_dict(d): 879 reversed_d = {} 880 for k,v in d.items(): 881 reversed_d[v] = k 882 return reversed_d 883 884 885def std(s, extras="-,./: "): 886 s = s or "" 887 try: 888 s = s.decode("latin1") 889 except Exception: 890 pass 891 def c(v): 892 try: 893 return chr(v) 894 except Exception: 895 return str(v) 896 def f(v): 897 return str.isalnum(c(v)) or v in extras 898 return "".join(filter(f, s)) 899 900def alnum(s): 901 try: 902 s = s.encode("latin1") 903 except Exception: 904 pass 905 def c(v): 906 try: 907 return chr(v) 908 except Exception: 909 return str(v) 910 def f(v): 911 return str.isalnum(c(v)) 912 return "".join(c(v) for v in filter(f, s)) 913 914def nonl(x): 915 if x is None: 916 return None 917 return str(x).replace("\n", "\\n").replace("\r", "\\r") 918 919def engs(v): 920 if isinstance(v, int): 921 l = v 922 else: 923 try: 924 l = len(v) 925 except TypeError: 926 return "" 927 return "s" if l!=1 else "" 928 929 930def obsc(v): 931 OBSCURE_PASSWORDS = envbool("XPRA_OBSCURE_PASSWORDS", True) 932 if OBSCURE_PASSWORDS: 933 return "".join("*" for _ in (v or "")) 934 return v 935 936 937def csv(v): 938 try: 939 return ", ".join(str(x) for x in v) 940 except Exception: 941 return str(v) 942 943 944def unsetenv(*varnames): 945 for x in varnames: 946 os.environ.pop(x, None) 947 948def envint(name : str, d=0): 949 try: 950 return int(os.environ.get(name, d)) 951 except ValueError: 952 return d 953 954def envbool(name : str, d=False): 955 try: 956 v = os.environ.get(name, "").lower() 957 if v is None: 958 return d 959 if v in ("yes", "true", "on"): 960 return True 961 if v in ("no", "false", "off"): 962 return False 963 return bool(int(v)) 964 except ValueError: 965 return d 966 967def envfloat(name : str, d=0): 968 try: 969 return float(os.environ.get(name, d)) 970 except ValueError: 971 return d 972 973 974#give warning message just once per key then ignore: 975_once_only = set() 976def first_time(key): 977 global _once_only 978 if key not in _once_only: 979 _once_only.add(key) 980 return True 981 return False 982