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) 2011 Serviware (Arthur Huillet, <ahuillet@serviware.com>) 

4# Copyright (C) 2010-2020 Antoine Martin <antoine@xpra.org> 

5# Copyright (C) 2008 Nathaniel Smith <njs@pobox.com> 

6# Xpra is released under the terms of the GNU GPL v2, or, at your option, any 

7# later version. See the file COPYING for details. 

8 

9import sys 

10import os.path 

11import optparse 

12 

13from xpra.version_util import full_version_str 

14from xpra.platform.features import LOCAL_SERVERS_SUPPORTED, SHADOW_SUPPORTED, CAN_DAEMONIZE 

15from xpra.util import envbool, csv 

16from xpra.os_util import getuid, WIN32, OSX, POSIX 

17from xpra.scripts.config import ( 

18 OPTION_TYPES, FALSE_OPTIONS, 

19 InitException, InitInfo, InitExit, 

20 fixup_debug_option, fixup_options, 

21 make_defaults_struct, parse_bool, print_number, 

22 validate_config, has_sound_support, name_to_field, 

23 ) 

24 

25 

26def enabled_str(v, true_str="yes", false_str="no") -> str: 

27 if v: 

28 return true_str 

29 return false_str 

30 

31def enabled_or_auto(v): 

32 return bool_or(v, None, true_str="yes", false_str="no", other_str="auto") 

33 

34def bool_or(v, other_value, true_str, false_str, other_str): 

35 vs = str(v).lower() 

36 if vs==other_value: 

37 return other_str 

38 bv = parse_bool("", v) 

39 return enabled_str(bv, true_str, false_str) 

40 

41def sound_option(v): 

42 vl = v.lower() 

43 #ensures we return only: "on", "off" or "disabled" given any value 

44 if vl=="no": 

45 vl = "disabled" 

46 return bool_or(vl, "disabled", "on", "off", "disabled") 

47 

48 

49def info(msg): 

50 #use this function to print warnings 

51 #we must write to stderr to prevent 

52 #the output from interfering when running as proxy over ssh 

53 #(which uses stdin / stdout as communication channel) 

54 try: 

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

56 sys.stderr.flush() 

57 except OSError: 

58 if POSIX: 

59 import syslog 

60 syslog.syslog(syslog.LOG_INFO, msg) 

61 

62def warn(msg): 

63 #use this function to print warnings 

64 #we must write to stderr to prevent 

65 #the output from interfering when running as proxy over ssh 

66 #(which uses stdin / stdout as communication channel) 

67 try: 

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

69 sys.stderr.flush() 

70 except OSError: 

71 if POSIX: 

72 import syslog 

73 syslog.syslog(syslog.LOG_WARNING, msg) 

74 

75def error(msg): 

76 #use this function to print warnings 

77 #we must write to stderr to prevent 

78 #the output from interfering when running as proxy over ssh 

79 #(which uses stdin / stdout as communication channel) 

80 try: 

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

82 sys.stderr.flush() 

83 except OSError: 

84 if POSIX: 

85 import syslog 

86 syslog.syslog(syslog.LOG_ERR, msg) 

87 

88 

89supports_proxy = True 

90supports_shadow = SHADOW_SUPPORTED 

91supports_server = LOCAL_SERVERS_SUPPORTED 

92if supports_server: 

93 try: 

94 from xpra.x11.bindings.wait_for_x_server import wait_for_x_server #@UnresolvedImport @UnusedImport 

95 except ImportError: 

96 supports_server = False 

97try: 

98 from xpra.net import mdns 

99 supports_mdns = bool(mdns) 

100except ImportError: 

101 supports_mdns = False 

102 

103 

104#this parse doesn't exit when it encounters an error, 

105#allowing us to deal with it better and show a UI message if needed. 

106class ModifiedOptionParser(optparse.OptionParser): 

107 def error(self, msg): 

108 raise InitException(msg) 

109 def exit(self, status=0, msg=None): 

110 raise InitExit(status, msg) 

111 

112 

113def fixup_defaults(defaults): 

114 for k in ("debug", "encoding", "sound-source", "microphone-codec", "speaker-codec"): 

115 fn = k.replace("-", "_") 

116 v = getattr(defaults, fn) 

117 if "help" in v: 

118 if not envbool("XPRA_SKIP_UI", False): 

119 #skip-ui: we're running in subprocess, don't bother spamming stderr 

120 warn(("Warning: invalid 'help' option found in '%s' configuration\n" % k) + 

121 " this should only be used as a command line argument\n") 

122 if k in ("encoding", "debug", "sound-source"): 

123 setattr(defaults, fn, "") 

124 else: 

125 v.remove("help") 

126 

127def do_replace_option(cmdline, oldoption, newoption): 

128 for i, x in enumerate(cmdline): 

129 if x==oldoption: 

130 cmdline[i] = newoption 

131 elif newoption.find("=")<0 and x.startswith("%s=" % oldoption): 

132 cmdline[i] = "%s=%s" % (newoption, x.split("=", 1)[1]) 

133 

134def do_legacy_bool_parse(cmdline, optionname, newoptionname=None): 

135 #find --no-XYZ or --XYZ 

136 #and replace it with --XYZ=yes|no 

137 no = "--no-%s" % optionname 

138 yes = "--%s" % optionname 

139 if newoptionname is None: 

140 newoptionname = optionname 

141 do_replace_option(cmdline, no, "--%s=no" % optionname) 

142 do_replace_option(cmdline, yes, "--%s=yes" % optionname) 

143 

144def ignore_options(args, options): 

145 for x in options: 

146 o = "--%s" % x #ie: --use-display 

147 while o in args: 

148 args.remove(o) 

149 o = "--%s=" % x #ie: --bind-tcp=.... 

150 remove = [] 

151 #find all command line arguments starting with this option: 

152 for v in args: 

153 if v.startswith(o): 

154 remove.append(v) 

155 #and remove them all: 

156 for r in remove: 

157 while r in args: 

158 args.remove(r) 

159 

160 

161def parse_env(env) -> dict: 

162 d = {} 

163 for ev in env: 

164 try: 

165 if ev.startswith("#"): 

166 continue 

167 v = ev.split("=", 1) 

168 if len(v)!=2: 

169 warn("Warning: invalid environment option '%s'" % ev) 

170 continue 

171 d[v[0]] = os.path.expandvars(v[1]) 

172 except Exception as e: 

173 warn("Warning: cannot parse environment option '%s':" % ev) 

174 warn(" %s" % e) 

175 return d 

176 

177 

178def parse_URL(url): 

179 from urllib.parse import urlparse, parse_qs 

180 up = urlparse(url) 

181 address = up.netloc 

182 qpos = url.find("?") 

183 options = {} 

184 if qpos>0: 

185 params_str = url[qpos+1:] 

186 params = parse_qs(params_str, keep_blank_values=True) 

187 f_params = {} 

188 for k,v in params.items(): 

189 t = OPTION_TYPES.get(k) 

190 if t is not None and t!=list: 

191 v = v[0] 

192 f_params[k] = v 

193 options = validate_config(f_params) 

194 scheme = up.scheme 

195 if scheme.startswith("xpra+"): 

196 scheme = scheme[len("xpra+"):] 

197 if scheme in ("tcp", "ssl", "ssh", "ws", "wss"): 

198 address = "%s://%s" % (scheme, address) 

199 return address, options 

200 

201 

202def get_server_modes(): 

203 server_modes = [] 

204 if supports_server: 

205 server_modes.append("start") 

206 server_modes.append("start-desktop") 

207 server_modes.append("upgrade") 

208 if supports_shadow: 

209 server_modes.append("shadow") 

210 return server_modes 

211 

212 

213def get_subcommands(): 

214 return tuple(x.split(" ")[0] for x in get_usage()) 

215 

216 

217def get_usage(): 

218 command_options = [""] 

219 if supports_server: 

220 command_options += ["start [DISPLAY]", 

221 "start-desktop [DISPLAY]", 

222 "upgrade [DISPLAY]", 

223 ] 

224 if supports_shadow: 

225 command_options.append("shadow [DISPLAY]") 

226 

227 command_options += [ 

228 "attach [DISPLAY]", 

229 "detach [DISPLAY]", 

230 "info [DISPLAY]", 

231 "version [DISPLAY]", 

232 "stop [DISPLAY]", 

233 "exit [DISPLAY]", 

234 "screenshot filename [DISPLAY]", 

235 "control DISPLAY command [arg1] [arg2]..", 

236 "print DISPLAY filename", 

237 "shell [DISPLAY]", 

238 "showconfig", 

239 "list", 

240 "list-windows", 

241 "sessions", 

242 "launcher", 

243 ] 

244 if supports_mdns: 

245 command_options.append("list-mdns") 

246 command_options.append("mdns-gui") 

247 return command_options 

248 

249def parse_cmdline(cmdline): 

250 defaults = make_defaults_struct() 

251 return do_parse_cmdline(cmdline, defaults) 

252 

253def do_parse_cmdline(cmdline, defaults): 

254 ################################################################# 

255 ## NOTE NOTE NOTE 

256 ## 

257 ## If you modify anything here, then remember to update the man page 

258 ## (xpra.1) as well! 

259 ## 

260 ## NOTE NOTE NOTE 

261 ################################################################# 

262 

263 version = "xpra v%s" % full_version_str() 

264 usage_strs = ["\t%%prog %s\n" % x for x in get_usage()] 

265 if not supports_server: 

