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#!/usr/bin/env python 

2# This file is part of Xpra. 

3# Copyright (C) 2010-2020 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 sys 

8import os 

9 

10from xpra.util import csv 

11from xpra.os_util import ( 

12 WIN32, OSX, POSIX, 

13 osexpand, getuid, getgid, get_username_for_uid, 

14 is_Debian, is_Ubuntu, is_arm, 

15 ) 

16 

17def warn(msg): 

18 sys.stderr.write(msg+"\n") 

19 

20def nodebug(*_args): 

21 #can be overriden 

22 pass 

23debug = nodebug 

24 

25class InitException(Exception): 

26 pass 

27class InitInfo(Exception): 

28 pass 

29class InitExit(Exception): 

30 def __init__(self, status, msg): 

31 self.status = status 

32 super().__init__(msg) 

33 

34 

35DEBUG_CONFIG_PROPERTIES = os.environ.get("XPRA_DEBUG_CONFIG_PROPERTIES", "").split() 

36 

37DEFAULT_XPRA_CONF_FILENAME = os.environ.get("XPRA_CONF_FILENAME", 'xpra.conf') 

38DEFAULT_NET_WM_NAME = os.environ.get("XPRA_NET_WM_NAME", "Xpra") 

39 

40if POSIX: 

41 DEFAULT_POSTSCRIPT_PRINTER = os.environ.get("XPRA_POSTSCRIPT_PRINTER", "drv:///sample.drv/generic.ppd") 

42else: # pragma: no cover 

43 DEFAULT_POSTSCRIPT_PRINTER = "" 

44DEFAULT_PULSEAUDIO = None #auto 

45if OSX or WIN32: # pragma: no cover 

46 DEFAULT_PULSEAUDIO = False 

47 

48 

49_has_sound_support = None 

50def has_sound_support(): 

51 global _has_sound_support 

52 if _has_sound_support is None: 

53 try: 

54 import xpra.sound 

55 _has_sound_support = bool(xpra.sound) 

56 except ImportError: 

57 _has_sound_support = False 

58 return _has_sound_support 

59 

60 

61def get_xorg_bin(): 

62 xorg = os.environ.get("XPRA_XORG_BIN") 

63 if xorg: 

64 return xorg 

65 # Detect Xorg Binary 

66 if is_arm() and is_Debian() and os.path.exists("/usr/bin/Xorg"): 

67 #Raspbian breaks if we use a different binary.. 

68 return "/usr/bin/Xorg" 

69 for p in ( 

70 "/usr/libexec/Xorg", #fedora 22+ 

71 "/usr/lib/xorg/Xorg", #ubuntu 16.10 

72 "/usr/lib/xorg-server/Xorg", #arch linux 

73 "/usr/lib/Xorg", #arch linux (new 2019) 

74 "/usr/X11/bin/X", #OSX 

75 ): 

76 if os.path.exists(p): 

77 return p 

78 #look for it in $PATH: 

79 for x in os.environ.get("PATH").split(os.pathsep): # pragma: no cover 

80 xorg = os.path.join(x, "Xorg") 

81 if os.path.isfile(xorg): 

82 return xorg 

83 return None 

84 

85 

86def get_Xdummy_confdir(): 

87 from xpra.platform.xposix.paths import get_runtime_dir 

88 xrd = get_runtime_dir() 

89 if xrd: 

90 base = "${XDG_RUNTIME_DIR}/xpra" 

91 else: # pragma: no cover 

92 base = "${HOME}/.xpra" 

93 return base+"/xorg.conf.d/$PID" 

94 

95def get_Xdummy_command(xorg_cmd="Xorg", log_dir="${XPRA_LOG_DIR}", xorg_conf="/etc/xpra/xorg.conf"): 

96 cmd = [xorg_cmd] #ie: ["Xorg"] or ["xpra_Xdummy"] or ["./install/bin/xpra_Xdummy"] 

97 cmd += [ 

98 "-noreset", "-novtswitch", 

99 "-nolisten", "tcp", 

100 "+extension", "GLX", 

101 "+extension", "RANDR", 

102 "+extension", "RENDER", 

103 "-auth", "$XAUTHORITY", 

104 "-logfile", "%s/Xorg.${DISPLAY}.log" % log_dir, 

105 #must be specified with some Xorg versions (ie: arch linux) 

106 #this directory can store xorg config files, it does not need to be created: 

107 "-configdir", get_Xdummy_confdir(), 

108 "-config", xorg_conf 

109 ] 

110 return cmd 

111 

112def get_Xvfb_command(width=8192, height=4096, dpi=96): 

113 cmd = ["Xvfb", 

114 "+extension", "GLX", 

115 "+extension", "Composite", 

116 "-screen", "0", "%ix%ix24+32" % (width, height), 

117 #better than leaving to vfb after a resize? 

118 "-nolisten", "tcp", 

119 "-noreset", 

120 "-auth", "$XAUTHORITY" 

121 ] 

122 if dpi>0: 

123 cmd += ["-dpi", str(dpi)] 

124 return cmd 

125 

126def detect_xvfb_command(conf_dir="/etc/xpra/", bin_dir=None, 

127 Xdummy_ENABLED=None, Xdummy_wrapper_ENABLED=None, warn=warn): 

128 #returns the xvfb command to use 

129 if WIN32: # pragma: no cover 

130 return "" 

131 if OSX: # pragma: no cover 

132 return get_Xvfb_command() 

133 if sys.platform.find("bsd")>=0 and Xdummy_ENABLED is None: # pragma: no cover 

134 if warn: 

135 warn("Warning: sorry, no support for Xdummy on %s" % sys.platform) 

136 return get_Xvfb_command() 

137 if is_arm(): 

138 #arm struggles to launch Xdummy, so use Xvfb: 

139 return get_Xvfb_command() 

140 if is_Ubuntu() or is_Debian(): 

141 #These distros do weird things and this can cause the real X11 server to crash 

142 #see ticket #2834 

143 return get_Xvfb_command() 

144 

145 xorg_bin = get_xorg_bin() 

146 def Xorg_suid_check(): 

147 if Xdummy_wrapper_ENABLED is not None: 

148 #honour what was specified: 

149 use_wrapper = Xdummy_wrapper_ENABLED 

150 elif not xorg_bin: 

151 if warn: 

152 warn("Warning: Xorg binary not found, assuming the wrapper is needed!") 

153 use_wrapper = True 

154 else: 

155 #auto-detect 

156 import stat 

157 xorg_stat = os.stat(xorg_bin) 

158 if (xorg_stat.st_mode & stat.S_ISUID)!=0: 

159 if (xorg_stat.st_mode & stat.S_IROTH)==0: 

160 if warn: 

161 warn("%s is suid and not readable, Xdummy support unavailable" % xorg_bin) 

162 return get_Xvfb_command() 

163 debug("%s is suid and readable, using the xpra_Xdummy wrapper" % xorg_bin) 

164 use_wrapper = True 

165 else: 

166 use_wrapper = False 

167 xorg_conf = os.path.join(conf_dir, "xorg.conf") 

168 if use_wrapper: 

169 xorg_cmd = "xpra_Xdummy" 

170 else: 

171 xorg_cmd = xorg_bin or "Xorg" 

172 #so we can run from install dir: 

173 if bin_dir and os.path.exists(os.path.join(bin_dir, xorg_cmd)): 

174 if bin_dir not in os.environ.get("PATH", "/bin:/usr/bin:/usr/local/bin").split(os.pathsep): 

175 xorg_cmd = os.path.join(bin_dir, xorg_cmd) 

176 return get_Xdummy_command(xorg_cmd, xorg_conf=xorg_conf) 

177 

178 if Xdummy_ENABLED is False: 

179 return get_Xvfb_command() 

180 if Xdummy_ENABLED is True: 

181 return Xorg_suid_check() 

182 debug("Xdummy support unspecified, will try to detect") 

183 return Xorg_suid_check() 

184 

185 

186def xvfb_cmd_str(xvfb): 

