Hide keyboard shortcuts

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. 

6 

7import binascii 

8import traceback 

9import threading 

10import sys 

11import os 

12import re 

13 

14 

15XPRA_APP_ID = 0 

16 

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("-","")) 

23 

24 

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 

41 

42 

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" 

69 

70 

71DEFAULT_PORT = 14500 

72 

73DEFAULT_PORTS = { 

74 "ws" : 80, 

75 "wss" : 443, 

76 "ssh" : 22, 

77 "tcp" : DEFAULT_PORT, 

78 "udp" : DEFAULT_PORT, 

79 } 

80 

81 

82#magic value for "workspace" window property, means unset 

83WORKSPACE_UNSET = 65535 

84WORKSPACE_ALL = 0xffffffff 

85 

86WORKSPACE_NAMES = { 

87 WORKSPACE_UNSET : "unset", 

88 WORKSPACE_ALL : "all", 

89 } 

90 

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") 

99 

100 

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 } 

136 

137 

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 

145 

146 

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) 

150 

151 

152def dump_exc(): 

153 """Call this from a except: clause to print a nice traceback.""" 

154 print("".join(traceback.format_exception(*sys.exc_info()))) 

155 

156def noerr(fn, *args): 

157 try: 

158 return fn(*args) 

159 except Exception: 

160 return None 

161 

162 

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__)) 

169 

170 

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))] 

175 

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 

192 

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 

209 

210 

211def roundup(n, m): 

212 return (n + m - 1) & ~(m - 1) 

213 

214 

215class AtomicInteger: 

216 def __init__(self, integer = 0): 

217 self.counter = integer 

218 self.lock = threading.RLock() 

219 

220 def increase(self, inc = 1): 

221 with self.lock: 

222 self.counter = self.counter + inc 

223 return self.counter 

224 

225 def decrease(self, dec = 1): 

226 with self.lock: 

227 self.counter = self.counter - dec 

228 return self.counter 

229 

230 def get(self): 

231 return self.counter 

232 

233 def __str__(self): 

234 return str(self.counter) 

235 

236 def __repr__(self): 

237 return "AtomicInteger(%s)" % self.counter 

238 

239 

240 def __int__(self): 

241 return self.counter 

242 

243 def __eq__(self, other): 

244 try: 

245 return self.counter==int(other) 

246 except ValueError: 

247 return -1 

248 

249 def __cmp__(self, other): 

250 try: 

251 return self.counter-int(other) 

252 except ValueError: 

253 return -1 

254 

255 

256class MutableInteger(object): 

257 def __init__(self, integer = 0): 

258 self.counter = integer 

259 

260 def increase(self, inc = 1): 

261 self.counter = self.counter + inc 

262 return self.counter 

263 

264 def decrease(self, dec = 1): 

265 self.counter = self.counter - dec 

266 return self.counter 

267 

268 def get(self): 

269 return self.counter 

270 

271 def __str__(self): 

272 return str(self.counter) 

273 

274 def __repr__(self): 

275 return "MutableInteger(%s)" % self.counter 

276 

277 

278 def __int__(self): 

279 return self.counter 

280 

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) 

295 

296 

297class typedict(dict): 

298 

299 def _warn(self, msg, *args, **kwargs): 

300 get_util_logger().warn(msg, *args, **kwargs) 

301 

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 

310 

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) 

317 

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) 

324 

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 

336 

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) 

342 

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 

352 

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 

364 

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) 

367 

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) 

370 

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 

376 

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 

407 

408 

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 

424 

425def from0to100(v): 

426 return intrangevalidator(v, 0, 100) 

427 

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 

435 

436 

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) 

442 

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 

459 

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)) 

491 

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)) 

522 

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 

553 

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) 

561 

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) 

570 

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) 

586 

587 

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 

608 

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,)) 

612 

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) 

622 

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)) 

628 

629 

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) 

642 

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) 

657 

658 

659def rindex(alist, avalue): 

660 return len(alist) - alist[::-1].index(avalue) - 1 

661 

662def iround(v): 

663 return int(v+0.5) 

664 

665 

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) 

672 

673def flatten_dict(info, sep="."): 

674 to = {} 

675 _flatten_dict(to, sep, None, info) 

676 return to 

677 

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 

692 

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 

713 

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 

732 

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) 

751 

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) 

761 

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))) 

801 

802def reverse_dict(d): 

803 reversed_d = {} 

804 for k,v in d.items(): 

805 reversed_d[v] = k 

806 return reversed_d 

807 

808 

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)) 

823 

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)) 

837 

838def nonl(x): 

839 if x is None: 

840 return None 

841 return str(x).replace("\n", "\\n").replace("\r", "\\r") 

842 

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 "" 

852 

853 

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 

859 

860 

861def csv(v): 

862 try: 

863 return ", ".join(str(x) for x in v) 

864 except Exception: 

865 return str(v) 

866 

867 

868def unsetenv(*varnames): 

869 for x in varnames: 

870 os.environ.pop(x, None) 

871 

872def envint(name : str, d=0): 

873 try: 

874 return int(os.environ.get(name, d)) 

875 except ValueError: 

876 return d 

877 

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 

890 

891def envfloat(name : str, d=0): 

892 try: 

893 return float(os.environ.get(name, d)) 

894 except ValueError: 

895 return d 

896 

897 

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