266 usage_strs = ["(this xpra installation does not support starting local servers)\n"]+usage_strs 

267 parser = ModifiedOptionParser(version=version, usage="\n" + "".join(usage_strs)) 

268 hidden_options = { 

269 "display" : defaults.display, 

270 "wm-name" : defaults.wm_name, 

271 "download-path" : defaults.download_path, 

272 } 

273 def replace_option(oldoption, newoption): 

274 do_replace_option(cmdline, oldoption, newoption) 

275 def legacy_bool_parse(optionname, newoptionname=None): 

276 do_legacy_bool_parse(cmdline, optionname, newoptionname) 

277 def ignore(defaults): 

278 ignore_options(cmdline, defaults.keys()) 

279 for k,v in defaults.items(): 

280 hidden_options[k.replace("-", "_")] = v 

281 group = optparse.OptionGroup(parser, "Server Options", 

282 "These options are only relevant on the server when using the %s mode." % 

283 " or ".join(["'%s'" % x for x in get_server_modes()])) 

284 parser.add_option_group(group) 

285 #we support remote start, so we need those even if we don't have server support: 

286 group.add_option("--start", action="append", 

287 dest="start", metavar="CMD", default=list(defaults.start or []), 

288 help="program to spawn in server (may be repeated). Default: %default.") 

289 group.add_option("--start-child", action="append", 

290 dest="start_child", metavar="CMD", default=list(defaults.start_child or []), 

291 help="program to spawn in server," 

292 +" taken into account by the exit-with-children option" 

293 +" (may be repeated to run multiple commands)." 

294 +" Default: %default.") 

295 group.add_option("--start-after-connect", action="append", 

296 dest="start_after_connect", default=defaults.start_after_connect, 

297 help="program to spawn in server after the first client has connected (may be repeated)." 

298 +" Default: %default.") 

299 group.add_option("--start-child-after-connect", action="append", 

300 dest="start_child_after_connect", default=defaults.start_child_after_connect, 

301 help="program to spawn in server after the first client has connected," 

302 +" taken into account by the exit-with-children option" 

303 +" (may be repeated to run multiple commands)." 

304 +" Default: %default.") 

305 group.add_option("--start-on-connect", action="append", 

306 dest="start_on_connect", default=defaults.start_on_connect, 

307 help="program to spawn in server every time a client connects (may be repeated)." 

308 +" Default: %default.") 

309 group.add_option("--start-child-on-connect", action="append", 

310 dest="start_child_on_connect", default=defaults.start_child_on_connect, 

311 help="program to spawn in server every time a client connects," 

312 +" taken into account by the exit-with-children option (may be repeated)." 

313 +" Default: %default.") 

314 group.add_option("--start-on-last-client-exit", action="append", 

315 dest="start_on_last_client_exit", default=defaults.start_on_last_client_exit, 

316 help="program to spawn in server every time a client disconnects" 

317 +" and there are no other clients left (may be repeated)." 

318 +" Default: %default.") 

319 group.add_option("--start-child-on-last-client-exit", action="append", 

320 dest="start_child_on_last_client_exit", default=defaults.start_child_on_last_client_exit, 

321 help="program to spawn in server every time a client disconnects" 

322 +" and there are no other clients left," 

323 +" taken into account by the exit-with-children option (may be repeated)." 

324 +" Default: %default.") 

325 group.add_option("--exec-wrapper", action="store", 

326 dest="exec_wrapper", metavar="CMD", default=defaults.exec_wrapper, 

327 help="Wrapper for executing commands. Default: %default.") 

328 legacy_bool_parse("terminate-children") 

329 group.add_option("--terminate-children", action="store", metavar="yes|no", 

330 dest="terminate_children", default=defaults.terminate_children, 

331 help="Terminate all the child commands on server stop. Default: %default") 

332 legacy_bool_parse("exit-with-children") 

333 group.add_option("--exit-with-children", action="store", metavar="yes|no", 

334 dest="exit_with_children", default=defaults.exit_with_children, 

335 help="Terminate the server when the last --start-child command(s) exit") 

336 legacy_bool_parse("start-new-commands") 

337 group.add_option("--start-new-commands", action="store", metavar="yes|no", 

338 dest="start_new_commands", default=defaults.start_new_commands, 

339 help="Allows clients to execute new commands on the server." 

340 +" Default: %s." % enabled_str(defaults.start_new_commands)) 

341 legacy_bool_parse("start-via-proxy") 

342 group.add_option("--start-via-proxy", action="store", metavar="yes|no|auto", 

343 dest="start_via_proxy", default=defaults.start_via_proxy, 

344 help="Start servers via the system proxy server. Default: %default.") 

345 legacy_bool_parse("proxy-start-sessions") 

346 group.add_option("--proxy-start-sessions", action="store", metavar="yes|no", 

347 dest="proxy_start_sessions", default=defaults.proxy_start_sessions, 

348 help="Allows proxy servers to start new sessions on demand." 

349 +" Default: %s." % enabled_str(defaults.proxy_start_sessions)) 

350 group.add_option("--dbus-launch", action="store", 

351 dest="dbus_launch", metavar="CMD", default=defaults.dbus_launch, 

352 help="Start the session within a dbus-launch context," 

353 +" leave empty to turn off. Default: %default.") 

354 group.add_option("--source", action="append", 

355 dest="source", default=list(defaults.source or []), 

356 help="Script to source into the server environment. Default: %s." % csv( 

357 ("'%s'" % x) for x in (defaults.source or []) if not x.startswith("#"))) 

358 group.add_option("--source-start", action="append", 

359 dest="source_start", default=list(defaults.source_start or []), 

360 help="Script to source into the environment used for starting commands. Default: %s." % csv( 

361 ("'%s'" % x) for x in (defaults.source_start or []) if not x.startswith("#"))) 

362 group.add_option("--start-env", action="append", 

363 dest="start_env", default=list(defaults.start_env or []), 

364 help="Define environment variables used with 'start-child' and 'start'," 

365 +" can be specified multiple times. Default: %s." % csv( 

366 ("'%s'" % x) for x in (defaults.start_env or []) if not x.startswith("#"))) 

367 if POSIX: 

368 legacy_bool_parse("systemd-run") 

369 group.add_option("--systemd-run", action="store", metavar="yes|no|auto", 

370 dest="systemd_run", default=defaults.systemd_run, 

371 help="Wrap server start commands with systemd-run. Default: %default.") 

372 group.add_option("--systemd-run-args", action="store", metavar="ARGS", 

373 dest="systemd_run_args", default=defaults.systemd_run_args, 

374 help="Command line arguments passed to systemd-run. Default: '%default'.") 

375 else: 

376 ignore({"systemd_run" : defaults.systemd_run, 

377 "systemd_run_args" : defaults.systemd_run_args}) 

378 

379 legacy_bool_parse("html") 

380 if supports_server or supports_shadow: 

381 group.add_option("--tcp-proxy", action="store", 

382 dest="tcp_proxy", default=defaults.tcp_proxy, 

383 metavar="HOST:PORT", 

384 help="The address to which non-xpra packets will be forwarded. Default: '%default'.") 

385 group.add_option("--html", action="store", 

386 dest="html", default=defaults.html, 

387 metavar="on|off|[HOST:]PORT", 

388 help="Enable the web server and the html5 client. Default: '%default'.") 

389 else: 

390 ignore({"tcp_proxy" : "", 

391 "html" : ""}) 

392 legacy_bool_parse("daemon") 

393 legacy_bool_parse("attach") 

394 if POSIX and getuid()==0: 

395 group.add_option("--uid", action="store", 

396 dest="uid", default=defaults.uid, 

397 help="The user id to change to when the server is started by root." 

398 +" Default: %s." % defaults.uid) 

399 group.add_option("--gid", action="store", 

400 dest="gid", default=defaults.gid, 

401 help="The group id to change to when the server is started by root." 

402 +" Default: %s." % defaults.gid) 

403 else: 

404 ignore({ 

405 "uid" : defaults.uid, 

406 "gid" : defaults.gid, 

407 }) 

408 if (supports_server or supports_shadow) and CAN_DAEMONIZE: 

409 group.add_option("--daemon", action="store", metavar="yes|no", 

410 dest="daemon", default=defaults.daemon, 

411 help="Daemonize when running as a server (default: %s)" % enabled_str(defaults.daemon)) 

412 group.add_option("--chdir", action="store", metavar="DIR", 

413 dest="chdir", default=defaults.chdir, 

414 help="Change to this directory (default: %s)" % enabled_str(defaults.chdir)) 

415 group.add_option("--pidfile", action="store", 

416 dest="pidfile", default=defaults.pidfile, 

417 help="Write the process id to this file (default: '%default')") 

418 group.add_option("--log-dir", action="store", 

419 dest="log_dir", default=defaults.log_dir, 

420 help="The directory where log files are placed" 

421 ) 

422 group.add_option("--log-file", action="store", 

423 dest="log_file", default=defaults.log_file, 

424 help="When daemonizing, this is where the log messages will go. Default: '%default'." 

425 + " If a relative filename is specified the it is relative to --log-dir," 

426 + " the value of '$DISPLAY' will be substituted with the actual display used" 

427 ) 

428 else: 

429 ignore({ 

430 "daemon" : defaults.daemon, 

431 "pidfile" : defaults.pidfile, 

432 "log_file" : defaults.log_file, 

433 "log_dir" : defaults.log_dir, 

434 "chdir" : defaults.chdir, 

435 }) 