187 xvfb_str = "" 

188 while xvfb: 

189 s = "" 

190 while xvfb: 

191 item = xvfb[0] 

192 l = len(item) 

193 if (item.startswith("-") or item.startswith("+")) and len(xvfb)>1: 

194 l += len(xvfb[1]) 

195 if s and len(s)+l>55: 

196 break 

197 v = xvfb.pop(0) 

198 if not s: 

199 s += v 

200 else: 

201 s += " "+v 

202 if xvfb_str: 

203 xvfb_str += " \\\n " 

204 xvfb_str += s 

205 return xvfb_str 

206 

207 

208def OpenGL_safety_check() -> str: 

209 return is_VirtualBox() 

210 

211def is_VirtualBox() -> str: 

212 #try to detect VirtualBox: 

213 #based on the code found here: 

214 #http://spth.virii.lu/eof2/articles/WarGame/vboxdetect.html 

215 #because it used to cause hard VM crashes when we probe the GL driver! 

216 if WIN32: # pragma: no cover 

217 try: 

218 from ctypes import cdll 

219 if cdll.LoadLibrary("VBoxHook.dll"): 

220 return "VirtualBox is present (VBoxHook.dll)" 

221 except (ImportError, OSError): 

222 pass 

223 try: 

224 try: 

225 f = None 

226 f = open("\\\\.\\VBoxMiniRdrDN", "r") 

227 return "VirtualBox is present (VBoxMiniRdrDN)" 

228 finally: 

229 if f: 

230 f.close() 

231 except Exception as e: 

232 import errno 

233 if e.args[0]==errno.EACCES: 

234 return "VirtualBox is present (VBoxMiniRdrDN)" 

235 return None 

236 

237 

238def get_build_info(): 

239 info = [] 

240 try: 

241 from xpra.src_info import REVISION, LOCAL_MODIFICATIONS, BRANCH, COMMIT #@UnresolvedImport 

242 info.append("revision %s" % REVISION) 

243 if COMMIT: 

244 info.append("commit %s from %s branch" % (COMMIT, BRANCH)) 

245 try: 

246 mods = int(LOCAL_MODIFICATIONS) 

247 info.append("with %s local changes" % (mods)) 

248 except ValueError: 

249 pass 

250 except Exception as e: 

251 warn("Error: could not find the source information: %s" % e) 

252 try: 

253 from xpra.build_info import ( 

254 BUILD_DATE, BUILD_TIME, BUILD_BIT, 

255 CYTHON_VERSION, COMPILER_VERSION, 

256 ) 

257 info.insert(0, "") 

258 einfo = "Python %i.%i" % sys.version_info[:2] 

259 if BUILD_BIT: 

260 einfo += ", "+BUILD_BIT 

261 info.insert(0, einfo) 

262 try: 

263 from xpra.build_info import BUILT_BY, BUILT_ON 

264 info.append("built on %s by %s" % (BUILT_ON, BUILT_BY)) 

265 except ImportError: 

266 #reproducible builds dropped this info 

267 pass 

268 if BUILD_DATE and BUILD_TIME: 

269 info.append("%s %s" % (BUILD_DATE, BUILD_TIME)) 

270 if CYTHON_VERSION!="unknown" or COMPILER_VERSION!="unknown": 

271 info.append("") 

272 if CYTHON_VERSION!="unknown": 

273 info.append("using Cython %s" % CYTHON_VERSION) 

274 if COMPILER_VERSION!="unknown": 

275 cv = COMPILER_VERSION.replace("Optimizing Compiler Version", "Optimizing Compiler\nVersion") 

276 info += cv.splitlines() 

277 except Exception as e: 

278 warn("Error: could not find the build information: %s" % e) 

279 return info 

280 

281 

282def name_to_field(name): 

283 return name.replace("-", "_") 

284 

285def save_config(conf_file, config, keys, extras_types=None): 

286 with open(conf_file, "w") as f: 

287 option_types = OPTION_TYPES.copy() 

288 if extras_types: 

289 option_types.update(extras_types) 

290 saved = {} 

291 for key in keys: 

292 assert key in option_types, "invalid configuration key: %s" % key 

293 v = getattr(config, name_to_field(key)) 

294 saved[key] = v 

295 f.write("%s=%s%s" % (key, v, os.linesep)) 

296 debug("save_config: saved %s to %s", saved, conf_file) 

297 

298def read_config(conf_file): 

299 """ 

300 Parses a config file into a dict of strings. 

301 If the same key is specified more than once, 

302 the value for this key will be an array of strings. 

303 """ 

304 d = {} 

305 if not os.path.isfile(conf_file): 

306 debug("read_config(%s) is not a file!", conf_file) 

307 return d 

308 with open(conf_file, "r") as f: 

309 lines = [] 

310 no = 0 

311 for line in f: 

312 sline = line.strip().rstrip('\r\n').strip() 

313 no += 1 

314 if not sline: 

315 debug("%4s empty line", no) 

316 continue 

317 if sline[0] in ( '!', '#' ): 

318 debug("%4s skipping comments : %s", no, sline[:16]+"..") 

319 continue 

320 debug("%4s loaded : %s", no, sline) 

321 lines.append(sline) 

322 debug("loaded %s lines", len(lines)) 

323 #aggregate any lines with trailing backslash 

324 agg_lines = [] 

325 l = "" 

326 for line in lines: 

327 if line.endswith("\\"): 

328 l += line[:-1]+" " 

329 else: 

330 l += line 

331 agg_lines.append(l) 

332 l = "" 

333 if l: 

334 #last line had a trailing backslash... meh 

335 agg_lines.append(l) 

336 debug("loaded %s aggregated lines", len(agg_lines)) 

337 #parse name=value pairs: 

338 for sline in agg_lines: 

339 if sline.find("=")<=0: 

340 debug("skipping line which is missing an equal sign: %s", sline) 

341 continue 

342 props = sline.split("=", 1) 

343 assert len(props)==2 

344 name = props[0].strip() 

345 value = props[1].strip() 

346 current_value = d.get(name) 

347 if current_value: 

348 if isinstance(current_value, list): 

349 d[name] = current_value + [value] 

350 else: 

351 d[name] = [current_value, value] 

352 debug("added to: %s='%s'", name, d[name]) 

353 else: 

354 debug("assigned (new): %s='%s'", name, value) 

355 d[name] = value 

356 if name in DEBUG_CONFIG_PROPERTIES: 

357 print("%s=%s (was %s), from %s" % (name, d[name], current_value, conf_file)) 

358 return d 

359 

360 

361def conf_files(conf_dir, xpra_conf_filename=DEFAULT_XPRA_CONF_FILENAME): 

362 """ 

363 Returns all the config file paths found in the config directory 

364 ie: ["/etc/xpra/conf.d/15_features.conf", ..., "/etc/xpra/xpra.conf"] 

365 """ 

366 d = [] 

367 cdir = os.path.expanduser(conf_dir) 

368 if not os.path.exists(cdir) or not os.path.isdir(cdir): 

369 debug("invalid config directory: %s", cdir) 

370 return d 

371 #look for conf.d subdirectory: 

372 conf_d_dir = os.path.join(cdir, "conf.d") 

373 if os.path.exists(conf_d_dir) and os.path.isdir(conf_d_dir): 

374 for f in sorted(os.listdir(conf_d_dir)): 

375 if f.endswith(".conf"): 

376 conf_file = os.path.join(conf_d_dir, f) 

377 if os.path.isfile(conf_file): 

378 d.append(conf_file) 

379 conf_file = os.path.join(cdir, xpra_conf_filename) 

380 if not os.path.exists(conf_file) or not os.path.isfile(conf_file): 

381 debug("config file does not exist: %s", conf_file) 

382 else: 

383 d.append(conf_file) 

384 return d 

385 

386def read_xpra_conf(conf_dir, xpra_conf_filename=DEFAULT_XPRA_CONF_FILENAME): 

387 """ 

388 Reads an <xpra_conf_filename> file from the given directory, 

389 returns a dict with values as strings and arrays of strings. 

390 """ 

