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