436 group.add_option("--attach", action="store", metavar="yes|no|auto", 

437 dest="attach", default=defaults.attach, 

438 help="Attach a client as soon as the server has started" 

439 +" (default: %s)" % enabled_or_auto(defaults.attach)) 

440 

441 legacy_bool_parse("printing") 

442 legacy_bool_parse("file-transfer") 

443 legacy_bool_parse("open-files") 

444 legacy_bool_parse("open-url") 

445 group.add_option("--file-transfer", action="store", metavar="yes|no|ask", 

446 dest="file_transfer", default=defaults.file_transfer, 

447 help="Support file transfers. Default: %s." % enabled_str(defaults.file_transfer)) 

448 group.add_option("--open-files", action="store", metavar="yes|no|ask", 

449 dest="open_files", default=defaults.open_files, 

450 help="Automatically open uploaded files (potentially dangerous). Default: '%default'.") 

451 group.add_option("--open-url", action="store", metavar="yes|no|ask", 

452 dest="open_url", default=defaults.open_url, 

453 help="Automatically open URL (potentially dangerous). Default: '%default'.") 

454 group.add_option("--printing", action="store", metavar="yes|no|ask", 

455 dest="printing", default=defaults.printing, 

456 help="Support printing. Default: %s." % enabled_str(defaults.printing)) 

457 group.add_option("--file-size-limit", action="store", metavar="SIZE", 

458 dest="file_size_limit", default=defaults.file_size_limit, 

459 help="Maximum size of file transfers. Default: %s." % defaults.file_size_limit) 

460 if supports_server: 

461 group.add_option("--lpadmin", action="store", 

462 dest="lpadmin", default=defaults.lpadmin, 

463 metavar="COMMAND", 

464 help="Specify the lpadmin command to use. Default: '%default'.") 

465 group.add_option("--lpinfo", action="store", 

466 dest="lpinfo", default=defaults.lpinfo, 

467 metavar="COMMAND", 

468 help="Specify the lpinfo command to use. Default: '%default'.") 

469 else: 

470 ignore({ 

471 "lpadmin" : defaults.lpadmin, 

472 "lpinfo" : defaults.lpinfo, 

473 }) 

474 #options without command line equivallents: 

475 hidden_options["pdf-printer"] = defaults.pdf_printer 

476 hidden_options["postscript-printer"] = defaults.postscript_printer 

477 hidden_options["add-printer-options"] = defaults.add_printer_options 

478 

479 legacy_bool_parse("exit-with-client") 

480 if (supports_server or supports_shadow): 

481 group.add_option("--exit-with-client", action="store", metavar="yes|no", 

482 dest="exit_with_client", default=defaults.exit_with_client, 

483 help="Terminate the server when the last client disconnects." 

484 +" Default: %s" % enabled_str(defaults.exit_with_client)) 

485 else: 

486 ignore({"exit_with_client" : defaults.exit_with_client}) 

487 group.add_option("--idle-timeout", action="store", 

488 dest="idle_timeout", type="int", default=defaults.idle_timeout, 

489 help="Disconnects the client when idle (0 to disable)." 

490 +" Default: %s seconds" % defaults.idle_timeout) 

491 group.add_option("--server-idle-timeout", action="store", 

492 dest="server_idle_timeout", type="int", default=defaults.server_idle_timeout, 

493 help="Exits the server when idle (0 to disable)." 

494 +" Default: %s seconds" % defaults.server_idle_timeout) 

495 legacy_bool_parse("fake-xinerama") 

496 legacy_bool_parse("use-display") 

497 if supports_server: 

498 group.add_option("--use-display", action="store", metavar="yes|no|auto", 

499 dest="use_display", default=defaults.use_display, 

500 help="Use an existing display rather than starting one with the xvfb command." 

501 +" Default: %s" % enabled_str(defaults.use_display)) 

502 group.add_option("--xvfb", action="store", 

503 dest="xvfb", 

504 default=defaults.xvfb, 

505 metavar="CMD", 

506 help="How to run the headless X server. Default: '%default'.") 

507 group.add_option("--displayfd", action="store", metavar="FD", 

508 dest="displayfd", default=defaults.displayfd, 

509 help="The xpra server will write the display number back on this file descriptor" 

510 +" as a newline-terminated string.") 

511 group.add_option("--fake-xinerama", action="store", metavar="path|auto|no", 

512 dest="fake_xinerama", 

513 default=defaults.fake_xinerama, 

514 help="Setup fake xinerama support for the session. "+ 

515 "You can specify the path to the libfakeXinerama.so library or a boolean." 

516 +" Default: %s." % enabled_str(defaults.fake_xinerama)) 

517 else: 

518 ignore({ 

519 "use-display" : defaults.use_display, 

520 "xvfb" : defaults.xvfb, 

521 "displayfd" : defaults.displayfd, 

522 "fake-xinerama" : defaults.fake_xinerama, 

523 }) 

524 group.add_option("--resize-display", action="store", 

525 dest="resize_display", default=defaults.resize_display, metavar="yes|no|widthxheight", 

526 help="Whether the server display should be resized to match the client resolution." 

527 +" Default: %s." % enabled_str(defaults.resize_display)) 

528 defaults_bind = defaults.bind 

529 if supports_server or supports_shadow: 

530 group.add_option("--bind", action="append", 

531 dest="bind", default=[], 

532 metavar="SOCKET", 

533 help="Listen for connections over %s." % ("named pipes" if WIN32 else "unix domain sockets") 

534 +" You may specify this option multiple times to listen on different locations." 

535 +" Default: %s" % csv(defaults_bind)) 

536 group.add_option("--bind-tcp", action="append", 

537 dest="bind_tcp", default=list(defaults.bind_tcp or []), 

538 metavar="[HOST]:[PORT]", 

539 help="Listen for connections over TCP." 

540 + " Use --tcp-auth to secure it." 

541 + " You may specify this option multiple times with different host and port combinations") 

542 group.add_option("--bind-udp", action="append", 

543 dest="bind_udp", default=list(defaults.bind_udp or []), 

544 metavar="[HOST]:[PORT]", 

545 help="Listen for connections over UDP." 

546 + " Use --udp-auth to secure it." 

547 + " You may specify this option multiple times with different host and port combinations") 

548 group.add_option("--bind-ws", action="append", 

549 dest="bind_ws", default=list(defaults.bind_ws or []), 

550 metavar="[HOST]:[PORT]", 

551 help="Listen for connections over Websocket." 

552 + " Use --ws-auth to secure it." 

553 + " You may specify this option multiple times with different host and port combinations") 

554 group.add_option("--bind-wss", action="append", 

555 dest="bind_wss", default=list(defaults.bind_wss or []), 

556 metavar="[HOST]:[PORT]", 

557 help="Listen for connections over HTTPS / wss (secure Websocket)." 

558 + " Use --wss-auth to secure it." 

559 + " You may specify this option multiple times with different host and port combinations") 

560 group.add_option("--bind-ssl", action="append", 

561 dest="bind_ssl", default=list(defaults.bind_ssl or []), 

562 metavar="[HOST]:PORT", 

563 help="Listen for connections over SSL." 

564 + " Use --ssl-auth to secure it." 

565 + " You may specify this option multiple times with different host and port combinations") 

566 group.add_option("--bind-ssh", action="append", 

567 dest="bind_ssh", default=list(defaults.bind_ssh or []), 

568 metavar="[HOST]:PORT", 

569 help="Listen for connections using SSH transport." 

570 + " Use --ssh-auth to secure it." 

571 + " You may specify this option multiple times with different host and port combinations") 

572 group.add_option("--bind-rfb", action="append", 

573 dest="bind_rfb", default=list(defaults.bind_rfb or []), 

574 metavar="[HOST]:PORT", 

575 help="Listen for RFB connections." 

576 + " Use --rfb-auth to secure it." 

577 + " You may specify this option multiple times with different host and port combinations") 

578 else: 

579 ignore({ 

580 "bind" : defaults.bind, 

581 "bind-tcp" : defaults.bind_tcp, 

582 "bind-udp" : defaults.bind_udp, 

583 "bind-ws" : defaults.bind_ws, 

584 "bind-wss" : defaults.bind_wss, 

585 "bind-ssl" : defaults.bind_ssl, 

586 "bind-ssh" : defaults.bind_ssh, 

587 "bind-rfb" : defaults.bind_rfb, 

588 }) 

589 try: 

590 from xpra.net import vsock 

591 except ImportError: 

592 vsock = None 

593 if vsock: 

594 group.add_option("--bind-vsock", action="append", 

595 dest="bind_vsock", default=list(defaults.bind_vsock or []), 

596 metavar="[CID]:[PORT]", 

597 help="Listen for connections over VSOCK." 

598 + " You may specify this option multiple times with different CID and port combinations") 

599 else: 

600 ignore({"bind-vsock" : []}) 

601 legacy_bool_parse("mdns") 

602 if (supports_server or supports_shadow): 

603 group.add_option("--mdns", action="store", metavar="yes|no", 

604 dest="mdns", default=defaults.mdns, 

605 help="Publish the session information via mDNS. Default: %s." % enabled_str(defaults.mdns)) 

606 else: 

607 ignore({"mdns" : defaults.mdns}) 

608 legacy_bool_parse("pulseaudio") 

609 legacy_bool_parse("dbus-proxy") 

610 legacy_bool_parse("dbus-control") 

611 if supports_server: 