391 files = conf_files(conf_dir, xpra_conf_filename) 

392 debug("read_xpra_conf(%s,%s) conf files: %s" % (conf_dir, xpra_conf_filename, files)) 

393 d = {} 

394 for f in files: 

395 cd = read_config(f) 

396 debug("config(%s)=%s" % (f, cd)) 

397 d.update(cd) 

398 return d 

399 

400def read_xpra_defaults(username=None, uid=None, gid=None): 

401 """ 

402 Reads the global <xpra_conf_filename> from the <conf_dir> 

403 and then the user-specific one. 

404 (the latter overrides values from the former) 

405 returns a dict with values as strings and arrays of strings. 

406 If the <conf_dir> is not specified, we figure out its location. 

407 """ 

408 dirs = get_xpra_defaults_dirs(username, uid, gid) 

409 defaults = {} 

410 for d in dirs: 

411 defaults.update(read_xpra_conf(d)) 

412 debug("read_xpra_defaults: updated defaults with %s", d) 

413 may_create_user_config() 

414 return defaults 

415 

416def get_xpra_defaults_dirs(username=None, uid=None, gid=None): 

417 from xpra.platform.paths import get_default_conf_dirs, get_system_conf_dirs, get_user_conf_dirs 

418 # load config files in this order (the later ones override earlier ones): 

419 # * application defaults (ie: "/Volumes/Xpra/Xpra.app/Contents/Resources/" on OSX) 

420 # (ie: "C:\Program Files\Xpra\" on win32) 

421 # (ie: None on others) 

422 # * system defaults (ie: "/etc/xpra" on Posix - not on OSX) 

423 # (ie: "/Library/Application Support/Xpra" on OSX) 

424 # (ie: "C:\Documents and Settings\All Users\Application Data\Xpra" with XP) 

425 # (ie: "C:\ProgramData\Xpra" with Vista onwards) 

426 # * user config (ie: "~/.xpra/" on all Posix, including OSX) 

427 # (ie: "C:\Documents and Settings\Username\Application Data\Xpra" with XP) 

428 # (ie: "C:\Users\<user name>\AppData\Roaming" with Visa onwards) 

429 dirs = get_default_conf_dirs() + get_system_conf_dirs() + get_user_conf_dirs(uid) 

430 defaults_dirs = [] 

431 for d in dirs: 

432 if not d: 

433 continue 

434 ad = osexpand(d, actual_username=username, uid=uid, gid=gid) 

435 if not os.path.exists(ad): 

436 debug("read_xpra_defaults: skipping %s", ad) 

437 continue 

438 defaults_dirs.append(ad) 

439 return defaults_dirs 

440 

441def may_create_user_config(xpra_conf_filename=DEFAULT_XPRA_CONF_FILENAME): 

442 from xpra.platform.paths import get_user_conf_dirs 

443 #save a user config template: 

444 udirs = get_user_conf_dirs() 

445 if udirs: 

446 has_user_conf = None 

447 for d in udirs: 

448 if conf_files(d): 

449 has_user_conf = d 

450 break 

451 if not has_user_conf: 

452 debug("no user configuration file found, trying to create one") 

453 for d in udirs: 

454 ad = os.path.expanduser(d) 

455 conf_file = os.path.join(ad, xpra_conf_filename) 

456 try: 

457 if not os.path.exists(ad): 

458 os.makedirs(ad, int('700', 8)) 

459 with open(conf_file, 'w') as f: 

460 f.write("# xpra user configuration file\n") 

461 f.write("# place your custom settings in this file\n") 

462 f.write("# they will take precedence over the system default ones.\n") 

463 f.write("\n") 

464 f.write("# Examples:\n") 

465 f.write("# speaker=off\n") 

466 f.write("# dpi=144\n") 

467 f.write("\n") 

468 f.write("# For more information on the file format,\n") 

469 f.write("# see the xpra manual at:\n") 

470 f.write("# https://xpra.org/manual.html\n") 

471 f.write("\n") 

472 f.flush() 

473 debug("created default config in "+d) 

474 break 

475 except Exception as e: 

476 debug("failed to create default config in '%s': %s" % (conf_file, e)) 

477 

478 

479OPTIONS_VALIDATION = {} 

480 

481OPTION_TYPES = { 

482 #string options: 

483 "encoding" : str, 

484 "opengl" : str, 

485 "title" : str, 

486 "username" : str, 

487 "password" : str, 

488 "wm-name" : str, 

489 "session-name" : str, 

490 "dock-icon" : str, 

491 "tray-icon" : str, 

492 "window-icon" : str, 

493 "keyboard-raw" : bool, 

494 "keyboard-layout" : str, 

495 "keyboard-layouts" : list, 

496 "keyboard-variant" : str, 

497 "keyboard-variants" : list, 

498 "keyboard-options" : str, 

499 "clipboard" : str, 

500 "clipboard-direction" : str, 

501 "clipboard-filter-file" : str, 

502 "remote-clipboard" : str, 

503 "local-clipboard" : str, 

504 "pulseaudio-command": str, 

505 "bandwidth-limit" : str, 

506 "tcp-encryption" : str, 

507 "tcp-encryption-keyfile": str, 

508 "encryption" : str, 

509 "encryption-keyfile": str, 

510 "pidfile" : str, 

511 "mode" : str, 

512 "ssh" : str, 

513 "systemd-run" : str, 

514 "systemd-run-args" : str, 

515 "system-proxy-socket" : str, 

516 "chdir" : str, 

517 "xvfb" : str, 

518 "socket-dir" : str, 

519 "mmap" : str, 

520 "log-dir" : str, 

521 "log-file" : str, 

522 "border" : str, 

523 "window-close" : str, 

524 "min-size" : str, 

525 "max-size" : str, 

526 "desktop-scaling" : str, 

527 "display" : str, 

528 "tcp-proxy" : str, 

529 "download-path" : str, 

530 "open-command" : str, 

531 "remote-logging" : str, 

532 "lpadmin" : str, 

533 "lpinfo" : str, 

534 "add-printer-options" : list, 

535 "pdf-printer" : str, 

536 "postscript-printer": str, 

537 "debug" : str, 

538 "input-method" : str, 

539 "video-scaling" : str, 

540 "microphone" : str, 

541 "speaker" : str, 

542 "sound-source" : str, 

543 "html" : str, 

544 "socket-permissions": str, 

545 "exec-wrapper" : str, 

546 "dbus-launch" : str, 

547 "webcam" : str, 

548 "mousewheel" : str, 

549 "input-devices" : str, 

550 "shortcut-modifiers": str, 

551 "open-files" : str, 

552 "open-url" : str, 

553 "file-transfer" : str, 

554 "printing" : str, 

555 "headerbar" : str, 

556 "challenge-handlers": list, 

557 #ssl options: 

558 "ssl" : str, 

559 "ssl-key" : str, 

560 "ssl-cert" : str, 

561 "ssl-protocol" : str, 

562 "ssl-ca-certs" : str, 

563 "ssl-ca-data" : str, 

564 "ssl-ciphers" : str, 

565 "ssl-client-verify-mode" : str, 

566 "ssl-server-verify-mode" : str, 

567 "ssl-verify-flags" : str, 

568 "ssl-check-hostname": bool, 

569 "ssl-server-hostname" : str, 

570 "ssl-options" : str, 

571 #int options: 

572 "displayfd" : int, 

573 "pings" : int, 

574 "quality" : int, 

575 "min-quality" : int, 

576 "speed" : int, 

577 "min-speed" : int, 

578 "compression_level" : int, 

579 "dpi" : int, 

580 "file-size-limit" : str, 

581 "idle-timeout" : int, 

582 "server-idle-timeout" : int, 

583 "sync-xvfb" : int, 

584 "pixel-depth" : int, 

585 "uid" : int, 

586 "gid" : int, 

587 "min-port" : int, 

588 "rfb-upgrade" : int, 

589 #float options: 

590 "auto-refresh-delay": float, 

591 #boolean options: 

592 "daemon" : bool, 

593 "start-via-proxy" : bool, 

594 "attach" : bool, 

595 "use-display" : str, 

596 "fake-xinerama" : str, 

597 "resize_display" : str, 

598 "tray" : bool, 

599 "pulseaudio" : bool, 

600 "dbus-proxy" : bool, 

601 "mmap-group" : str, 

602 "readonly" : bool, 

603 "keyboard-sync" : bool, 

604 "cursors" : bool, 

605 "bell" : bool, 

606 "notifications" : bool, 

607 "xsettings" : bool, 

608 "system-tray" : bool, 

609 "sharing" : bool, 

610 "lock" : bool, 

611 "delay-tray" : bool, 

612 "windows" : bool, 

613 "terminate-children": bool, 

614 "exit-with-children": bool, 

615 "exit-with-client" : bool, 

616 "exit-ssh" : bool, 

617 "dbus-control" : bool, 

618 "av-sync" : bool, 

619 "mdns" : bool, 

620 "swap-keys" : bool, 

621 "start-new-commands": bool, 

622 "proxy-start-sessions": bool, 

623 "desktop-fullscreen": bool, 

624 "global-menus" : bool, 

625 "forward-xdg-open" : bool, 

626 "modal-windows" : bool, 

627 "bandwidth-detection" : bool, 

628 "ssh-upgrade" : bool, 

629 "splash" : bool, 

630 #arrays of strings: 

631 "pulseaudio-configure-commands" : list, 

632 "socket-dirs" : list, 

633 "client-socket-dirs" : list, 

634 "remote-xpra" : list, 

635 "encodings" : list, 

636 "proxy-video-encoders" : list, 

637 "video-encoders" : list, 

638 "csc-modules" : list, 

639 "video-decoders" : list, 

640 "speaker-codec" : list, 

641 "microphone-codec" : list, 

642 "compressors" : list, 

643 "packet-encoders" : list, 

644 "key-shortcut" : list, 

645 "source" : list, 

646 "source-start" : list, 

647 "start" : list, 

648 "start-child" : list, 

649 "start-after-connect" : list, 

650 "start-child-after-connect" : list, 

651 "start-on-connect" : list, 

652 "start-child-on-connect" : list, 

653 "start-on-last-client-exit" : list, 

654 "start-child-on-last-client-exit" : list, 

655 "bind" : list, 

656 "bind-vsock" : list, 

657 "bind-tcp" : list, 

658 "bind-udp" : list, 

659 "bind-ws" : list, 

660 "bind-wss" : list, 

661 "bind-ssl" : list, 

662 "bind-ssh" : list, 

663 "bind-rfb" : list, 

664 "auth" : list, 

665 "vsock-auth" : list, 

666 "tcp-auth" : list, 

667 "udp-auth" : list, 

668 "ws-auth" : list, 

669 "wss-auth" : list, 

670 "ssl-auth" : list, 

671 "ssh-auth" : list, 

672 "rfb-auth" : list, 

673 "password-file" : list, 

674 "start-env" : list, 

675 "env" : list, 

676 } 

