Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# This file is part of Xpra. 

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

3# Copyright (C) 2008, 2010 Nathaniel Smith <njs@pobox.com> 

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

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

6 

7import os.path 

8import sys 

9 

10from gi.repository import GLib 

11from gi.repository import GObject 

12 

13from xpra.util import ( 

14 nonl, sorted_nicely, print_nested_dict, envint, flatten_dict, typedict, 

15 disconnect_is_an_error, ellipsizer, DONE, first_time, 

16 ) 

17from xpra.os_util import bytestostr, strtobytes, get_hex_uuid, POSIX, OSX, hexstr 

18from xpra.simple_stats import std_unit 

19from xpra.client.client_base import XpraClientBase, EXTRA_TIMEOUT 

20from xpra.exit_codes import ( 

21 EXIT_OK, EXIT_CONNECTION_LOST, EXIT_TIMEOUT, EXIT_INTERNAL_ERROR, 

22 EXIT_FAILURE, EXIT_UNSUPPORTED, EXIT_REMOTE_ERROR, EXIT_FILE_TOO_BIG, 

23 ) 

24from xpra.log import Logger 

25 

26log = Logger("gobject", "client") 

27 

28FLATTEN_INFO = envint("XPRA_FLATTEN_INFO", 1) 

29 

30 

31def errwrite(msg): 

32 try: 

33 sys.stderr.write(msg) 

34 sys.stderr.flush() 

35 except (OSError, AttributeError): 

36 pass 

37 

38 

39class GObjectXpraClient(GObject.GObject, XpraClientBase): 

40 """ 

41 Utility superclass for GObject clients 

42 """ 

43 COMMAND_TIMEOUT = EXTRA_TIMEOUT 

44 

45 def __init__(self): 

46 self.idle_add = GLib.idle_add 

47 self.timeout_add = GLib.timeout_add 

48 self.source_remove = GLib.source_remove 

49 GObject.GObject.__init__(self) 

50 XpraClientBase.__init__(self) 

51 

52 def init(self, opts): 

53 XpraClientBase.init(self, opts) 

54 self.glib_init() 

55 

56 def get_scheduler(self): 

57 return GLib 

58 

59 

60 def install_signal_handlers(self): 

61 from xpra.gtk_common.gobject_compat import install_signal_handlers 

62 install_signal_handlers("%s Client" % self.client_type(), self.handle_app_signal) 

63 

64 

65 def setup_connection(self, conn): 

66 protocol = super().setup_connection(conn) 

67 protocol._log_stats = False 

68 GLib.idle_add(self.send_hello) 

69 return protocol 

70 

71 

72 def client_type(self): 

73 #overriden in subclasses! 

74 return "Python3/GObject" 

75 

76 

77 def init_packet_handlers(self): 

78 XpraClientBase.init_packet_handlers(self) 

79 def noop(*args): # pragma: no cover 

80 log("ignoring packet: %s", args) 

81 #ignore the following packet types without error: 

82 #(newer servers should avoid sending us any of those) 

83 for t in ( 

84 "new-window", "new-override-redirect", 

85 "draw", "cursor", "bell", 

86 "notify_show", "notify_close", 

87 "ping", "ping_echo", 

88 "window-metadata", "configure-override-redirect", 

89 "lost-window", 

90 ): 

91 self._packet_handlers[t] = noop 

92 

93 def run(self): 

94 XpraClientBase.run(self) 

95 self.glib_mainloop = GLib.MainLoop() 

96 self.glib_mainloop.run() 

97 return self.exit_code 

98 

99 def make_hello(self): 

100 capabilities = XpraClientBase.make_hello(self) 

101 capabilities["keyboard"] = False 

102 return capabilities 

103 

104 def quit(self, exit_code): 

105 log("quit(%s) current exit_code=%s", exit_code, self.exit_code) 

106 if self.exit_code is None: 

107 self.exit_code = exit_code 

108 self.cleanup() 

109 GLib.timeout_add(50, self.exit_loop) 

110 

111 def exit_loop(self): 

112 self.glib_mainloop.quit() 

113 

114 

115class CommandConnectClient(GObjectXpraClient): 

116 """ 

117 Utility superclass for clients that only send one command 

118 via the hello packet. 

119 """ 

120 

121 def __init__(self, opts): 

122 super().__init__() 

123 super().init(opts) 

124 self.display_desc = {} 

125 #not used by command line clients, 