612 group.add_option("--pulseaudio", action="store", metavar="yes|no|auto", 

613 dest="pulseaudio", default=defaults.pulseaudio, 

614 help="Start a pulseaudio server for the session." 

615 +" Default: %s." % enabled_or_auto(defaults.pulseaudio)) 

616 group.add_option("--pulseaudio-command", action="store", 

617 dest="pulseaudio_command", default=defaults.pulseaudio_command, 

618 help="The command used to start the pulseaudio server. Default: '%default'.") 

619 group.add_option("--pulseaudio-configure-commands", action="append", 

620 dest="pulseaudio_configure_commands", default=defaults.pulseaudio_configure_commands, 

621 help="The commands used to configure the pulseaudio server. Default: '%default'.") 

622 group.add_option("--dbus-proxy", action="store", metavar="yes|no", 

623 dest="dbus_proxy", default=defaults.dbus_proxy, 

624 help="Forward dbus calls from the client. Default: %s." % enabled_str(defaults.dbus_proxy)) 

625 group.add_option("--dbus-control", action="store", metavar="yes|no", 

626 dest="dbus_control", default=defaults.dbus_control, 

627 help="Allows the server to be controlled via its dbus interface." 

628 + " Default: %s." % enabled_str(defaults.dbus_control)) 

629 else: 

630 ignore({"pulseaudio" : defaults.pulseaudio, 

631 "pulseaudio-command" : defaults.pulseaudio_command, 

632 "dbus-proxy" : defaults.dbus_proxy, 

633 "dbus-control" : defaults.dbus_control, 

634 "pulseaudio-configure-commands" : defaults.pulseaudio_configure_commands, 

635 }) 

636 

637 group = optparse.OptionGroup(parser, "Server Controlled Features", 

638 "These options be specified on the client or on the server, " 

639 "but the server's settings will have precedence over the client's.") 

640 parser.add_option_group(group) 

641 replace_option("--bwlimit", "--bandwidth-limit") 

642 group.add_option("--bandwidth-limit", action="store", 

643 dest="bandwidth_limit", default=defaults.bandwidth_limit, 

644 help="Limit the bandwidth used. The value is specified in bits per second," 

645 +" use the value '0' to disable restrictions. Default: '%default'.") 

646 legacy_bool_parse("bandwidth-detection") 

647 group.add_option("--bandwidth-detection", action="store", 

648 dest="bandwidth_detection", default=defaults.bandwidth_detection, 

649 help="Automatically detect runtime bandwidth limits. Default: '%default'.") 

650 replace_option("--readwrite", "--readonly=no") 

651 replace_option("--readonly", "--readonly=yes") 

652 group.add_option("--readonly", action="store", metavar="yes|no", 

653 dest="readonly", default=defaults.readonly, 

654 help="Disable keyboard input and mouse events from the clients. " 

655 +" Default: %s." % enabled_str(defaults.readonly)) 

656 legacy_bool_parse("clipboard") 

657 group.add_option("--clipboard", action="store", metavar="yes|no|clipboard-type", 

658 dest="clipboard", default=defaults.clipboard, 

659 help="Enable clipboard support. Default: %s." % defaults.clipboard) 

660 group.add_option("--clipboard-direction", action="store", metavar="to-server|to-client|both", 

661 dest="clipboard_direction", default=defaults.clipboard_direction, 

662 help="Direction of clipboard synchronization. Default: %s." % defaults.clipboard_direction) 

663 legacy_bool_parse("notifications") 

664 group.add_option("--notifications", action="store", metavar="yes|no", 

665 dest="notifications", default=defaults.notifications, 

666 help="Forwarding of system notifications. Default: %s." % enabled_str(defaults.notifications)) 

667 legacy_bool_parse("system-tray") 

668 group.add_option("--system-tray", action="store", metavar="yes|no", 

669 dest="system_tray", default=defaults.system_tray, 

670 help="Forward of system tray icons. Default: %s." % enabled_str(defaults.system_tray)) 

671 legacy_bool_parse("cursors") 

672 group.add_option("--cursors", action="store", metavar="yes|no", 

673 dest="cursors", default=defaults.cursors, 

674 help="Forward custom application mouse cursors. Default: %s." % enabled_str(defaults.cursors)) 

675 legacy_bool_parse("bell") 

676 group.add_option("--bell", action="store", 

677 dest="bell", default=defaults.bell, metavar="yes|no", 

678 help="Forward the system bell. Default: %s." % enabled_str(defaults.bell)) 

679 legacy_bool_parse("webcam") 

680 group.add_option("--webcam", action="store", 

681 dest="webcam", default=defaults.webcam, 

682 help="Webcam forwarding, can be used to specify a device. Default: %s." % defaults.webcam) 

683 legacy_bool_parse("mousewheel") 

684 group.add_option("--mousewheel", action="store", 

685 dest="mousewheel", default=defaults.mousewheel, 

686 help="Mouse wheel forwarding, can be used to disable the device ('no') or invert some axes " 

687 "('invert-all', 'invert-x', invert-y', 'invert-z')." 

688 +" Default: %s." % defaults.webcam) 

689 from xpra.platform.features import INPUT_DEVICES 

690 if len(INPUT_DEVICES)>1: 

691 group.add_option("--input-devices", action="store", metavar="APINAME", 

692 dest="input_devices", default=defaults.input_devices, 

693 help="Which API to use for input devices. Default: %s." % defaults.input_devices) 

694 else: 

695 ignore({"input-devices" : INPUT_DEVICES[0]}) 

696 legacy_bool_parse("global-menus") 

697 group.add_option("--global-menus", action="store", 

698 dest="global_menus", default=defaults.global_menus, metavar="yes|no", 

699 help="Forward application global menus. Default: %s." % enabled_str(defaults.global_menus)) 

700 legacy_bool_parse("xsettings") 

701 if POSIX: 

702 group.add_option("--xsettings", action="store", metavar="yes|no", 

703 dest="xsettings", default=defaults.xsettings, 

704 help="xsettings synchronization. Default: %s." % enabled_str(defaults.xsettings)) 

705 else: 

706 ignore({"xsettings" : defaults.xsettings}) 

707 legacy_bool_parse("mmap") 

708 group.add_option("--mmap", action="store", metavar="yes|no|mmap-filename", 

709 dest="mmap", default=defaults.mmap, 

710 help="Use memory mapped transfers for local connections. Default: %s." % defaults.mmap) 

711 replace_option("--enable-sharing", "--sharing=yes") 

712 legacy_bool_parse("sharing") 

713 group.add_option("--sharing", action="store", metavar="yes|no", 

714 dest="sharing", default=defaults.sharing, 

715 help="Allow more than one client to connect to the same session. " 

716 +" Default: %s." % enabled_or_auto(defaults.sharing)) 

717 legacy_bool_parse("lock") 

718 group.add_option("--lock", action="store", metavar="yes|no", 

719 dest="lock", default=defaults.lock, 

720 help="Prevent sessions from being taken over by new clients. " 

721 +" Default: %s." % enabled_or_auto(defaults.lock)) 

722 legacy_bool_parse("remote-logging") 

723 group.add_option("--remote-logging", action="store", metavar="no|send|receive|both", 

724 dest="remote_logging", default=defaults.remote_logging, 

725 help="Forward all the client's log output to the server. " 

726 +" Default: %s." % enabled_str(defaults.remote_logging)) 

727 legacy_bool_parse("speaker") 

728 legacy_bool_parse("microphone") 

729 legacy_bool_parse("av-sync") 

730 if has_sound_support(): 

731 group.add_option("--speaker", action="store", metavar="on|off|disabled", 

732 dest="speaker", default=defaults.speaker, 

733 help="Forward sound output to the client(s). Default: %s." % sound_option(defaults.speaker)) 

734 CODEC_HELP = """Specify the codec(s) to use for forwarding the %s sound output. 

735 This parameter can be specified multiple times and the order in which the codecs 

736 are specified defines the preferred codec order. 

737 Use the special value 'help' to get a list of options. 

738 When unspecified, all the available codecs are allowed and the first one is used.""" 

739 group.add_option("--speaker-codec", action="append", 

740 dest="speaker_codec", default=list(defaults.speaker_codec or []), 

741 help=CODEC_HELP % "speaker") 

742 group.add_option("--microphone", action="store", metavar="on|off|disabled", 

743 dest="microphone", default=defaults.microphone, 

744 help="Forward sound input to the server. Default: %s." % sound_option(defaults.microphone)) 

745 group.add_option("--microphone-codec", action="append", 

746 dest="microphone_codec", default=list(defaults.microphone_codec or []), 

747 help=CODEC_HELP % "microphone") 

748 group.add_option("--sound-source", action="store", 

749 dest="sound_source", default=defaults.sound_source, 

750 help="Specifies which sound system to use to capture the sound stream " 

751 +" (use 'help' for options)") 

752 group.add_option("--av-sync", action="store", 

753 dest="av_sync", default=defaults.av_sync, 

754 help="Try to synchronize sound and video. Default: %s." % enabled_str(defaults.av_sync)) 

755 else: 

756 ignore({"av-sync" : defaults.av_sync, 

757 "speaker" : defaults.speaker, 

758 "speaker-codec" : defaults.speaker_codec, 

759 "microphone" : defaults.microphone, 

760 "microphone-codec" : defaults.microphone_codec, 

761 "sound-source" : defaults.sound_source, 

762 }) 

763 

764 group = optparse.OptionGroup(parser, "Encoding and Compression Options", 

765 "These options are used by the client to specify the desired picture and network data compression." 

766 "They may also be specified on the server as default settings.") 