677 

678#in the options list, available in session files, 

679#but not on the command line: 

680NON_COMMAND_LINE_OPTIONS = [ 

681 "mode", 

682 "wm-name", 

683 "download-path", 

684 "proxy-video-encoders", 

685 "display", 

686 "pdf-printer", 

687 "postscript-printer", 

688 "add-printer-options", 

689 ] 

690 

691START_COMMAND_OPTIONS = [ 

692 "start", "start-child", 

693 "start-after-connect", "start-child-after-connect", 

694 "start-on-connect", "start-child-on-connect", 

695 "start-on-last-client-exit", "start-child-on-last-client-exit", 

696 ] 

697BIND_OPTIONS = ["bind", "bind-tcp", "bind-udp", "bind-ssl", "bind-ws", "bind-wss", "bind-vsock", "bind-rfb"] 

698 

699#keep track of the options added since v3, 

700#so we can generate command lines that work with older supported versions: 

701OPTIONS_ADDED_SINCE_V3 = [ 

702 "source", "source-start", "headerbar", 

703 ] 

704OPTIONS_COMPAT_NAMES = { 

705 "--compression_level=" : "-z" 

706 } 

707 

708CLIENT_OPTIONS = ["title", "username", "password", "session-name", 

709 "dock-icon", "tray-icon", "window-icon", 

710 "clipboard", "clipboard-direction", "clipboard-filter-file", 

711 "remote-clipboard", "local-clipboard", 

712 "tcp-encryption", "tcp-encryption-keyfile", "encryption", "encryption-keyfile", 

713 "systemd-run", "systemd-run-args", 

714 "socket-dir", "socket-dirs", "client-socket-dirs", 

715 "border", "window-close", "min-size", "max-size", "desktop-scaling", 

716 "file-transfer", "file-size-limit", "download-path", 

717 "open-command", "open-files", "printing", "open-url", 

718 "headerbar", 

719 "challenge-handlers", 

720 "dbus-proxy", 

721 "remote-logging", 

722 "lpadmin", "lpinfo", 

723 "debug", 

724 "microphone", "speaker", "sound-source", 

725 "microphone-codec", "speaker-codec", 

726 "mmap", "encodings", "encoding", 

727 "quality", "min-quality", "speed", "min-speed", 

728 "compression_level", 

729 "dpi", "video-scaling", "auto-refresh-delay", 

730 "webcam", "mousewheel", "input-devices", "shortcut-modifiers", "pings", 

731 "tray", "keyboard-sync", "cursors", "bell", "notifications", 

732 "xsettings", "system-tray", "sharing", "lock", 

733 "delay-tray", "windows", "readonly", 

734 "av-sync", "swap-keys", 

735 "opengl", 

736 "start-new-commands", 

737 "desktop-fullscreen", "global-menus", 

738 "video-encoders", "csc-modules", "video-decoders", 

739 "compressors", "packet-encoders", 

740 "key-shortcut", 

741 "env"] 

742 

743CLIENT_ONLY_OPTIONS = ["username", "swap-keys", "dock-icon", 

744 "tray", "delay-tray", "tray-icon"] 

745 

746#options that clients can pass to the proxy 

747#and which will be forwarded to the new proxy instance process: 

748PROXY_START_OVERRIDABLE_OPTIONS = [ 

749 "env", "start-env", "chdir", 

750 "dpi", 

751 "encoding", "encodings", 

752 "quality", "min-quality", "speed", "min-speed", 

753 #"auto-refresh-delay", float! 

754 #no corresponding command line option: 

755 #"wm-name", "download-path", 

756 "compression_level", "video-scaling", 

757 "title", "session-name", 

758 "clipboard", "clipboard-direction", "clipboard-filter-file", 

759 "input-method", 

760 "microphone", "speaker", "sound-source", "pulseaudio", 

761 "idle-timeout", "server-idle-timeout", 

762 "use-display", 

763 "fake-xinerama", "resize_display", "dpi", "pixel-depth", 

764 "readonly", "keyboard-sync", "cursors", "bell", "notifications", "xsettings", 

765 "system-tray", "sharing", "lock", "windows", "webcam", "html", 

766 "terminate-children", "exit-with-children", "exit-with-client", 

767 "av-sync", "global-menus", 

768 "forward-xdg-open", "modal-windows", "bandwidth-detection", 

769 "ssh-upgrade", 

770 "splash", 

771 "printing", "file-transfer", "open-command", "open-files", "open-url", "start-new-commands", 

772 "mmap", "mmap-group", "mdns", 

773 "auth", "vsock-auth", "tcp-auth", "udp-auth", "ws-auth", "wss-auth", "ssl-auth", "ssh-auth", "rfb-auth", 

774 "bind", "bind-vsock", "bind-tcp", "bind-udp", "bind-ssl", "bind-ws", "bind-wss", "bind-ssh", "bind-rfb", 

775 "rfb-upgrade", "bandwidth-limit", 

776 "start", "start-child", "source", "source-start", 

777 "start-after-connect", "start-child-after-connect", 

778 "start-on-connect", "start-child-on-connect", 

779 "start-on-last-client-exit", "start-child-on-last-client-exit", 

780 ] 