126 #so don't try probing for printers, etc 

127 self.file_transfer = False 

128 self.printing = False 

129 self.command_timeout = None 

130 #don't bother with many of these things for one-off commands: 

131 for x in ("ui_client", "wants_aliases", "wants_encodings", 

132 "wants_versions", "wants_features", "wants_sound", "windows", 

133 "webcam", "keyboard", "mouse", "network-state", 

134 ): 

135 self.hello_extra[x] = False 

136 

137 def setup_connection(self, conn): 

138 protocol = super().setup_connection(conn) 

139 if conn.timeout>0: 

140 self.command_timeout = GLib.timeout_add((conn.timeout + self.COMMAND_TIMEOUT) * 1000, self.timeout) 

141 return protocol 

142 

143 def timeout(self, *_args): 

144 log.warn("timeout!") # pragma: no cover 

145 

146 def cancel_command_timeout(self): 

147 ct = self.command_timeout 

148 if ct: 

149 self.command_timeout = None 

150 GLib.source_remove(ct) 

151 

152 def _process_connection_lost(self, _packet): 

153 #override so we don't log a warning 

154 #"command clients" are meant to exit quickly by losing the connection 

155 p = self._protocol 

156 if p and p.input_packetcount==0: 

157 self.quit(EXIT_CONNECTION_LOST) 

158 else: 

159 self.quit(EXIT_OK) 

160 

161 def server_connection_established(self, caps : typedict): 

162 #don't bother parsing the network caps: 

163 #* it could cause errors if the caps are missing 

164 #* we don't care about sending anything back after hello 

165 log("server_capabilities: %s", ellipsizer(caps)) 

166 log("protocol state: %s", self._protocol.save_state()) 

167 self.cancel_command_timeout() 

168 self.do_command(caps) 

169 return True 

170 

171 def do_command(self, caps : typedict): 

172 raise NotImplementedError() 

173 

174 

175class SendCommandConnectClient(CommandConnectClient): 

176 """ 

177 Utility superclass for clients that only send at least one more packet 

178 after the hello packet. 

179 So unlike CommandConnectClient, we do need the network and encryption to be setup. 

180 """ 

181 

182 def server_connection_established(self, caps): 

183 assert self.parse_encryption_capabilities(caps), "encryption failure" 

184 assert self.parse_network_capabilities(caps), "network capabilities failure" 

185 return super().server_connection_established(caps) 

186 

187 

188class HelloRequestClient(SendCommandConnectClient): 

189 """ 

190 Utility superclass for clients that send a server request 

191 as part of the hello packet. 

192 """ 

193 

194 def make_hello_base(self): 

195 caps = super().make_hello_base() 

196 caps.update(self.hello_request()) 

197 return caps 

198 

199 def timeout(self, *_args): 

200 self.warn_and_quit(EXIT_TIMEOUT, "timeout: server did not disconnect us") 

201 

202 def hello_request(self): # pragma: no cover 

203 raise NotImplementedError() 

204 

205 def do_command(self, caps : typedict): 

206 self.quit(EXIT_OK) 

207 

208 def _process_disconnect(self, packet): 

209 #overriden method so we can avoid printing a warning, 

210 #we haven't received the hello back from the server 

211 #but that's fine for a request client 

212 info = tuple(nonl(bytestostr(x)) for x in packet[1:]) 

213 reason = info[0] 

214 if disconnect_is_an_error(reason): 

215 self.server_disconnect_warning(*info) 

216 elif self.exit_code is None: 

217 #we're not in the process of exiting already, 

218 #tell the user why the server is disconnecting us 

219 self.server_disconnect(*info) 

220 

221 

222class ScreenshotXpraClient(CommandConnectClient): 

223 """ This client does one thing only: 

224 it sends the hello packet with a screenshot request 

225 and exits when the resulting image is received (or timedout) 

226 """ 

227 

228 def __init__(self, opts, screenshot_filename): 

229 self.screenshot_filename = screenshot_filename 

230 super().__init__(opts) 

231 self.hello_extra["screenshot_request"] = True 

232 self.hello_extra["request"] = "screenshot" 

233 

234 def timeout(self, *_args): 

235 self.warn_and_quit(EXIT_TIMEOUT, "timeout: did not receive the screenshot") 

236 

237 def _process_screenshot(self, packet): 

238 (w, h, encoding, _, img_data) = packet[1:6] 

239 assert encoding==b"png", "expected png screenshot data but got %s" % bytestostr(encoding) 