767 parser.add_option_group(group) 

768 group.add_option("--encodings", action="store", 

769 dest="encodings", default=defaults.encodings, 

770 help="Specify which encodings are allowed. Default: %s." % csv(defaults.encodings)) 

771 group.add_option("--encoding", action="store", 

772 metavar="ENCODING", default=defaults.encoding, 

773 dest="encoding", type="str", 

774 help="Which image compression algorithm to use, specify 'help' to get a list of options." 

775 " Default: %default." 

776 ) 

777 group.add_option("--video-encoders", action="store", 

778 dest="video_encoders", default=defaults.video_encoders, 

779 help="Specify which video encoders to enable, to get a list of all the options specify 'help'") 

780 group.add_option("--proxy-video-encoders", action="store", 

781 dest="proxy_video_encoders", default=defaults.proxy_video_encoders, 

782 help="Specify which video encoders to enable when running a proxy server," 

783 +" to get a list of all the options specify 'help'") 

784 group.add_option("--csc-modules", action="store", 

785 dest="csc_modules", default=defaults.csc_modules, 

786 help="Specify which colourspace conversion modules to enable," 

787 +" to get a list of all the options specify 'help'. Default: %default.") 

788 group.add_option("--video-decoders", action="store", 

789 dest="video_decoders", default=defaults.video_decoders, 

790 help="Specify which video decoders to enable," 

791 +" to get a list of all the options specify 'help'") 

792 group.add_option("--video-scaling", action="store", 

793 metavar="SCALING", 

794 dest="video_scaling", type="str", default=defaults.video_scaling, 

795 help="How much automatic video downscaling should be used," 

796 +" from 1 (rarely) to 100 (aggressively), 0 to disable." 

797 +" Default: %default.") 

798 group.add_option("--min-quality", action="store", 

799 metavar="MIN-LEVEL", 

800 dest="min_quality", type="int", default=defaults.min_quality, 

801 help="Sets the minimum encoding quality allowed in automatic quality setting," 

802 +" from 1 to 100, 0 to leave unset." 

803 +" Default: %default.") 

804 group.add_option("--quality", action="store", 

805 metavar="LEVEL", 

806 dest="quality", type="int", default=defaults.quality, 

807 help="Use a fixed image compression quality - only relevant for lossy encodings," 

808 +" from 1 to 100, 0 to use automatic setting." 

809 +" Default: %default.") 

810 group.add_option("--min-speed", action="store", 

811 metavar="SPEED", 

812 dest="min_speed", type="int", default=defaults.min_speed, 

813 help="Sets the minimum encoding speed allowed in automatic speed setting," 

814 "from 1 to 100, 0 to leave unset. Default: %default.") 

815 group.add_option("--speed", action="store", 

816 metavar="SPEED", 

817 dest="speed", type="int", default=defaults.speed, 

818 help="Use image compression with the given encoding speed," 

819 +" from 1 to 100, 0 to use automatic setting." 

820 +" Default: %default.") 

821 group.add_option("--auto-refresh-delay", action="store", 

822 dest="auto_refresh_delay", type="float", default=defaults.auto_refresh_delay, 

823 metavar="DELAY", 

824 help="Idle delay in seconds before doing an automatic lossless refresh." 

825 + " 0.0 to disable." 

826 + " Default: %default.") 

827 group.add_option("--compressors", action="store", 

828 dest="compressors", default=csv(defaults.compressors), 

829 help="The packet compressors to enable. Default: %default.") 

830 group.add_option("--packet-encoders", action="store", 

831 dest="packet_encoders", default=csv(defaults.packet_encoders), 

832 help="The packet encoders to enable. Default: %default.") 

833 replace_option("--compression-level", "--compression_level") 

834 replace_option("--compress", "--compression_level") 

835 group.add_option("-z", "--compression_level", action="store", 

836 dest="compression_level", type="int", default=defaults.compression_level, 

837 metavar="LEVEL", 

838 help="How hard to work on compressing packet data." 

839 + " You generally do not need to use this option," 

840 + " the default value should be adequate," 

841 + " picture data is compressed separately (see --encoding)." 

842 + " 0 to disable compression," 

843 + " 9 for maximal (slowest) compression. Default: %default.") 

844 

845 group = optparse.OptionGroup(parser, "Client Features Options", 

846 "These options control client features that affect the appearance or the keyboard.") 

847 parser.add_option_group(group) 

848 legacy_bool_parse("opengl") 

849 group.add_option("--opengl", action="store", metavar="(yes|no|auto)[:backends]", 

850 dest="opengl", default=defaults.opengl, 

851 help="Use OpenGL accelerated rendering. Default: %s." % defaults.opengl) 

852 legacy_bool_parse("splash") 

853 group.add_option("--splash", action="store", metavar="yes|no|auto", 

854 dest="splash", default=defaults.splash, 

855 help="Show a splash screen whilst loading the client. Default: %s." % enabled_or_auto(defaults.splash)) 

856 legacy_bool_parse("headerbar") 

857 group.add_option("--headerbar", action="store", metavar="auto|no|force", 

858 dest="headerbar", default=defaults.headerbar, 

859 help="Add a headerbar with menu to decorated windows. Default: %s." % defaults.headerbar) 

860 legacy_bool_parse("windows") 

861 group.add_option("--windows", action="store", metavar="yes|no", 

862 dest="windows", default=defaults.windows, 

863 help="Forward windows. Default: %s." % enabled_str(defaults.windows)) 

864 group.add_option("--session-name", action="store", 

865 dest="session_name", default=defaults.session_name, 

866 help="The name of this session, which may be used in notifications, menus, etc. Default: 'Xpra'.") 

867 group.add_option("--min-size", action="store", 

868 dest="min_size", default=defaults.min_size, 

869 metavar="MIN_SIZE", 

870 help="The minimum size for normal decorated windows, ie: 100x20. Default: '%default'.") 

871 group.add_option("--max-size", action="store", 

872 dest="max_size", default=defaults.max_size, 

873 metavar="MAX_SIZE", 

874 help="The maximum size for normal windows, ie: 800x600. Default: '%default'.") 

875 group.add_option("--desktop-scaling", action="store", 

876 dest="desktop_scaling", default=defaults.desktop_scaling, 

877 metavar="SCALING", 

878 help="How much to scale the client desktop by." 

879 " This value can be specified in the form of absolute pixels: \"WIDTHxHEIGHT\"" 

880 " as a fraction: \"3/2\" or just as a decimal number: \"1.5\"." 

881 " You can also specify each dimension individually: \"2x1.5\"." 

882 " Default: '%default'.") 

883 legacy_bool_parse("desktop-fullscreen") 

884 group.add_option("--desktop-fullscreen", action="store", 

885 dest="desktop_fullscreen", default=defaults.desktop_fullscreen, 

886 help="Make the window fullscreen if it is from a desktop or shadow server," 

887 +" scaling it to fit the screen." 

888 +" Default: '%default'.") 

889 group.add_option("--border", action="store", 

890 dest="border", default=defaults.border, 

891 help="The border to draw inside xpra windows to distinguish them from local windows." 

892 "Format: color[,size]. Default: '%default'") 

893 group.add_option("--title", action="store", 

894 dest="title", default=defaults.title, 

895 help="Text which is shown as window title, may use remote metadata variables." 

896 +" Default: '%default'.") 

897 group.add_option("--window-close", action="store", 

898 dest="window_close", default=defaults.window_close, 

899 help="The action to take when a window is closed by the client." 

900 +" Valid options are: 'forward', 'ignore', 'disconnect'." 

901 +" Default: '%default'.") 

902 group.add_option("--window-icon", action="store", 

903 dest="window_icon", default=defaults.window_icon, 

904 help="Path to the default image which will be used for all windows" 

905 +" (the application may override this)") 

906 if OSX: 

907 group.add_option("--dock-icon", action="store", 

908 dest="dock_icon", default=defaults.dock_icon, 

909 help="Path to the icon shown in the dock") 

910 do_legacy_bool_parse(cmdline, "swap-keys") 

911 group.add_option("--swap-keys", action="store", metavar="yes|no", 

912 dest="swap_keys", default=defaults.swap_keys, 

913 help="Swap the 'Command' and 'Control' keys. Default: %s" % enabled_str(defaults.swap_keys)) 

914 ignore({"tray" : defaults.tray}) 

915 ignore({"delay-tray" : defaults.delay_tray}) 

916 else: 

917 ignore({"swap-keys" : defaults.swap_keys}) 

918 ignore({"dock-icon" : defaults.dock_icon}) 

919 do_legacy_bool_parse(cmdline, "tray") 

920 if WIN32: 

921 extra_text = ", this will also disable notifications" 

922 else: 

923 extra_text = "" 

924 parser.add_option("--tray", action="store", metavar="yes|no", 

925 dest="tray", default=defaults.tray, 

926 help="Enable Xpra's own system tray menu%s." % extra_text 

927 +" Default: %s" % enabled_str(defaults.tray)) 

928 do_legacy_bool_parse(cmdline, "delay-tray") 

929 parser.add_option("--delay-tray", action="store", metavar="yes|no", 

930 dest="delay_tray", default=defaults.delay_tray, 

931 help="Waits for the first events before showing the system tray%s." % extra_text 

932 +" Default: %s" % enabled_str(defaults.delay_tray)) 

933 group.add_option("--tray-icon", action="store", 

934 dest="tray_icon", default=defaults.tray_icon, 

935 help="Path to the image which will be used as icon for the system-tray or dock") 