781tmp = os.environ.get("XPRA_PROXY_START_OVERRIDABLE_OPTIONS", "") 

782if tmp: 

783 PROXY_START_OVERRIDABLE_OPTIONS = tmp.split(",") 

784del tmp 

785 

786 

787def get_default_key_shortcuts(): 

788 return [shortcut for e,shortcut in ( 

789 (True, "Control+Menu:toggle_keyboard_grab"), 

790 (True, "Shift+Menu:toggle_pointer_grab"), 

791 (not OSX,"Shift+F11:toggle_fullscreen"), 

792 (OSX, "Control+F11:toggle_fullscreen"), 

793 (True, "#+F1:show_menu"), 

794 (True, "#+F2:show_start_new_command"), 

795 (True, "#+F3:show_bug_report"), 

796 (True, "#+F4:quit"), 

797 (True, "#+F5:show_window_info"), 

798 (True, "#+F10:magic_key"), 

799 (True, "#+F11:show_session_info"), 

800 (True, "#+F12:toggle_debug"), 

801 (True, "#+plus:scaleup"), 

802 (OSX, "#+plusminus:scaleup"), 

803 (True, "#+minus:scaledown"), 

804 (True, "#+underscore:scaledown"), 

805 (OSX, "#+emdash:scaledown"), 

806 (True, "#+KP_Add:scaleup"), 

807 (True, "#+KP_Subtract:scaledown"), 

808 (True, "#+KP_Multiply:scalereset"), 

809 (True, "#+bar:scalereset"), 

810 (True, "#+question:scalingoff"), 

811 (OSX, "#+degree:scalereset"), 

812 (OSX, "meta+grave:void"), 

813 (OSX, "meta+shift+asciitilde:void"), 

814 ) 

815 if e] 

816 

817def get_default_systemd_run(): 

818 if WIN32 or OSX: 

819 return "no" 

820 #systemd-run was previously broken in Fedora 26: 

821 #https://github.com/systemd/systemd/issues/3388 

822 #but with newer kernels, it is working again.. 

823 #now that we test it before using it, 

824 #it should be safe to leave it on auto: 

825 return "auto" 

826 

827def get_default_pulseaudio_command(): 

828 if WIN32 or OSX: 

829 return [] 

830 cmd = [ 

831 "pulseaudio", "--start", "-n", "--daemonize=false", "--system=false", 

832 "--exit-idle-time=-1", "--load=module-suspend-on-idle", 

833 "'--load=module-null-sink sink_name=\"Xpra-Speaker\" sink_properties=device.description=\"Xpra\\ Speaker\"'", 

834 "'--load=module-null-sink sink_name=\"Xpra-Microphone\" sink_properties=device.description=\"Xpra\\ Microphone\"'", 

835 "'--load=module-native-protocol-unix socket=$XPRA_PULSE_SERVER'", 

836 "--load=module-dbus-protocol", 

837 "--load=module-x11-publish", 

838 "--log-level=2", "--log-target=stderr", 

839 ] 

840 from xpra.util import envbool 

841 MEMFD = envbool("XPRA_PULSEAUDIO_MEMFD", False) 

842 if not MEMFD: 

843 cmd.append("--enable-memfd=no") 

844 return cmd 

845 

846 

847GLOBAL_DEFAULTS = None 

848#lowest common denominator here 

849#(the xpra.conf file shipped is generally better tuned than this - especially for 'xvfb') 

850def get_defaults(): 

851 global GLOBAL_DEFAULTS 

852 if GLOBAL_DEFAULTS is not None: 

853 return GLOBAL_DEFAULTS 

854 from xpra.platform.features import ( 

855 OPEN_COMMAND, DEFAULT_PULSEAUDIO_CONFIGURE_COMMANDS, 

856 DEFAULT_ENV, CAN_DAEMONIZE, SYSTEM_PROXY_SOCKET, 

857 ) 

858 from xpra.platform.paths import get_download_dir, get_remote_run_xpra_scripts, get_socket_dirs, get_client_socket_dirs 

859 try: 

860 from xpra.platform.info import get_username 

861 username = get_username() 

862 except Exception: 

863 username = "" 

864 conf_dirs = [os.environ.get("XPRA_CONF_DIR")] 

865 build_root = os.environ.get("RPM_BUILD_ROOT") 

866 if build_root: 

867 conf_dirs.append(os.path.join(build_root, "etc", "xpra")) 

868 xpra_cmd = sys.argv[0] 

869 bin_dir = None 

870 if sys.argv: 

871 for strip in ("/usr/bin", "/bin"): 

872 pos = xpra_cmd.find(strip) 

873 if pos>=0: 

874 bin_dir = xpra_cmd[:pos+len(strip)] 

875 root = xpra_cmd[:pos] or "/" 

876 conf_dirs.append(os.path.join(root, "etc", "xpra")) 

877 break 

878 if sys.prefix=="/usr": 

879 conf_dirs.append("/etc/xpra") 

880 else: 

881 conf_dirs.append(os.path.join(sys.prefix, "etc", "xpra")) 

882 for conf_dir in conf_dirs: 

883 if conf_dir and os.path.exists(conf_dir): 

884 break 

885 xvfb = detect_xvfb_command(conf_dir, bin_dir, warn=None) 

886 xvfb_str = xvfb_cmd_str(xvfb) 

887 

888 ssl_protocol = "TLSv1_2" 

889 

890 if POSIX and not OSX and not (is_Debian() or is_Ubuntu()): 

891 fake_xinerama = "auto" 

892 else: 

893 fake_xinerama = "no" 

894 