240 if not img_data: 

241 self.warn_and_quit(EXIT_OK, 

242 "screenshot is empty and has not been saved (maybe there are no windows or they are not currently shown)") 

243 return 

244 if self.screenshot_filename=="-": 

245 output = os.fdopen(sys.stdout.fileno(), "wb", closefd=False) 

246 else: 

247 output = open(self.screenshot_filename, "wb") 

248 with output: 

249 output.write(img_data) 

250 output.flush() 

251 self.warn_and_quit(EXIT_OK, "screenshot %sx%s saved to: %s" % (w, h, self.screenshot_filename)) 

252 

253 def init_packet_handlers(self): 

254 super().init_packet_handlers() 

255 self._ui_packet_handlers["screenshot"] = self._process_screenshot 

256 

257 

258class InfoXpraClient(CommandConnectClient): 

259 """ This client does one thing only: 

260 it queries the server with an 'info' request 

261 """ 

262 

263 def __init__(self, opts): 

264 super().__init__(opts) 

265 self.hello_extra["info_request"] = True 

266 self.hello_extra["request"] = "info" 

267 if FLATTEN_INFO>=1: 

268 self.hello_extra["info-namespace"] = True 

269 

270 def timeout(self, *_args): 

271 self.warn_and_quit(EXIT_TIMEOUT, "timeout: did not receive the info") 

272 

273 def do_command(self, caps : typedict): 

274 if caps: 

275 if FLATTEN_INFO<2: 

276 #compatibility mode: 

277 c = flatten_dict(caps) 

278 for k in sorted_nicely(c.keys()): 

279 v = c.get(k) 

280 #FIXME: this is a nasty and horrible python3 workaround (yet again) 

281 #we want to print bytes as strings without the ugly 'b' prefix.. 

282 #it assumes that all the strings are raw or in (possibly nested) lists or tuples only 

283 #we assume that all strings we get are utf-8, 

284 #and fallback to the bytestostr hack if that fails 

285 def fixvalue(w): 

286 if isinstance(w, bytes): 

287 if k.endswith(".data"): 

288 return hexstr(w) 

289 try: 

290 return w.decode("utf-8") 

291 except: 

292 return bytestostr(w) 

293 elif isinstance(w, (tuple,list)): 

294 return type(w)([fixvalue(x) for x in w]) 

295 return w 

296 v = fixvalue(v) 

297 k = fixvalue(k) 

298 log.info("%s=%s", k, nonl(v)) 

299 else: 

300 print_nested_dict(caps) 

301 self.quit(EXIT_OK) 

302 

303class IDXpraClient(InfoXpraClient): 

304 

305 def __init__(self, *args): 

306 super().__init__(*args) 

307 self.hello_extra["request"] = "id" 

308 

309 

310class ConnectTestXpraClient(CommandConnectClient): 

311 """ This client does one thing only: 

312 it queries the server with an 'info' request 

313 """ 

314 

315 def __init__(self, opts, **kwargs): 

316 super().__init__(opts) 

317 self.value = get_hex_uuid() 

318 self.hello_extra.update({ 

319 "connect_test_request" : self.value, 

320 "request" : "connect_test", 

321 #tells proxy servers we don't want to connect to the real / new instance: 

322 "connect" : False, 

323 #older servers don't know about connect-test, 

324 #pretend that we're interested in info: 

325 "info_request" : True, 

326 "info-namespace" : True, 

327 }) 

328 self.hello_extra.update(kwargs) 

329 

330 def timeout(self, *_args): 

331 self.warn_and_quit(EXIT_TIMEOUT, "timeout: no server response") 

332 

333 def _process_connection_lost(self, _packet): 

334 #we should always receive a hello back and call do_command, 

335 #which sets the correct exit code, landing here is an error: 

336 self.quit(EXIT_FAILURE) 

337 

338 def do_command(self, caps : typedict): 

339 if caps: 

340 ctr = caps.strget("connect_test_response") 

341 log("do_command(..) expected connect test response='%s', got '%s'", self.value, ctr) 

342 if ctr==self.value: 

343 self.quit(EXIT_OK) 

344 else: 

345 self.quit(EXIT_INTERNAL_ERROR) 

346 else: 

347 self.quit(EXIT_FAILURE) 

348 

349 

350class MonitorXpraClient(SendCommandConnectClient): 