936 group.add_option("--shortcut-modifiers", action="store", 

937 dest="shortcut_modifiers", type="str", default=defaults.shortcut_modifiers, 

938 help="Default set of modifiers required by the key shortcuts. Default %default.") 

939 group.add_option("--key-shortcut", action="append", 

940 dest="key_shortcut", default=[], 

941 help="Define key shortcuts that will trigger specific actions." 

942 + "If no shortcuts are defined, it defaults to: \n%s" % ("\n ".join(defaults.key_shortcut or ()))) 

943 legacy_bool_parse("keyboard-sync") 

944 group.add_option("--keyboard-sync", action="store", metavar="yes|no", 

945 dest="keyboard_sync", default=defaults.keyboard_sync, 

946 help="Synchronize keyboard state. Default: %s." % enabled_str(defaults.keyboard_sync)) 

947 group.add_option("--keyboard-raw", action="store", metavar="yes|no", 

948 dest="keyboard_raw", default=defaults.keyboard_raw, 

949 help="Send raw keyboard keycodes. Default: %s." % enabled_str(defaults.keyboard_raw)) 

950 group.add_option("--keyboard-layout", action="store", metavar="LAYOUT", 

951 dest="keyboard_layout", default=defaults.keyboard_layout, 

952 help="The keyboard layout to use. Default: %default.") 

953 group.add_option("--keyboard-layouts", action="store", metavar="LAYOUTS", 

954 dest="keyboard_layouts", default=defaults.keyboard_layouts, 

955 help="The keyboard layouts to enable. Default: %s." % csv(defaults.keyboard_layouts)) 

956 group.add_option("--keyboard-variant", action="store", metavar="VARIANT", 

957 dest="keyboard_variant", default=defaults.keyboard_variant, 

958 help="The keyboard layout variant to use. Default: %default.") 

959 group.add_option("--keyboard-variants", action="store", metavar="VARIANTS", 

960 dest="keyboard_variants", default=defaults.keyboard_variant, 

961 help="The keyboard layout variants to enable. Default: %s." % csv(defaults.keyboard_variants)) 

962 group.add_option("--keyboard-options", action="store", metavar="OPTIONS", 

963 dest="keyboard_options", default=defaults.keyboard_options, 

964 help="The keyboard layout options to use. Default: %default.") 

965 

966 group = optparse.OptionGroup(parser, "SSL Options", 

967 "These options apply to both client and server. Please refer to the man page for details.") 

968 parser.add_option_group(group) 

969 group.add_option("--ssl", action="store", 

970 dest="ssl", default=defaults.ssl, 

971 help="Whether to enable SSL on TCP sockets and for what purpose (requires 'ssl-cert')." 

972 +" Default: '%s'." % enabled_str(defaults.ssl)) 

973 group.add_option("--ssl-key", action="store", 

974 dest="ssl_key", default=defaults.ssl_key, 

975 help="Key file to use." 

976 +" Default: '%default'.") 

977 group.add_option("--ssl-cert", action="store", 

978 dest="ssl_cert", default=defaults.ssl_cert, 

979 help="Certifcate file to use." 

980 +" Default: '%default'.") 

981 group.add_option("--ssl-protocol", action="store", 

982 dest="ssl_protocol", default=defaults.ssl_protocol, 

983 help="Specifies which version of the SSL protocol to use."+ 

984 " Default: '%default'.") 

985 group.add_option("--ssl-ca-certs", action="store", 

986 dest="ssl_ca_certs", default=defaults.ssl_ca_certs, 

987 help="The ca_certs file contains a set of concatenated 'certification authority' certificates," 

988 +" or you can set this to a directory containing CAs files."+ 

989 " Default: '%default'.") 

990 group.add_option("--ssl-ca-data", action="store", 

991 dest="ssl_ca_data", default=defaults.ssl_ca_data, 

992 help="PEM or DER encoded certificate data, optionally converted to hex." 

993 +" Default: '%default'.") 

994 group.add_option("--ssl-ciphers", action="store", 

995 dest="ssl_ciphers", default=defaults.ssl_ciphers, 

996 help="Sets the available ciphers, " 

997 +" it should be a string in the OpenSSL cipher list format." 

998 +" Default: '%default'.") 

999 group.add_option("--ssl-client-verify-mode", action="store", 

1000 dest="ssl_client_verify_mode", default=defaults.ssl_client_verify_mode, 

1001 help="Whether to try to verify the client's certificates" 

1002 +" and how to behave if verification fails." 

1003 +" Default: '%default'.") 

1004 group.add_option("--ssl-server-verify-mode", action="store", 

1005 dest="ssl_server_verify_mode", default=defaults.ssl_server_verify_mode, 

1006 help="Whether to try to verify the server's certificates" 

1007 +" and how to behave if verification fails. " 

1008 +" Default: '%default'.") 

1009 group.add_option("--ssl-verify-flags", action="store", 

1010 dest="ssl_verify_flags", default=defaults.ssl_verify_flags, 

1011 help="The flags for certificate verification operations." 

1012 +" Default: '%default'.") 

1013 group.add_option("--ssl-check-hostname", action="store", metavar="yes|no", 

1014 dest="ssl_check_hostname", default=defaults.ssl_check_hostname, 

1015 help="Whether to match the peer cert's hostname or accept any host, dangerous." 

1016 +" Default: '%s'." % enabled_str(defaults.ssl_check_hostname)) 

1017 group.add_option("--ssl-server-hostname", action="store", metavar="hostname", 

1018 dest="ssl_server_hostname", default=defaults.ssl_server_hostname, 

1019 help="The server hostname to match." 

1020 +" Default: '%default'.") 

1021 group.add_option("--ssl-options", action="store", metavar="options", 

1022 dest="ssl_options", default=defaults.ssl_options, 

1023 help="Set of SSL options enabled on this context." 

1024 +" Default: '%default'.") 

1025 

1026 group = optparse.OptionGroup(parser, "Advanced Options", 

1027 "These options apply to both client and server. Please refer to the man page for details.") 

1028 parser.add_option_group(group) 

1029 group.add_option("--env", action="append", 

1030 dest="env", default=list(defaults.env or []), 

1031 help="Define environment variables which will apply to this process and all subprocesses," 

1032 +" can be specified multiple times." 

1033 +" Default: %s." % csv( 

1034 ("'%s'" % x) for x in (defaults.env or []) if not x.startswith("#"))) 

1035 group.add_option("--challenge-handlers", action="append", 

1036 dest="challenge_handlers", default=[], 

1037 help="Which handlers to use for processing server authentication challenges." 

1038 +" Default: %s." % csv(defaults.challenge_handlers)) 

1039 group.add_option("--password-file", action="append", 

1040 dest="password_file", default=defaults.password_file, 

1041 help="The file containing the password required to connect" 

1042 +" (useful to secure TCP mode)." 

1043 +" Default: %s." % csv(defaults.password_file)) 

1044 group.add_option("--forward-xdg-open", action="store", 

1045 dest="forward_xdg_open", default=defaults.forward_xdg_open, 

1046 help="Intercept calls to xdg-open and forward them to the client." 

1047 +" Default: '%default'.") 

1048 group.add_option("--open-command", action="store", 

1049 dest="open_command", default=defaults.open_command, 

1050 help="Command to use to open files and URLs." 

1051 +" Default: '%default'.") 

1052 legacy_bool_parse("modal-windows") 

1053 group.add_option("--modal-windows", action="store", 

1054 dest="modal_windows", default=defaults.modal_windows, 

1055 help="Honour modal windows." 

1056 +" Default: '%default'.") 

1057 group.add_option("--input-method", action="store", 

1058 dest="input_method", default=defaults.input_method, 

1059 help="Which X11 input method to configure for client applications started with start or" 

1060 + "start-child (Default: '%default', options: none, keep, xim, IBus, SCIM, uim)") 

1061 group.add_option("--dpi", action="store", 

1062 dest="dpi", default=defaults.dpi, 

1063 help="The 'dots per inch' value that client applications should try to honour," 

1064 +" from 10 to 1000 or 0 for automatic setting." 

1065 +" Default: %s." % print_number(defaults.dpi)) 

1066 group.add_option("--pixel-depth", action="store", 

1067 dest="pixel_depth", default=defaults.pixel_depth, 

1068 help="The bits per pixel of the virtual framebuffer when starting a server" 

1069 +" (8, 16, 24 or 30), or for rendering when starting a client. " 

1070 +" Default: %s." % (defaults.pixel_depth or "0 (auto)")) 

1071 group.add_option("--sync-xvfb", action="store", 

1072 dest="sync_xvfb", default=defaults.sync_xvfb, 

1073 help="How often to synchronize the virtual framebuffer used for X11 seamless servers " 

1074 +"(0 to disable)." 

1075 +" Default: %s." % defaults.sync_xvfb) 

1076 group.add_option("--client-socket-dirs", action="store", 

1077 dest="client_socket_dirs", default=defaults.client_socket_dirs, 

1078 help="Directories where the clients create their control socket." 

1079 +" Default: %s." % os.path.pathsep.join("'%s'" % x for x in defaults.client_socket_dirs)) 

1080 group.add_option("--socket-dirs", action="store", 

1081 dest="socket_dirs", default=defaults.socket_dirs, 

1082 help="Directories to look for the socket files in." 

1083 +" Default: %s." % os.path.pathsep.join("'%s'" % x for x in defaults.socket_dirs)) 