895 GLOBAL_DEFAULTS = { 

896 "encoding" : "auto", 

897 "title" : "@title@ on @hostinfo@", 

898 "username" : username, 

899 "password" : "", 

900 "wm-name" : DEFAULT_NET_WM_NAME, 

901 "session-name" : "", 

902 "dock-icon" : "", 

903 "tray-icon" : "", 

904 "window-icon" : "", 

905 "keyboard-raw" : False, 

906 "keyboard-layout" : "", 

907 "keyboard-layouts" : [], 

908 "keyboard-variant" : "", 

909 "keyboard-variants" : [], 

910 "keyboard-options" : "", 

911 "clipboard" : "yes", 

912 "clipboard-direction" : "both", 

913 "clipboard-filter-file" : "", 

914 "remote-clipboard" : "CLIPBOARD", 

915 "local-clipboard" : "CLIPBOARD", 

916 "pulseaudio-command": " ".join(get_default_pulseaudio_command()), 

917 "bandwidth-limit" : "auto", 

918 "encryption" : "", 

919 "tcp-encryption" : "", 

920 "encryption-keyfile": "", 

921 "tcp-encryption-keyfile": "", 

922 "pidfile" : "", 

923 "ssh" : "auto", 

924 "systemd-run" : get_default_systemd_run(), 

925 "systemd-run-args" : "", 

926 "system-proxy-socket" : SYSTEM_PROXY_SOCKET, 

927 "xvfb" : xvfb_str, 

928 "chdir" : "", 

929 "socket-dir" : "", 

930 "log-dir" : "auto", 

931 "log-file" : "$DISPLAY.log", 

932 "border" : "auto,5:off", 

933 "window-close" : "auto", 

934 "min-size" : "", 

935 "max-size" : "", 

936 "desktop-scaling" : "auto", 

937 "display" : "", 

938 "tcp-proxy" : "", 

939 "download-path" : get_download_dir(), 

940 "open-command" : " ".join(OPEN_COMMAND), 

941 "remote-logging" : "both", 

942 "lpadmin" : "/usr/sbin/lpadmin", 

943 "lpinfo" : "/usr/sbin/lpinfo", 

944 "add-printer-options" : ["-E", "-o printer-is-shared=false", "-u allow:$USER"], 

945 "pdf-printer" : "", 

946 "postscript-printer": DEFAULT_POSTSCRIPT_PRINTER, 

947 "debug" : "", 

948 "input-method" : "none", 

949 "sound-source" : "", 

950 "html" : "auto", 

951 "socket-permissions": "600", 

952 "exec-wrapper" : "", 

953 "dbus-launch" : "dbus-launch --sh-syntax --close-stderr", 

954 "webcam" : ["auto", "no"][OSX or WIN32], 

955 "mousewheel" : "on", 

956 "input-devices" : "auto", 

957 "shortcut-modifiers": "auto", 

958 "open-files" : "auto", 

959 "open-url" : "auto", 

960 "file-transfer" : "auto", 

961 "printing" : "yes", 

962 "headerbar" : ["auto", "no"][OSX or WIN32], 

963 "challenge-handlers": ["all"], 

964 #ssl options: 

965 "ssl" : "auto", 

966 "ssl-key" : "", 

967 "ssl-cert" : "", 

968 "ssl-protocol" : ssl_protocol, 

969 "ssl-ca-certs" : "default", 

970 "ssl-ca-data" : "", 

971 "ssl-ciphers" : "DEFAULT", 

972 "ssl-client-verify-mode" : "optional", 

973 "ssl-server-verify-mode" : "required", 

974 "ssl-verify-flags" : "X509_STRICT", 

975 "ssl-check-hostname": True, 

976 "ssl-server-hostname": "", 

977 "ssl-options" : "ALL,NO_COMPRESSION", 

978 "quality" : 0, 

979 "min-quality" : 30, 

980 "speed" : 0, 

981 "min-speed" : 30, 

982 "compression_level" : 1, 

983 "dpi" : 0, 

984 "file-size-limit" : "100M", 

985 "idle-timeout" : 0, 

986 "server-idle-timeout" : 0, 

987 "sync-xvfb" : 0, 

988 "pixel-depth" : 0, 

989 "uid" : getuid(), 

990 "gid" : getgid(), 

991 "min-port" : 1024, 

992 "rfb-upgrade" : 5, 

993 "auto-refresh-delay": 0.15, 

994 "daemon" : CAN_DAEMONIZE, 

995 "start-via-proxy" : False, 

996 "attach" : None, 

997 "use-display" : "auto", 

998 "fake-xinerama" : fake_xinerama, 

999 "resize-display" : ["no", "yes"][not OSX and not WIN32], 

1000 "tray" : True, 

1001 "pulseaudio" : DEFAULT_PULSEAUDIO, 

1002 "dbus-proxy" : not OSX and not WIN32, 

1003 "mmap" : "yes", 

1004 "mmap-group" : "auto", 

1005 "speaker" : ["disabled", "on"][has_sound_support() and not is_arm()], 

1006 "microphone" : ["disabled", "off"][has_sound_support()], 

1007 "video-scaling" : "auto", 

1008 "readonly" : False, 

1009 "keyboard-sync" : True, 

1010 "displayfd" : 0, 

1011 "pings" : 5, 

1012 "cursors" : True, 

1013 "bell" : True, 

1014 "notifications" : True, 

1015 "xsettings" : not OSX and not WIN32, 

1016 "system-tray" : True, 

1017 "sharing" : None, 

1018 "lock" : None, 

1019 "delay-tray" : False, 

1020 "windows" : True, 

1021 "terminate-children": False, 

1022 "exit-with-children": False, 

1023 "exit-with-client" : False, 

1024 "start-new-commands": True, 

1025 "proxy-start-sessions": True, 

1026 "av-sync" : True, 

1027 "exit-ssh" : True, 

1028 "dbus-control" : not WIN32 and not OSX, 

1029 "opengl" : "probe", 

1030 "mdns" : not WIN32, 

1031 "swap-keys" : OSX, #only used on osx 

1032 "desktop-fullscreen": False, 

1033 "global-menus" : True, 

1034 "forward-xdg-open" : True, 

1035 "modal-windows" : False, 

1036 "bandwidth-detection" : True, 

1037 "ssh-upgrade" : True, 

1038 "splash" : None, 

1039 "pulseaudio-configure-commands" : [" ".join(x) for x in DEFAULT_PULSEAUDIO_CONFIGURE_COMMANDS], 

1040 "socket-dirs" : get_socket_dirs(), 

1041 "client-socket-dirs" : get_client_socket_dirs(), 

1042 "remote-xpra" : get_remote_run_xpra_scripts(), 

1043 "encodings" : ["all"], 

1044 "proxy-video-encoders" : [], 

1045 "video-encoders" : ["all"], 

1046 "csc-modules" : ["all"], 

1047 "video-decoders" : ["all"], 

1048 "speaker-codec" : [], 

1049 "microphone-codec" : [], 

1050 "compressors" : ["all"], 

1051 "packet-encoders" : ["all"], 

1052 "key-shortcut" : get_default_key_shortcuts(), 

1053 "bind" : ["auto"], 

1054 "bind-vsock" : [], 

1055 "bind-tcp" : [], 

1056 "bind-udp" : [], 

1057 "bind-ws" : [], 

1058 "bind-wss" : [], 

1059 "bind-ssl" : [], 

1060 "bind-ssh" : [], 

1061 "bind-rfb" : [], 

1062 "auth" : [], 

1063 "vsock-auth" : [], 

1064 "tcp-auth" : [], 

1065 "udp-auth" : [], 

1066 "ws-auth" : [], 

1067 "wss-auth" : [], 

1068 "ssl-auth" : [], 

1069 "ssh-auth" : [], 

1070 "rfb-auth" : [], 

1071 "password-file" : [], 

1072 "source" : [], 

1073 "source-start" : [], 

1074 "start" : [], 

1075 "start-child" : [], 

1076 "start-after-connect" : [], 

1077 "start-child-after-connect" : [], 

1078 "start-on-connect" : [], 

1079 "start-child-on-connect" : [], 

1080 "start-on-last-client-exit" : [], 

1081 "start-child-on-last-client-exit" : [], 

1082 "start-env" : DEFAULT_ENV, 

1083 "env" : [], 

1084 } 

1085 return GLOBAL_DEFAULTS 

1086#fields that got renamed: 

1087CLONES = {} 

1088 

1089#these options should not be specified in config files: 

1090NO_FILE_OPTIONS = ("daemon", ) 

1091 

1092 

1093TRUE_OPTIONS = ("yes", "true", "1", "on", True) 

1094FALSE_OPTIONS = ("no", "false", "0", "off", False) 

1095OFF_OPTIONS = ("off", ) 

1096def parse_bool(k, v, auto=None): 

1097 if isinstance(v, str): 

1098 v = v.lower().strip() 

1099 if v in TRUE_OPTIONS: 

1100 return True 

1101 if v in FALSE_OPTIONS: 

1102 return False 

1103 if v in ("auto", None): 

1104 #keep default - which may be None! 

1105 return auto 

1106 try: 

1107 return bool(int(v)) 

1108 except ValueError: 

1109 warn("Warning: cannot parse value '%s' for '%s' as a boolean" % (v, k)) 

1110 return None 

1111 

1112def print_bool(k, v, true_str='yes', false_str='no'): 

1113 if v is None: 

1114 return "auto" 

1115 if isinstance(v, bool): 

1116 if v: 

1117 return true_str 

1118 return false_str 

1119 warn("Warning: cannot print value '%s' for '%s' as a boolean" % (v, k)) 

1120 return "" 

1121 

1122def parse_bool_or_int(k, v): 

1123 return parse_bool_or_number(int, k, v) 

1124 

1125def parse_bool_or_number(numtype, k, v, auto=0): 

1126 if isinstance(v, str): 

1127 v = v.lower() 

1128 if v in TRUE_OPTIONS: 

1129 return 1 

1130 if v in FALSE_OPTIONS: 

1131 return 0 

1132 return parse_number(numtype, k, v, auto) 

1133 

1134def parse_number(numtype, k, v, auto=0): 