351 """ This client does one thing only: 

352 it prints out events received from the server. 

353 If the server does not support this feature it exits with an error. 

354 """ 

355 

356 def __init__(self, opts): 

357 super().__init__(opts) 

358 for x in ("wants_features", "wants_events", "event_request"): 

359 self.hello_extra[x] = True 

360 self.hello_extra["request"] = "event" 

361 self.hello_extra["info-namespace"] = True 

362 

363 def timeout(self, *args): 

364 pass 

365 #self.warn_and_quit(EXIT_TIMEOUT, "timeout: did not receive the info") 

366 

367 def do_command(self, caps : typedict): 

368 log.info("waiting for server events") 

369 

370 def _process_server_event(self, packet): 

371 log.info(": ".join(bytestostr(x) for x in packet[1:])) 

372 

373 def init_packet_handlers(self): 

374 super().init_packet_handlers() 

375 self._packet_handlers["server-event"] = self._process_server_event 

376 self._packet_handlers["ping"] = self._process_ping 

377 

378 def _process_ping(self, packet): 

379 echotime = packet[1] 

380 self.send("ping_echo", echotime, 0, 0, 0, -1) 

381 

382 

383class ShellXpraClient(SendCommandConnectClient): 

384 """ 

385 Provides an interactive shell with the socket it connects to 

386 """ 

387 

388 def __init__(self, opts): 

389 super().__init__(opts) 

390 self.stdin_io_watch = None 

391 self.stdin_buffer = "" 

392 self.hello_extra["shell"] = "True" 

393 

394 def timeout(self, *args): 

395 pass 

396 

397 def cleanup(self): 

398 siw = self.stdin_io_watch 

399 if siw: 

400 self.stdin_io_watch = None 

401 self.source_remove(siw) 

402 super().cleanup() 

403 

404 def do_command(self, caps : typedict): 

405 if not caps.boolget("shell"): 

406 msg = "this server does not support the 'shell' subcommand" 

407 log.error(msg) 

408 self.disconnect_and_quit(EXIT_UNSUPPORTED, msg) 

409 return 

410 #start reading from stdin: 

411 self.install_signal_handlers() 

412 stdin = sys.stdin 

413 fileno = stdin.fileno() 

414 import fcntl 

415 fl = fcntl.fcntl(fileno, fcntl.F_GETFL) 

416 fcntl.fcntl(fileno, fcntl.F_SETFL, fl | os.O_NONBLOCK) 

417 self.stdin_io_watch = GLib.io_add_watch(sys.stdin, 

418 GLib.PRIORITY_DEFAULT, GLib.IO_IN, 

419 self.stdin_ready) 

420 self.print_prompt() 

421 

422 def stdin_ready(self, *_args): 

423 data = sys.stdin.read() 

424 #log.warn("stdin=%r", data) 

425 self.stdin_buffer += data 

426 sent = 0 

427 if self.stdin_buffer.endswith("\n"): 

428 for line in self.stdin_buffer.splitlines(): 

429 if line: 

430 if line.rstrip("\n\r") in ("quit", "exit"): 

431 self.disconnect_and_quit(EXIT_OK, "user requested %s" % line) 

432 self.stdin_io_watch = None 

433 return False 

434 self.send("shell-exec", line.encode()) 

435 sent += 1 

436 self.stdin_buffer = "" 

437 if not sent: 

438 self.print_prompt() 

439 return True 

440 

441 def init_packet_handlers(self): 

442 super().init_packet_handlers() 

443 self._packet_handlers["shell-reply"] = self._process_shell_reply 

444 self._packet_handlers["ping"] = self._process_ping 

445 

446 def _process_ping(self, packet): 

447 echotime = packet[1] 

448 self.send("ping_echo", echotime, 0, 0, 0, -1) 

449 

450 def _process_shell_reply(self, packet): 

451 fd = packet[1] 

452 message = packet[2] 

453 if fd==1: 

454 stream = sys.stdout 

455 elif fd==2: 

456 stream = sys.stderr 

457 else: 

458 raise Exception("invalid file descriptor %i" % fd) 

459 s = message.decode("utf8") 

460 if s.endswith("\n"): 

461 s = s[:-1] 

462 stream.write("%s" % s) 

463 stream.flush() 

464 if fd==2: 

465 stream.write("\n") 

466 self.print_prompt() 

467 

468 def print_prompt(self): 

469 sys.stdout.write("> ") 

470 sys.stdout.flush() 