1084 default_socket_dir_str = defaults.socket_dir or "$XPRA_SOCKET_DIR or the first valid directory in socket-dirs" 

1085 group.add_option("--socket-dir", action="store", 

1086 dest="socket_dir", default=defaults.socket_dir, 

1087 help="Directory to place/look for the socket files in. Default: '%s'." % default_socket_dir_str) 

1088 group.add_option("--system-proxy-socket", action="store", 

1089 dest="system_proxy_socket", default=defaults.system_proxy_socket, 

1090 help="The socket path to use to contact the system-wide proxy server. Default: '%default'.") 

1091 group.add_option("--ssh-upgrade", action="store", 

1092 dest="ssh_upgrade", default=defaults.ssh_upgrade, 

1093 help="Upgrade TCP sockets to handle SSH connections. Default: '%default'.") 

1094 group.add_option("--rfb-upgrade", action="store", 

1095 dest="rfb_upgrade", default=defaults.rfb_upgrade, 

1096 help="Upgrade TCP sockets to send a RFB handshake after this delay" 

1097 +" (in seconds). Default: '%default'.") 

1098 group.add_option("-d", "--debug", action="store", 

1099 dest="debug", default=defaults.debug, metavar="FILTER1,FILTER2,...", 

1100 help="List of categories to enable debugging for" 

1101 +" (you can also use \"all\" or \"help\", default: '%default')") 

1102 group.add_option("--ssh", action="store", 

1103 dest="ssh", default=defaults.ssh, metavar="CMD", 

1104 help="How to run ssh. Default: '%default'.") 

1105 legacy_bool_parse("exit-ssh") 

1106 group.add_option("--exit-ssh", action="store", metavar="yes|no|auto", 

1107 dest="exit_ssh", default=defaults.exit_ssh, 

1108 help="Terminate SSH when disconnecting. Default: %default.") 

1109 group.add_option("--username", action="store", 

1110 dest="username", default=defaults.username, 

1111 help="The username supplied by the client for authentication. Default: '%default'.") 

1112 group.add_option("--auth", action="append", 

1113 dest="auth", default=list(defaults.auth or []), 

1114 help="The authentication module to use (default: '%default')") 

1115 group.add_option("--tcp-auth", action="append", 

1116 dest="tcp_auth", default=list(defaults.tcp_auth or []), 

1117 help="The authentication module to use for TCP sockets (default: '%default')") 

1118 group.add_option("--udp-auth", action="append", 

1119 dest="udp_auth", default=list(defaults.udp_auth or []), 

1120 help="The authentication module to use for UDP sockets (default: '%default')") 

1121 group.add_option("--ws-auth", action="append", 

1122 dest="ws_auth", default=list(defaults.ws_auth or []), 

1123 help="The authentication module to use for Websockets (default: '%default')") 

1124 group.add_option("--wss-auth", action="append", 

1125 dest="wss_auth", default=list(defaults.wss_auth or []), 

1126 help="The authentication module to use for Secure Websockets (default: '%default')") 

1127 group.add_option("--ssl-auth", action="append", 

1128 dest="ssl_auth", default=list(defaults.ssl_auth or []), 

1129 help="The authentication module to use for SSL sockets (default: '%default')") 

1130 group.add_option("--ssh-auth", action="append", 

1131 dest="ssh_auth", default=list(defaults.ssh_auth or []), 

1132 help="The authentication module to use for SSH sockets (default: '%default')") 

1133 group.add_option("--rfb-auth", action="append", 

1134 dest="rfb_auth", default=list(defaults.rfb_auth or []), 

1135 help="The authentication module to use for RFB sockets (default: '%default')") 

1136 if vsock: 

1137 group.add_option("--vsock-auth", action="append", 

1138 dest="vsock_auth", default=list(defaults.vsock_auth or []), 

1139 help="The authentication module to use for vsock sockets (default: '%default')") 

1140 else: 

1141 ignore({"vsock-auth" : defaults.vsock_auth}) 

1142 group.add_option("--min-port", action="store", 

1143 dest="min_port", default=defaults.min_port, 

1144 help="The minimum port number allowed when creating UDP or TCP sockets (default: '%default')") 

1145 ignore({"password" : defaults.password}) 

1146 if POSIX: 

1147 group.add_option("--mmap-group", action="store", 

1148 dest="mmap_group", default=defaults.mmap_group, 

1149 help="When creating the mmap file with the client," 

1150 +" set the group permission on the mmap file to this group," 

1151 +" use the special value 'auto' to use the same value as the owner" 

1152 +" of the server socket file we connect to (default: '%default')") 

1153 group.add_option("--socket-permissions", action="store", 

1154 dest="socket_permissions", default=defaults.socket_permissions, 

1155 help="When creating the server unix domain socket," 

1156 +" what file access mode to use (default: '%default')") 

1157 else: 

1158 ignore({"mmap-group" : defaults.mmap_group, 

1159 "socket-permissions" : defaults.socket_permissions, 

1160 }) 

1161 

1162 replace_option("--enable-pings", "--pings=5") 

1163 group.add_option("--pings", action="store", metavar="yes|no", 

1164 dest="pings", default=defaults.pings, 

1165 help="How often to send ping packets (in seconds, use zero to disable)." 

1166 +" Default: %s." % defaults.pings) 

1167 group.add_option("--clipboard-filter-file", action="store", 

1168 dest="clipboard_filter_file", default=defaults.clipboard_filter_file, 

1169 help="Name of a file containing regular expressions of clipboard contents " 

1170 +" that must be filtered out") 

1171 group.add_option("--local-clipboard", action="store", 

1172 dest="local_clipboard", default=defaults.local_clipboard, 

1173 metavar="SELECTION", 

1174 help="Name of the local clipboard selection to be synchronized" 

1175 +" when using the translated clipboard (default: %default)") 

1176 group.add_option("--remote-clipboard", action="store", 

1177 dest="remote_clipboard", default=defaults.remote_clipboard, 

1178 metavar="SELECTION", 

1179 help="Name of the remote clipboard selection to be synchronized" 

1180 +" when using the translated clipboard (default: %default)") 

1181 group.add_option("--remote-xpra", action="store", 

1182 dest="remote_xpra", default=defaults.remote_xpra, 

1183 metavar="CMD", 

1184 help="How to run xpra on the remote host." 

1185 +" (Default: %s)" % (" or ".join(defaults.remote_xpra))) 

1186 group.add_option("--encryption", action="store", 

1187 dest="encryption", default=defaults.encryption, 

1188 metavar="ALGO", 

1189 help="Specifies the encryption cipher to use," 

1190 +" specify 'help' to get a list of options. (default: None)") 

1191 group.add_option("--encryption-keyfile", action="store", 

1192 dest="encryption_keyfile", default=defaults.encryption_keyfile, 

1193 metavar="FILE", 

1194 help="Specifies the file containing the encryption key." 

1195 +" (Default: '%default')") 

1196 group.add_option("--tcp-encryption", action="store", 

1197 dest="tcp_encryption", default=defaults.tcp_encryption, 

1198 metavar="ALGO", 

1199 help="Specifies the encryption cipher to use for TCP sockets," 

1200 +" specify 'help' to get a list of options. (default: None)") 

1201 group.add_option("--tcp-encryption-keyfile", action="store", 

1202 dest="tcp_encryption_keyfile", default=defaults.tcp_encryption_keyfile, 

1203 metavar="FILE", 

1204 help="Specifies the file containing the encryption key to use for TCP sockets." 

1205 +" (default: '%default')") 

1206 

1207 options, args = parser.parse_args(cmdline[1:]) 

1208 

1209 #ensure all the option fields are set even though 

1210 #some options are not shown to the user: 

1211 for k,v in hidden_options.items(): 

1212 if not hasattr(options, k): 

1213 setattr(options, k.replace("-", "_"), v) 

1214 

1215 #deal with boolean fields by converting them to a boolean value: 

1216 for k,t in OPTION_TYPES.items(): 

1217 if t==bool: 

1218 fieldname = name_to_field(k) 

1219 if not hasattr(options, fieldname): 

1220 #some fields may be missing if they're platform specific 

1221 continue 

1222 v = getattr(options, fieldname) 

1223 bv = parse_bool(fieldname, v) 

1224 if bv!=v: 

1225 setattr(options, fieldname, bv) 

1226 

1227 #process "help" arguments early: 

1228 options.debug = fixup_debug_option(options.debug) 

1229 if options.debug: 

1230 categories = options.debug.split(",") 

1231 for cat in categories: 

1232 if cat=="help": 

1233 h = [] 

1234 from xpra.log import STRUCT_KNOWN_FILTERS 

1235 for category, d in STRUCT_KNOWN_FILTERS.items(): 

1236 h.append("%s:" % category) 

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

1238 h.append(" * %-16s: %s" % (k,v)) 

1239 raise InitInfo("known logging filters: \n%s" % "\n".join(h)) 

1240 if options.sound_source=="help": 

1241 from xpra.sound.gstreamer_util import NAME_TO_INFO_PLUGIN 

1242 try: 

1243 from xpra.sound.wrapper import query_sound 

1244 source_plugins = query_sound().strtupleget("sources", ()) 

1245 source_default = query_sound().strget("source.default", "") 

1246 except Exception as e: 

1247 raise InitInfo(e) from None 

1248 if source_plugins: 

1249 raise InitInfo("The following audio capture plugins may be used (default: %s):\n" % source_default+ 

1250 "\n".join([" * "+p.ljust(16)+NAME_TO_INFO_PLUGIN.get(p, "") for p in source_plugins])) 