1135 if isinstance(v, str): 

1136 v = v.lower() 

1137 if v=="auto": 

1138 return auto 

1139 try: 

1140 return numtype(v) 

1141 except (ValueError, TypeError) as e: 

1142 warn("Warning: cannot parse value '%s' for '%s' as a type %s: %s" % (v, k, numtype, e)) 

1143 return None 

1144 

1145def print_number(i, auto_value=0): 

1146 if i==auto_value: 

1147 return "auto" 

1148 return str(i) 

1149 

1150def parse_with_unit(numtype, v, subunit="bps", min_value=250000): 

1151 if isinstance(v, int): 

1152 return v 

1153 #special case for bandwidth-limit, which can be specified using units: 

1154 try: 

1155 v = str(v).lower().strip() 

1156 import re 

1157 if not v or v in FALSE_OPTIONS: 

1158 return 0 

1159 if v=="auto": 

1160 return None 

1161 r = re.match(r'([0-9\.]*)(.*)', v) 

1162 assert r 

1163 f = float(r.group(1)) 

1164 unit = r.group(2).lower() 

1165 if unit.endswith(subunit): 

1166 unit = unit[:-len(subunit)] #ie: 10mbps -> 10m 

1167 if unit=="b": 

1168 pass 

1169 elif unit=="k": 

1170 f *= 1000 

1171 elif unit=="m": 

1172 f *= 1000000 

1173 elif unit=="g": 

1174 f *= 1000000000 

1175 if min_value is not None: 

1176 assert f>=min_value, "value is too low" 

1177 return int(f) 

1178 except Exception as e: 

1179 raise InitException("invalid value for %s '%s': %s" % (numtype, v, e)) from None 

1180 

1181 

1182def validate_config(d=None, discard=NO_FILE_OPTIONS, extras_types=None, extras_validation=None): 

1183 return do_validate_config(d or {}, discard, extras_types or {}, extras_validation or {}) 

1184 

1185def do_validate_config(d:dict, discard, extras_types:dict, extras_validation:dict): 

1186 """ 

1187 Validates all the options given in a dict with fields as keys and 

1188 strings or arrays of strings as values. 

1189 Each option is strongly typed and invalid value are discarded. 

1190 We get the required datatype from OPTION_TYPES 

1191 """ 

1192 validations = OPTIONS_VALIDATION.copy() 

1193 validations.update(extras_validation) 

1194 option_types = OPTION_TYPES.copy() 

1195 option_types.update(extras_types) 

1196 nd = {} 

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

1198 if k in discard: 

1199 warn("Warning: option '%s' is not allowed in configuration files" % k) 

1200 continue 

1201 vt = option_types.get(k) 

1202 if vt is None: 

1203 warn("Warning: invalid option: '%s'" % k) 

1204 continue 

1205 if vt==str: 

1206 if not isinstance(v, str): 

1207 warn("invalid value for '%s': %s (string required)" % (k, type(v))) 

1208 continue 

1209 elif vt==int: 

1210 v = parse_bool_or_number(int, k, v) 

1211 if v is None: 

1212 continue 

1213 elif vt==float: 

1214 v = parse_number(float, k, v) 

1215 if v is None: 

1216 continue 

1217 elif vt==bool: 

1218 v = parse_bool(k, v) 

1219 if v is None: 

1220 continue 

1221 elif vt==list: 

1222 if isinstance(v, str): 

1223 #could just be that we specified it only once.. 

1224 v = [v] 

1225 elif isinstance(v, list) or v is None: 

1226 #ok so far.. 

1227 pass 

1228 else: 

1229 warn("Warning: invalid value for '%s': %s (a string or list of strings is required)" % (k, type(v))) 

1230 continue 

1231 else: 

1232 warn("Error: unknown option type for '%s': %s" % (k, vt)) 

1233 validation = validations.get(k) 

1234 if validation and v is not None: 

1235 msg = validation(v) 

1236 if msg: 

1237 warn("Warning: invalid value for '%s': %s, %s" % (k, v, msg)) 

1238 continue 

1239 nd[k] = v 

1240 return nd 

1241 

1242 

1243def make_defaults_struct(extras_defaults=None, extras_types=None, extras_validation=None, username="", uid=getuid(), gid=getgid()): 

1244 return do_make_defaults_struct(extras_defaults or {}, extras_types or {}, extras_validation or {}, username, uid, gid) 

1245 

1246def do_make_defaults_struct(extras_defaults:dict, extras_types:dict, extras_validation:dict, username:str, uid:int, gid:int): 

1247 #populate config with default values: 

1248 if not username and uid: 

1249 username = get_username_for_uid(uid) 

1250 defaults = read_xpra_defaults(username, uid, gid) 

1251 return dict_to_validated_config(defaults, extras_defaults, extras_types, extras_validation) 

1252 

1253def dict_to_validated_config(d:dict, extras_defaults=None, extras_types=None, extras_validation=None): 

1254 options = get_defaults().copy() 

1255 if extras_defaults: 

1256 options.update(extras_defaults) 

1257 #parse config: 

1258 validated = validate_config(d, extras_types=extras_types, extras_validation=extras_validation) 

1259 options.update(validated) 

1260 for k,v in CLONES.items(): 

1261 if k in options: 

1262 options[v] = options[k] 

1263 return dict_to_config(options) 

1264 

1265def dict_to_config(options): 

1266 config = XpraConfig() 

1267 for k,v in options.items(): 

1268 setattr(config, name_to_field(k), v) 

1269 return config 

1270 

1271 

1272class XpraConfig: 

1273 def __repr__(self): 

1274 return "XpraConfig(%s)" % self.__dict__ 

1275 

1276 def clone(self): 

1277 c = XpraConfig() 

1278 c.__dict__ = dict(self.__dict__) 

1279 return c 

1280 

1281 

1282def fixup_debug_option(value): 

1283 """ backwards compatible parsing of the debug option, which used to be a boolean """ 

1284 if not value: 

1285 return "" 

1286 value = str(value) 

1287 if value.strip().lower() in ("yes", "true", "on", "1"): 

1288 return "all" 

1289 if value.strip().lower() in ("no", "false", "off", "0"): 

1290 return "" 

1291 #if we're here, the value should be a CSV list of categories 

1292 return value 

1293 

1294def _csvstr(value): 

1295 if isinstance(value, (tuple, list)): 

1296 return ",".join(str(x).lower().strip() for x in value if x) 

1297 if isinstance(value, str): 

1298 return value.strip().lower() 

1299 raise Exception("don't know how to convert %s to a csv list!" % type(value)) 

1300 

1301def _nodupes(s): 

1302 from xpra.util import remove_dupes 

1303 return remove_dupes(x.strip().lower() for x in s.split(",")) 

1304 

1305def fixup_video_all_or_none(options): 

1306 #we import below, but only if we really need to access 

1307 #one of the lists, because those are expensive to probe 

1308 #(we have to load the codec, which may load other libraries) 

1309 # 

1310 #from xpra.codecs.video_helper import ( 

1311 # ALL_VIDEO_ENCODER_OPTIONS, 

1312 # ALL_CSC_MODULE_OPTIONS, 

1313 # ALL_VIDEO_DECODER_OPTIONS, 

1314 # HARDWARE_ENCODER_OPTIONS, 

1315 #) 

1316 def getlist(strarg, help_txt, all_list_name): 

1317 if strarg=="help": 

1318 from xpra.codecs import video_helper 

1319 raise InitInfo("the following %s may be available: %s" % 

1320 (help_txt, csv(getattr(video_helper, all_list_name)))) 

1321 elif strarg=="none": 

1322 return [] 

1323 elif strarg=="all": 

1324 from xpra.codecs import video_helper #@Reimport 

1325 return getattr(video_helper, all_list_name) 

1326 else: 

1327 return [x for x in _nodupes(strarg) if x] 

1328 vestr = _csvstr(options.video_encoders) 

1329 cscstr = _csvstr(options.csc_modules) 