471 

472 

473class VersionXpraClient(HelloRequestClient): 

474 """ This client does one thing only: 

475 it queries the server for version information and prints it out 

476 """ 

477 

478 def hello_request(self): 

479 return { 

480 "version_request" : True, 

481 "request" : "version", 

482 "full-version-request" : True, 

483 } 

484 

485 def parse_network_capabilities(self, *_args): 

486 #don't bother checking anything - this could generate warnings 

487 return True 

488 

489 def do_command(self, caps : typedict): 

490 v = caps.strget(b"version") 

491 if not v: 

492 self.warn_and_quit(EXIT_FAILURE, "server did not provide the version information") 

493 else: 

494 sys.stdout.write("%s\n" % (v,)) 

495 sys.stdout.flush() 

496 self.quit(EXIT_OK) 

497 

498 

499class ControlXpraClient(CommandConnectClient): 

500 """ Allows us to send commands to a server. 

501 """ 

502 def set_command_args(self, command): 

503 self.command = command 

504 

505 def timeout(self, *_args): 

506 self.warn_and_quit(EXIT_TIMEOUT, "timeout: server did not respond") 

507 

508 def do_command(self, caps : typedict): 

509 cr = caps.tupleget("command_response") 

510 if cr is None: 

511 self.warn_and_quit(EXIT_UNSUPPORTED, "server does not support control command") 

512 return 

513 code, text = cr 

514 text = bytestostr(text) 

515 if code!=0: 

516 log.warn("server returned error code %s", code) 

517 self.warn_and_quit(EXIT_REMOTE_ERROR, " %s" % text) 

518 return 

519 self.warn_and_quit(EXIT_OK, text) 

520 

521 def make_hello(self): 

522 capabilities = super().make_hello() 

523 log("make_hello() adding command request '%s' to %s", self.command, capabilities) 

524 def b(s): 

525 try: 

526 return s.encode("utf8") 

527 except: 

528 return strtobytes(s) 

529 capabilities["command_request"] = tuple(b(x) for x in self.command) 

530 capabilities["request"] = "command" 

531 return capabilities 

532 

533 

534class PrintClient(SendCommandConnectClient): 

535 """ Allows us to send a file to the server for printing. 

536 """ 

537 def set_command_args(self, command): 

538 log("set_command_args(%s)", command) 

539 self.filename = command[0] 

540 #print command arguments: 

541 #filename, file_data, mimetype, source_uuid, title, printer, no_copies, print_options_str = packet[1:9] 

542 self.command = command[1:] 

543 #TODO: load as needed... 

544 def sizeerr(size): 

545 self.warn_and_quit(EXIT_FILE_TOO_BIG, 

546 "the file is too large: %sB (the file size limit is %sB)" % ( 

547 std_unit(size), std_unit(self.file_size_limit))) 

548 return 

549 if self.filename=="-": 

550 #replace with filename proposed 

551 self.filename = command[2] 

552 #read file from stdin 

553 with open(sys.stdin.fileno(), mode='rb', closefd=False) as stdin_binary: 

554 self.file_data = stdin_binary.read() 

555 log("read %i bytes from stdin", len(self.file_data)) 

556 else: 

557 size = os.path.getsize(self.filename) 

558 if size>self.file_size_limit: 

559 sizeerr(size) 

560 return 

561 from xpra.os_util import load_binary_file 

562 self.file_data = load_binary_file(self.filename) 

563 log("read %i bytes from %s", len(self.file_data), self.filename) 

564 size = len(self.file_data) 

565 if size>self.file_size_limit: 

566 sizeerr(size) 

567 return 

568 assert self.file_data, "no data found for '%s'" % self.filename 

569 

570 def client_type(self): 

571 return "Python/GObject/Print" 

572 

573 def timeout(self, *_args): 

574 self.warn_and_quit(EXIT_TIMEOUT, "timeout: server did not respond") 

575 

576 def do_command(self, caps : typedict): 

577 printing = caps.boolget("printing") 

578 if not printing: 

579 self.warn_and_quit(EXIT_UNSUPPORTED, "server does not support printing") 

580 return 

581 #TODO: compress file data? (this should run locally most of the time anyway) 

582 from xpra.net.compression import Compressed 

583 blob = Compressed("print", self.file_data) 

584 self.send("print", self.filename, blob, *self.command) 

585 log("print: sending %s as %s for printing", self.filename, blob) 