1251 raise InitInfo("No audio capture plugins found!") 

1252 

1253 #only use the default bind option if the user hasn't specified one on the command line: 

1254 if not options.bind: 

1255 #use the default: 

1256 options.bind = defaults_bind 

1257 

1258 #only use the default challenge-handlers if the user hasn't specified any: 

1259 if not options.challenge_handlers: 

1260 options.challenge_handlers = defaults.challenge_handlers 

1261 

1262 #only use the default key-shortcut list if the user hasn't specified one: 

1263 if not options.key_shortcut: 

1264 options.key_shortcut = defaults.key_shortcut 

1265 

1266 #special handling for URL mode: 

1267 #xpra attach xpra://[mode:]host:port/?param1=value1&param2=value2 

1268 if len(args)==2 and args[0]=="attach": 

1269 URL_MODES = { 

1270 "xpra" : "tcp", 

1271 "xpras" : "ssl", 

1272 "xpra+tcp" : "tcp", 

1273 "xpratcp" : "tcp", 

1274 "xpra+tls" : "ssl", 

1275 "xpratls" : "ssl", 

1276 "xpra+ssl" : "ssl", 

1277 "xprassl" : "ssl", 

1278 "xpra+ssh" : "ssh", 

1279 "xprassh" : "ssh", 

1280 "xpra+ws" : "ws", 

1281 "xpraws" : "ws", 

1282 "xpra+wss" : "wss", 

1283 "xprawss" : "wss", 

1284 } 

1285 for prefix, mode in URL_MODES.items(): 

1286 url = args[1] 

1287 fullprefix = "%s://" % prefix 

1288 if url.startswith(fullprefix): 

1289 url = "%s://%s" % (mode, url[len(fullprefix):]) 

1290 address, params = parse_URL(url) 

1291 for k,v in validate_config(params).items(): 

1292 setattr(options, k.replace("-", "_"), v) 

1293 #replace with our standard URL format, 

1294 #ie: tcp://host:port 

1295 args[1] = address 

1296 break 

1297 

1298 NEED_ENCODING_MODES = ("attach", "start", "start-desktop", "shadow", 

1299 "listen", "launcher", 

1300 "bug-report", "encoding", "gui-info") 

1301 fixup_options(options, skip_encodings=len(args)==0 or args[0] not in NEED_ENCODING_MODES) 

1302 

1303 for x in ("dpi", "sync_xvfb"): 

1304 try: 

1305 s = getattr(options, x, None) 

1306 if x=="sync_xvfb" and (s or "").lower() in FALSE_OPTIONS: 

1307 v = 0 

1308 else: 

1309 v = int(s) 

1310 setattr(options, x, v) 

1311 except Exception as e: 

1312 raise InitException("invalid value for %s: '%s': %s" % (x, s, e)) from None 

1313 

1314 def parse_window_size(v, attribute="max-size"): 

1315 try: 

1316 #split on "," or "x": 

1317 pv = tuple(int(x.strip()) for x in v.replace(",", "x").split("x", 1)) 

1318 assert len(pv)==2 

1319 w, h = pv 

1320 assert 0<w<32768 and 0<h<32768 

1321 return w, h 

1322 except: 

1323 raise InitException("invalid %s: %s" % (attribute, v)) from None 

1324 if options.min_size: 

1325 options.min_size = "%sx%s" % parse_window_size(options.min_size, "min-size") 

1326 if options.max_size: 

1327 options.max_size = "%sx%s" % parse_window_size(options.max_size, "max-size") 

1328 if options.encryption_keyfile and not options.encryption: 

1329 options.encryption = "AES" 

1330 if options.tcp_encryption_keyfile and not options.tcp_encryption: 

1331 options.tcp_encryption = "AES" 

1332 return options, args 

1333 

1334def validated_encodings(encodings): 

1335 try: 

1336 from xpra.codecs.codec_constants import PREFERRED_ENCODING_ORDER 

1337 except ImportError: 

1338 return [] 

1339 lower_encodings = [x.lower() for x in encodings] 

1340 validated = [x for x in PREFERRED_ENCODING_ORDER if x.lower() in lower_encodings] 

1341 if not validated: 

1342 raise InitException("no valid encodings specified") 

1343 return validated 

1344 

1345def validate_encryption(opts): 

1346 do_validate_encryption(opts.auth, opts.tcp_auth, 

1347 opts.encryption, opts.tcp_encryption, opts.encryption_keyfile, opts.tcp_encryption_keyfile) 

1348 

1349def do_validate_encryption(auth, tcp_auth, 

1350 encryption, tcp_encryption, encryption_keyfile, tcp_encryption_keyfile): 

1351 if not encryption and not tcp_encryption: 

1352 #don't bother initializing anything 

1353 return 

1354 from xpra.net.crypto import crypto_backend_init 

1355 crypto_backend_init() 

1356 env_key = os.environ.get("XPRA_ENCRYPTION_KEY") 

1357 pass_key = os.environ.get("XPRA_PASSWORD") 

1358 from xpra.net.crypto import ENCRYPTION_CIPHERS 

1359 if not ENCRYPTION_CIPHERS: 

1360 raise InitException("cannot use encryption: no ciphers available (the python-cryptography library must be installed)") 

1361 if encryption=="help" or tcp_encryption=="help": 

1362 raise InitInfo("the following encryption ciphers are available: %s" % csv(ENCRYPTION_CIPHERS)) 

1363 if encryption and encryption not in ENCRYPTION_CIPHERS: 

1364 raise InitException("encryption %s is not supported, try: %s" % (encryption, csv(ENCRYPTION_CIPHERS))) 

1365 if tcp_encryption and tcp_encryption not in ENCRYPTION_CIPHERS: 

1366 raise InitException("encryption %s is not supported, try: %s" % (tcp_encryption, csv(ENCRYPTION_CIPHERS))) 

1367 if encryption and not encryption_keyfile and not env_key and not auth: 

1368 raise InitException("encryption %s cannot be used without an authentication module or keyfile" 

1369 +" (see --encryption-keyfile option)" % encryption) 

1370 if tcp_encryption and not tcp_encryption_keyfile and not env_key and not tcp_auth: 

1371 raise InitException("tcp-encryption %s cannot be used " % tcp_encryption+ 

1372 "without a tcp authentication module or keyfile " 

1373 +" (see --tcp-encryption-keyfile option)") 

1374 if pass_key and env_key and pass_key==env_key: 

1375 raise InitException("encryption and authentication should not use the same value") 

1376 #discouraged but not illegal: 

1377 #if password_file and encryption_keyfile and password_file==encryption_keyfile: 

1378 # if encryption: 

1379 # raise InitException("encryption %s should not use the same file" 

1380 # +" as the password authentication file" % encryption) 

1381 # elif tcp_encryption: 

1382 # raise InitException("tcp-encryption %s should not use the same file" 

1383 # +" as the password authentication file" % tcp_encryption) 

1384 

1385def show_sound_codec_help(is_server, speaker_codecs, microphone_codecs): 

1386 from xpra.sound.wrapper import query_sound 

1387 props = query_sound() 

1388 if not props: 

1389 return ["sound is not supported - gstreamer not present or not accessible"] 

1390 codec_help = [] 

1391 all_speaker_codecs = props.strtupleget("encoders" if is_server else "decoders") 

1392 invalid_sc = [x for x in speaker_codecs if x not in all_speaker_codecs] 

1393 hs = "help" in speaker_codecs 

1394 if hs: 

1395 codec_help.append("speaker codecs available: %s" % csv(all_speaker_codecs)) 

1396 elif invalid_sc: 

1397 codec_help.append("WARNING: some of the specified speaker codecs are not available: %s" % csv(invalid_sc)) 

1398 

1399 all_microphone_codecs = props.strtupleget("decoders" if is_server else "encoders") 

1400 invalid_mc = [x for x in microphone_codecs if x not in all_microphone_codecs] 

1401 hm = "help" in microphone_codecs 

1402 if hm: 

1403 codec_help.append("microphone codecs available: %s" % csv(all_microphone_codecs)) 

1404 elif invalid_mc: 

1405 codec_help.append("WARNING: some of the specified microphone codecs are not available:" 

1406 +" %s" % csv(invalid_mc)) 

1407 return codec_help 

1408 

1409 

1410def parse_vsock(vsock_str): 

1411 from xpra.net.vsock import STR_TO_CID, CID_ANY, PORT_ANY #@UnresolvedImport 

1412 if not vsock_str.find(":")>=0: 

1413 raise InitException("invalid vsocket format '%s'" % vsock_str) 

1414 cid_str, port_str = vsock_str.split(":", 1) 

1415 if cid_str.lower() in ("auto", "any"): 

1416 cid = CID_ANY 

1417 else: 

1418 try: 

1419 cid = int(cid_str) 

1420 except ValueError: 

1421 cid = STR_TO_CID.get(cid_str.upper()) # @UndefinedVariable 

1422 if cid is None: 

1423 raise InitException("invalid vsock cid '%s'" % cid_str) from None 

1424 if port_str.lower() in ("auto", "any"): 

1425 iport = PORT_ANY 

1426 else: 

1427 try: 

1428 iport = int(port_str) 

1429 except ValueError: 

1430 raise InitException("invalid vsock port '%s'" % port_str) from None 

1431 return cid, iport 

1432 

1433 

1434def is_local(host) -> bool: 

1435 return host.lower() in ("localhost", "127.0.0.1", "::1")