1330 vdstr = _csvstr(options.video_decoders) 

1331 pvestr = _csvstr(options.proxy_video_encoders) 

1332 options.video_encoders = getlist(vestr, "video encoders", "ALL_VIDEO_ENCODER_OPTIONS") 

1333 options.csc_modules = getlist(cscstr, "csc modules", "ALL_CSC_MODULE_OPTIONS") 

1334 options.video_decoders = getlist(vdstr, "video decoders", "ALL_VIDEO_DECODER_OPTIONS") 

1335 options.proxy_video_encoders = getlist(pvestr, "proxy video encoders", "HARDWARE_ENCODER_OPTIONS") 

1336 

1337def fixup_socketdirs(options): 

1338 if isinstance(options.socket_dirs, str): 

1339 options.socket_dirs = options.socket_dirs.split(os.path.pathsep) 

1340 else: 

1341 assert isinstance(options.socket_dirs, (list, tuple)) 

1342 options.socket_dirs = [v for x in options.socket_dirs for v in x.split(os.path.pathsep)] 

1343 

1344def fixup_pings(options): 

1345 #pings used to be a boolean, True mapped to "5" 

1346 if isinstance(options.pings, int): 

1347 return 

1348 try: 

1349 pings = str(options.pings).lower() 

1350 if pings in TRUE_OPTIONS: 

1351 options.pings = 5 

1352 elif pings in FALSE_OPTIONS: 

1353 options.pings = 0 

1354 else: 

1355 options.pings = int(options.pings) 

1356 except ValueError: 

1357 options.pings = 5 

1358 

1359def fixup_encodings(options): 

1360 try: 

1361 from xpra.codecs.codec_constants import PREFERRED_ENCODING_ORDER 

1362 except ImportError: 

1363 return 

1364 RENAME = {"jpg" : "jpeg"} 

1365 if options.encoding: 

1366 options.encoding = RENAME.get(options.encoding, options.encoding) 

1367 estr = _csvstr(options.encodings) 

1368 if estr=="all": 

1369 #replace with an actual list 

1370 options.encodings = list(PREFERRED_ENCODING_ORDER) 

1371 return 

1372 encodings = [RENAME.get(x, x) for x in _nodupes(estr)] 

1373 if "rgb" in encodings: 

1374 if "rgb24" not in encodings: 

1375 encodings.append("rgb24") 

1376 if "rgb32" not in encodings: 

1377 encodings.append("rgb32") 

1378 options.encodings = encodings 

1379 

1380def fixup_compression(options): 

1381 #packet compression: 

1382 from xpra.net import compression 

1383 cstr = _csvstr(options.compressors) 

1384 if cstr=="none": 

1385 compressors = [] 

1386 elif cstr=="all": 

1387 compressors = compression.PERFORMANCE_ORDER 

1388 else: 

1389 compressors = _nodupes(cstr) 

1390 unknown = tuple(x for x in compressors if x and x not in compression.ALL_COMPRESSORS) 

1391 if unknown: 

1392 warn("warning: invalid compressor(s) specified: %s" % csv(unknown)) 

1393 options.compressors = list(compressors) 

1394 

1395def fixup_packetencoding(options): 

1396 #packet encoding 

1397 from xpra.net import packet_encoding 

1398 pestr = _csvstr(options.packet_encoders) 

1399 if pestr=="all": 

1400 packet_encoders = packet_encoding.PERFORMANCE_ORDER 

1401 else: 

1402 packet_encoders = _nodupes(pestr) 

1403 unknown = [x for x in packet_encoders if x and x not in packet_encoding.ALL_ENCODERS] 

1404 if unknown: 

1405 warn("warning: invalid packet encoder(s) specified: %s" % csv(unknown)) 

1406 options.packet_encoders = packet_encoders 

1407 

1408def fixup_keyboard(options): 

1409 #variants and layouts can be specified as CSV, convert them to lists: 

1410 def p(v): 

1411 try: 

1412 from xpra.util import remove_dupes 

1413 r = remove_dupes(x.strip() for x in v.split(",")) 

1414 #remove empty string if that's the only value: 

1415 if r and len(r)==1 and r[0]=="": 

1416 r = [] 

1417 return r 

1418 except Exception: 

1419 return [] 

1420 options.keyboard_layouts = p(options.keyboard_layouts) 

1421 options.keyboard_variants = p(options.keyboard_variants) 

1422 options.keyboard_raw = parse_bool("keyboard-raw", options.keyboard_raw) 

1423 

1424def fixup_clipboard(options): 

1425 cd = options.clipboard_direction.lower().replace("-", "") 

1426 if cd=="toserver": 

1427 options.clipboard_direction = "to-server" 

1428 elif cd=="toclient": 

1429 options.clipboard_direction = "to-client" 

1430 elif cd=="both": 

1431 options.clipboard_direction = "both" 

1432 elif cd in ("disabled", "none"): 

1433 options.clipboard_direction = "disabled" 

1434 else: 

1435 warn("Warning: invalid value for clipboard-direction: '%s'" % options.clipboard_direction) 

1436 warn(" specify 'to-server', 'to-client' or 'both'") 

1437 options.clipboard_direction = "disabled" 

1438 

1439def abs_paths(options): 

1440 #convert to absolute paths before we daemonize 

1441 for k in ("clipboard-filter-file", 

1442 "tcp-encryption-keyfile", "encryption-keyfile", 

1443 "log-dir", 

1444 "download-path", "exec-wrapper", 

1445 "ssl-key", "ssl-cert", "ssl-ca-certs"): 

1446 f = k.replace("-", "_") 

1447 v = getattr(options, f) 

1448 if v and (k!="ssl-ca-certs" or v!="default"): 

1449 if os.path.isabs(v) or v=="auto": 

1450 continue 

1451 if v.startswith("~") or v.startswith("$"): 

1452 continue 

1453 setattr(options, f, os.path.abspath(v)) 

1454 

1455 

1456def fixup_options(options, skip_encodings=False): 

1457 if not skip_encodings: 

1458 fixup_encodings(options) 

1459 fixup_pings(options) 

1460 fixup_compression(options) 

1461 fixup_packetencoding(options) 

1462 fixup_video_all_or_none(options) 

1463 fixup_socketdirs(options) 

1464 fixup_clipboard(options) 

1465 fixup_keyboard(options) 

1466 abs_paths(options) 

1467 #remote-xpra is meant to be a list, but the user can specify a string using the command line, 

1468 #in which case we replace all the default values with this single entry: 

1469 if not isinstance(options.remote_xpra, (list, tuple)): 

1470 options.remote_xpra = [options.remote_xpra] 

1471 

1472 

1473def main(): 

1474 from xpra.util import nonl 

1475 def print_options(o): 

1476 for k,ot in sorted(OPTION_TYPES.items()): 

1477 v = getattr(o, name_to_field(k), "") 

1478 if ot==bool and v is None: 

1479 v = "Auto" 

1480 if isinstance(v, list): 

1481 v = csv(str(x) for x in v) 

1482 print("* %-32s : %s" % (k, nonl(v))) 

1483 from xpra.platform import program_context 

1484 from xpra.log import enable_color 

1485 with program_context("Config-Info", "Config Info"): 

1486 enable_color() 

1487 args = list(sys.argv[1:]) 

1488 if "-v" in args or "--verbose" in sys.argv: 

1489 global debug 

1490 def print_debug(*args): 

1491 print(args[0] % args[1:]) 

1492 debug = print_debug 

1493 args.remove("-v") 

1494 

1495 if args: 

1496 for filename in args: 

1497 print("") 

1498 print("Configuration file '%s':" % filename) 

1499 if not os.path.exists(filename): 

1500 print(" Error: file not found") 

1501 continue 

1502 d = read_config(filename) 

1503 config = dict_to_validated_config(d) 

1504 print_options(config) 

1505 else: 

1506 print("Default Configuration:") 

1507 print_options(make_defaults_struct()) 

1508 

1509 

1510if __name__ == "__main__": 

1511 main()