586 self.idle_add(self.send, "disconnect", DONE, "detaching") 

587 

588 def make_hello(self): 

589 capabilities = super().make_hello() 

590 capabilities["wants_features"] = True #so we know if printing is supported or not 

591 capabilities["print_request"] = True #marker to skip full setup 

592 capabilities["request"] = "print" 

593 return capabilities 

594 

595 

596class ExitXpraClient(HelloRequestClient): 

597 """ This client does one thing only: 

598 it asks the server to terminate (like stop), 

599 but without killing the Xvfb or clients. 

600 """ 

601 

602 def hello_request(self): 

603 return { 

604 "exit_request" : True, 

605 "request" : "exit", 

606 } 

607 

608 def do_command(self, caps : typedict): 

609 self.idle_add(self.send, "exit-server") 

610 

611 

612class StopXpraClient(HelloRequestClient): 

613 """ stop a server """ 

614 

615 def hello_request(self): 

616 return { 

617 "stop_request" : True, 

618 "request" : "stop", 

619 } 

620 

621 def do_command(self, caps : typedict): 

622 if not self.server_client_shutdown: 

623 log.error("Error: cannot shutdown this server") 

624 log.error(" the feature is disable on the server") 

625 self.quit(EXIT_FAILURE) 

626 return 

627 self.timeout_add(1000, self.send_shutdown_server) 

628 #self.idle_add(self.send_shutdown_server) 

629 #not exiting the client here, 

630 #the server should send us the shutdown disconnection message anyway 

631 #and if not, we will then hit the timeout to tell us something went wrong 

632 

633 

634class DetachXpraClient(HelloRequestClient): 

635 """ run the detach subcommand """ 

636 

637 def hello_request(self): 

638 return { 

639 "detach_request" : True, 

640 "request" : "detach", 

641 } 

642 

643 def do_command(self, caps : typedict): 

644 self.idle_add(self.send, "disconnect", DONE, "detaching") 

645 #not exiting the client here, 

646 #the server should disconnect us with the response 

647 

648class WaitForDisconnectXpraClient(DetachXpraClient): 

649 """ we just want the connection to close """ 

650 

651 def _process_disconnect(self, _packet): 

652 self.quit(EXIT_OK) 

653 

654 

655class RequestStartClient(HelloRequestClient): 

656 """ request the system proxy server to start a new session for us """ 

657 #wait longer for this command to return: 

658 from xpra.scripts.main import WAIT_SERVER_TIMEOUT 

659 COMMAND_TIMEOUT = EXTRA_TIMEOUT+WAIT_SERVER_TIMEOUT 

660 

661 def dots(self): 

662 errwrite(".") 

663 return not self.connection_established 

664 

665 def _process_connection_lost(self, packet): 

666 errwrite("\n") 

667 super()._process_connection_lost(packet) 

668 

669 def hello_request(self): 

670 if first_time("hello-request"): 

671 #this can be called again if we receive a challenge, 

672 #but only print this message once: 

673 errwrite("requesting new session, please wait") 

674 self.timeout_add(1*1000, self.dots) 

675 return { 

676 "start-new-session" : self.start_new_session, 

677 #tells proxy servers we don't want to connect to the real / new instance: 

678 "connect" : False, 

679 } 

680 

681 def server_connection_established(self, caps : typedict): 

682 #the server should respond with the display chosen 

683 log("server_connection_established() exit_code=%s", self.exit_code) 

684 display = caps.strget("display") 

685 if display: 

686 mode = caps.strget("mode") 

687 session_type = { 

688 "start" : "seamless ", 

689 "start-desktop" : "desktop ", 

690 "shadow" : "shadow ", 

691 }.get(mode, "") 

692 try: 

693 errwrite("\n%ssession now available on display %s\n" % (session_type, display)) 

694 if POSIX and not OSX and self.displayfd>0 and display and display.startswith(b":"): 

695 from xpra.platform.displayfd import write_displayfd 

696 log("writing display %s to displayfd=%s", display, self.displayfd) 

697 write_displayfd(self.displayfd, display[1:]) 

698 except OSError: 

699 log("server_connection_established(..)", exc_info=True) 

700 if not self.exit_code: 

701 self.quit(0) 

702 return True 

703 

704 def __init__(self, opts): 

705 super().__init__(opts) 

706 try: 

707 self.displayfd = int(opts.displayfd) 

708 except (ValueError, TypeError): 

709 self.displayfd = 0