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 9from time import monotonic 10 11from gi.repository import GLib 12from gi.repository import GObject 13 14from xpra.util import ( 15 u, net_utf8, nonl, sorted_nicely, print_nested_dict, envint, flatten_dict, typedict, 16 disconnect_is_an_error, ellipsizer, first_time, csv, 17 repr_ellipsized, 18 SERVER_EXIT, DONE, 19 ) 20from xpra.os_util import ( 21 bytestostr, 22 get_hex_uuid, hexstr, 23 POSIX, OSX, 24 ) 25from xpra.simple_stats import std_unit 26from xpra.client.client_base import XpraClientBase, EXTRA_TIMEOUT 27from xpra.exit_codes import ( 28 EXIT_OK, EXIT_CONNECTION_LOST, EXIT_TIMEOUT, EXIT_INTERNAL_ERROR, 29 EXIT_FAILURE, EXIT_UNSUPPORTED, EXIT_REMOTE_ERROR, EXIT_FILE_TOO_BIG, 30 EXIT_IO_ERROR, EXIT_NO_DATA, 31 ) 32from xpra.log import Logger 33 34log = Logger("gobject", "client") 35 36FLATTEN_INFO = envint("XPRA_FLATTEN_INFO", 1) 37 38 39def errwrite(msg): 40 try: 41 sys.stderr.write(msg) 42 sys.stderr.flush() 43 except (OSError, AttributeError): 44 pass 45 46 47class GObjectXpraClient(GObject.GObject, XpraClientBase): 48 """ 49 Utility superclass for GObject clients 50 """ 51 COMMAND_TIMEOUT = EXTRA_TIMEOUT 52 53 def __init__(self): 54 self.idle_add = GLib.idle_add 55 self.timeout_add = GLib.timeout_add 56 self.source_remove = GLib.source_remove 57 GObject.GObject.__init__(self) 58 XpraClientBase.__init__(self) 59 60 def init(self, opts): 61 XpraClientBase.init(self, opts) 62 self.glib_init() 63 64 def get_scheduler(self): 65 return GLib 66 67 68 def install_signal_handlers(self): 69 from xpra.gtk_common.gobject_compat import install_signal_handlers 70 install_signal_handlers("%s Client" % self.client_type(), self.handle_app_signal) 71 72 73 def setup_connection(self, conn): 74 protocol = super().setup_connection(conn) 75 protocol._log_stats = False 76 GLib.idle_add(self.send_hello) 77 return protocol 78 79 80 def client_type(self): 81 #overriden in subclasses! 82 return "Python3/GObject" 83 84 85 def init_packet_handlers(self): 86 XpraClientBase.init_packet_handlers(self) 87 def noop(*args): # pragma: no cover 88 log("ignoring packet: %s", args) 89 #ignore the following packet types without error: 90 #(newer servers should avoid sending us any of those) 91 for t in ( 92 "new-window", "new-override-redirect", 93 "draw", "cursor", "bell", 94 "notify_show", "notify_close", 95 "ping", "ping_echo", 96 "window-metadata", "configure-override-redirect", 97 "lost-window", 98 ): 99 self._packet_handlers[t] = noop 100 101 def run(self): 102 XpraClientBase.run(self) 103 self.run_loop() 104 return self.exit_code 105 106 def run_loop(self): 107 self.glib_mainloop = GLib.MainLoop() 108 self.glib_mainloop.run() 109 110 def make_hello(self): 111 capabilities = XpraClientBase.make_hello(self) 112 capabilities["keyboard"] = False 113 return capabilities 114 115 def quit(self, exit_code=0): 116 log("quit(%s) current exit_code=%s", exit_code, self.exit_code) 117 if self.exit_code is None: 118 self.exit_code = exit_code 119 self.cleanup() 120 GLib.timeout_add(50, self.exit_loop) 121 122 def exit_loop(self): 123 self.glib_mainloop.quit() 124 125 126class CommandConnectClient(GObjectXpraClient): 127 """ 128 Utility superclass for clients that only send one command 129 via the hello packet. 130 """ 131 132 def __init__(self, opts): 133 super().__init__() 134 super().init(opts) 135 self.display_desc = {} 136 #not used by command line clients, 137 #so don't try probing for printers, etc 138 self.file_transfer = False 139 self.printing = False 140 self.command_timeout = None 141 #don't bother with many of these things for one-off commands: 142 for x in ("ui_client", "wants_aliases", "wants_encodings", 143 "wants_versions", "wants_features", "wants_sound", "windows", 144 "webcam", "keyboard", "mouse", "network-state", 145 ): 146 self.hello_extra[x] = False 147 148 def setup_connection(self, conn): 149 protocol = super().setup_connection(conn) 150 if conn.timeout>0: 151 self.command_timeout = GLib.timeout_add((conn.timeout + self.COMMAND_TIMEOUT) * 1000, self.timeout) 152 return protocol 153 154 def timeout(self, *_args): 155 log.warn("timeout!") # pragma: no cover 156 157 def cancel_command_timeout(self): 158 ct = self.command_timeout 159 if ct: 160 self.command_timeout = None 161 GLib.source_remove(ct) 162 163 def _process_connection_lost(self, packet): 164 log("_process_connection_lost%s", packet) 165 #override so we don't log a warning 166 #"command clients" are meant to exit quickly by losing the connection 167 p = self._protocol 168 if p and p.input_packetcount==0: 169 #never got any data back so we have never connected, 170 #try to give feedback to the user as to why that is: 171 details = "" 172 if len(packet)>1: 173 details = ": %s" % csv(packet[1:]) 174 log.warn("Connection failed%s", details) 175 self.quit(EXIT_CONNECTION_LOST) 176 else: 177 self.quit(EXIT_OK) 178 179 def server_connection_established(self, caps : typedict): 180 #don't bother parsing the network caps: 181 #* it could cause errors if the caps are missing 182 #* we don't care about sending anything back after hello 183 log("server_capabilities: %s", ellipsizer(caps)) 184 log("protocol state: %s", self._protocol.save_state()) 185 self.cancel_command_timeout() 186 self.do_command(caps) 187 return True 188 189 def do_command(self, caps : typedict): 190 raise NotImplementedError() 191 192 193class SendCommandConnectClient(CommandConnectClient): 194 """ 195 Utility superclass for clients that only send at least one more packet 196 after the hello packet. 197 So unlike CommandConnectClient, we do need the network and encryption to be setup. 198 """ 199 200 def server_connection_established(self, caps): 201 assert self.parse_encryption_capabilities(caps), "encryption failure" 202 assert self.parse_network_capabilities(caps), "network capabilities failure" 203 return super().server_connection_established(caps) 204 205 def do_command(self, caps : typedict): 206 raise NotImplementedError() 207 208 209class HelloRequestClient(SendCommandConnectClient): 210 """ 211 Utility superclass for clients that send a server request 212 as part of the hello packet. 213 """ 214 215 def make_hello_base(self): 216 caps = super().make_hello_base() 217 caps.update(self.hello_request()) 218 return caps 219 220 def timeout(self, *_args): 221 self.warn_and_quit(EXIT_TIMEOUT, "timeout: server did not disconnect us") 222 223 def hello_request(self): # pragma: no cover 224 raise NotImplementedError() 225 226 def do_command(self, caps : typedict): 227 self.quit(EXIT_OK) 228 229 def _process_disconnect(self, packet): 230 #overriden method so we can avoid printing a warning, 231 #we haven't received the hello back from the server 232 #but that's fine for a request client 233 info = tuple(nonl(bytestostr(x)) for x in packet[1:]) 234 reason = info[0] 235 if disconnect_is_an_error(reason): 236 self.server_disconnect_warning(*info) 237 elif self.exit_code is None: 238 #we're not in the process of exiting already, 239 #tell the user why the server is disconnecting us 240 self.server_disconnect(*info) 241 242 243class ScreenshotXpraClient(CommandConnectClient): 244 """ This client does one thing only: 245 it sends the hello packet with a screenshot request 246 and exits when the resulting image is received (or timedout) 247 """ 248 249 def __init__(self, opts, screenshot_filename): 250 self.screenshot_filename = screenshot_filename 251 super().__init__(opts) 252 self.hello_extra["screenshot_request"] = True 253 self.hello_extra["request"] = "screenshot" 254 255 def timeout(self, *_args): 256 self.warn_and_quit(EXIT_TIMEOUT, "timeout: did not receive the screenshot") 257 258 def _process_screenshot(self, packet): 259 (w, h, encoding, _, img_data) = packet[1:6] 260 assert bytestostr(encoding)=="png", "expected png screenshot data but got %s" % bytestostr(encoding) 261 if not img_data: 262 self.warn_and_quit(EXIT_OK, 263 "screenshot is empty and has not been saved (maybe there are no windows or they are not currently shown)") 264 return 265 if self.screenshot_filename=="-": 266 output = os.fdopen(sys.stdout.fileno(), "wb", closefd=False) 267 else: 268 output = open(self.screenshot_filename, "wb") 269 with output: 270 output.write(img_data) 271 output.flush() 272 self.warn_and_quit(EXIT_OK, "screenshot %sx%s saved to: %s" % (w, h, self.screenshot_filename)) 273 274 def init_packet_handlers(self): 275 super().init_packet_handlers() 276 self._ui_packet_handlers["screenshot"] = self._process_screenshot 277 278 279class InfoXpraClient(CommandConnectClient): 280 """ This client does one thing only: 281 it queries the server with an 'info' request 282 """ 283 284 def __init__(self, opts): 285 super().__init__(opts) 286 self.hello_extra["info_request"] = True 287 self.hello_extra["request"] = "info" 288 if FLATTEN_INFO>=1: 289 self.hello_extra["info-namespace"] = True 290 291 def timeout(self, *_args): 292 self.warn_and_quit(EXIT_TIMEOUT, "timeout: did not receive the info") 293 294 def do_command(self, caps : typedict): 295 def print_fn(s): 296 sys.stdout.write("%s\n" % (s,)) 297 if not caps: 298 self.quit(EXIT_NO_DATA) 299 return 300 exit_code = EXIT_OK 301 try: 302 if FLATTEN_INFO<2: 303 #compatibility mode: 304 c = flatten_dict(caps) 305 for k in sorted_nicely(c.keys()): 306 v = c.get(k) 307 #FIXME: this is a nasty and horrible python3 workaround (yet again) 308 #we want to print bytes as strings without the ugly 'b' prefix.. 309 #it assumes that all the strings are raw or in (possibly nested) lists or tuples only 310 #we assume that all strings we get are utf-8, 311 #and fallback to the bytestostr hack if that fails 312 def fixvalue(w): 313 if isinstance(w, bytes): 314 if k.endswith(".data"): 315 return hexstr(w) 316 return u(w) 317 elif isinstance(w, (tuple,list)): 318 return type(w)([fixvalue(x) for x in w]) 319 return w 320 v = fixvalue(v) 321 k = fixvalue(k) 322 print_fn("%s=%s" % (k, nonl(v))) 323 else: 324 print_nested_dict(caps, print_fn=print_fn) 325 except OSError: 326 exit_code = EXIT_IO_ERROR 327 self.quit(exit_code) 328 329class IDXpraClient(InfoXpraClient): 330 331 def __init__(self, *args): 332 super().__init__(*args) 333 self.hello_extra["request"] = "id" 334 335 336class RequestXpraClient(CommandConnectClient): 337 338 def __init__(self, request, opts): 339 super().__init__(opts) 340 self.hello_extra["request"] = request 341 342 def do_command(self, caps : typedict): 343 self.quit(EXIT_OK) 344 345 346class ConnectTestXpraClient(CommandConnectClient): 347 """ This client does one thing only: 348 it queries the server with an 'info' request 349 """ 350 351 def __init__(self, opts, **kwargs): 352 super().__init__(opts) 353 self.value = get_hex_uuid() 354 self.hello_extra.update({ 355 "connect_test_request" : self.value, 356 "request" : "connect_test", 357 #tells proxy servers we don't want to connect to the real / new instance: 358 "connect" : False, 359 #older servers don't know about connect-test, 360 #pretend that we're interested in info: 361 "info_request" : True, 362 "info-namespace" : True, 363 }) 364 self.hello_extra.update(kwargs) 365 366 def timeout(self, *_args): 367 self.warn_and_quit(EXIT_TIMEOUT, "timeout: no server response") 368 369 def _process_connection_lost(self, _packet): 370 #we should always receive a hello back and call do_command, 371 #which sets the correct exit code, landing here is an error: 372 self.quit(EXIT_FAILURE) 373 374 def do_command(self, caps : typedict): 375 if caps: 376 ctr = caps.strget("connect_test_response") 377 log("do_command(..) expected connect test response='%s', got '%s'", self.value, ctr) 378 if ctr==self.value: 379 self.quit(EXIT_OK) 380 else: 381 self.quit(EXIT_INTERNAL_ERROR) 382 else: 383 self.quit(EXIT_FAILURE) 384 385 386class MonitorXpraClient(SendCommandConnectClient): 387 """ This client does one thing only: 388 it prints out events received from the server. 389 If the server does not support this feature it exits with an error. 390 """ 391 392 def __init__(self, opts): 393 super().__init__(opts) 394 for x in ("wants_features", "wants_events", "event_request"): 395 self.hello_extra[x] = True 396 self.hello_extra["request"] = "event" 397 self.hello_extra["info-namespace"] = True 398 399 def timeout(self, *args): 400 pass 401 #self.warn_and_quit(EXIT_TIMEOUT, "timeout: did not receive the info") 402 403 def do_command(self, caps : typedict): 404 log.info("waiting for server events") 405 406 def _process_server_event(self, packet): 407 log.info(": ".join(bytestostr(x) for x in packet[1:])) 408 409 def init_packet_handlers(self): 410 super().init_packet_handlers() 411 self._packet_handlers["server-event"] = self._process_server_event 412 self._packet_handlers["ping"] = self._process_ping 413 414 def _process_ping(self, packet): 415 echotime = packet[1] 416 self.send("ping_echo", echotime, 0, 0, 0, -1) 417 418 419class InfoTimerClient(MonitorXpraClient): 420 """ 421 This client keeps monitoring the server 422 and requesting info data 423 """ 424 REFRESH_RATE = envint("XPRA_REFRESH_RATE", 1) 425 426 def __init__(self, *args): 427 super().__init__(*args) 428 self.info_request_pending = False 429 self.server_last_info = typedict() 430 self.server_last_info_time = 0 431 self.info_timer = 0 432 433 def run(self): 434 from xpra.gtk_common.gobject_compat import register_os_signals 435 register_os_signals(self.signal_handler, None) 436 v = super().run() 437 self.log("run()=%s" % v) 438 self.cleanup() 439 return v 440 441 def signal_handler(self, signum, *args): 442 self.log("exit_code=%s" % self.exit_code) 443 self.log("signal_handler(%s, %s)" % (signum, args,)) 444 self.quit(128+signum) 445 self.log("exit_code=%s" % self.exit_code) 446 447 def log(self, message): 448 #this method is overriden in top client to use a log file 449 log(message) 450 451 def err(self, e): 452 log.error(str(e)) 453 454 def cleanup(self): 455 self.cancel_info_timer() 456 MonitorXpraClient.cleanup(self) 457 458 def do_command(self, caps : typedict): 459 self.send_info_request() 460 self.timeout_add(self.REFRESH_RATE*1000, self.send_info_request) 461 462 def send_info_request(self, *categories): 463 self.log("send_info_request%s" % (categories,)) 464 if not self.info_request_pending: 465 self.info_request_pending = True 466 window_ids = () #no longer used or supported by servers 467 self.send("info-request", [self.uuid], window_ids, categories) 468 if not self.info_timer: 469 self.info_timer = self.timeout_add((self.REFRESH_RATE+2)*1000, self.info_timeout) 470 return True 471 472 def init_packet_handlers(self): 473 MonitorXpraClient.init_packet_handlers(self) 474 self.add_packet_handler("info-response", self._process_info_response, False) 475 476 def _process_server_event(self, packet): 477 self.log("server event: %s" % (packet,)) 478 self.last_server_event = packet[1:] 479 self.update_screen() 480 481 def _process_info_response(self, packet): 482 self.log("info response: %s" % repr_ellipsized(packet)) 483 self.cancel_info_timer() 484 self.info_request_pending = False 485 self.server_last_info = typedict(packet[1]) 486 self.server_last_info_time = monotonic() 487 #log.info("server_last_info=%s", self.server_last_info) 488 self.update_screen() 489 490 def cancel_info_timer(self): 491 it = self.info_timer 492 if it: 493 self.info_timer = None 494 self.source_remove(it) 495 496 def info_timeout(self): 497 self.log("info timeout") 498 self.update_screen() 499 return True 500 501 def update_screen(self): 502 raise NotImplementedError() 503 504 505class ShellXpraClient(SendCommandConnectClient): 506 """ 507 Provides an interactive shell with the socket it connects to 508 """ 509 510 def __init__(self, opts): 511 super().__init__(opts) 512 self.stdin_io_watch = None 513 self.stdin_buffer = "" 514 self.hello_extra["shell"] = "True" 515 516 def timeout(self, *args): 517 """ 518 The shell client never times out, 519 but the superclass calls this method automatically, 520 just ignore it. 521 """ 522 523 def cleanup(self): 524 siw = self.stdin_io_watch 525 if siw: 526 self.stdin_io_watch = None 527 self.source_remove(siw) 528 super().cleanup() 529 530 def do_command(self, caps : typedict): 531 if not caps.boolget("shell"): 532 msg = "this server does not support the 'shell' subcommand" 533 log.error(msg) 534 self.disconnect_and_quit(EXIT_UNSUPPORTED, msg) 535 return 536 #start reading from stdin: 537 self.install_signal_handlers() 538 stdin = sys.stdin 539 fileno = stdin.fileno() 540 import fcntl 541 fl = fcntl.fcntl(fileno, fcntl.F_GETFL) 542 fcntl.fcntl(fileno, fcntl.F_SETFL, fl | os.O_NONBLOCK) 543 self.stdin_io_watch = GLib.io_add_watch(sys.stdin, 544 GLib.PRIORITY_DEFAULT, GLib.IO_IN, 545 self.stdin_ready) 546 self.print_prompt() 547 548 def stdin_ready(self, *_args): 549 data = sys.stdin.read() 550 #log.warn("stdin=%r", data) 551 self.stdin_buffer += data 552 sent = 0 553 if self.stdin_buffer.endswith("\n"): 554 for line in self.stdin_buffer.splitlines(): 555 if line: 556 if line.rstrip("\n\r") in ("quit", "exit"): 557 self.disconnect_and_quit(EXIT_OK, "user requested %s" % line) 558 self.stdin_io_watch = None 559 return False 560 self.send("shell-exec", line.encode()) 561 sent += 1 562 self.stdin_buffer = "" 563 if not sent: 564 self.print_prompt() 565 return True 566 567 def init_packet_handlers(self): 568 super().init_packet_handlers() 569 self._packet_handlers["shell-reply"] = self._process_shell_reply 570 self._packet_handlers["ping"] = self._process_ping 571 572 def _process_ping(self, packet): 573 echotime = packet[1] 574 self.send("ping_echo", echotime, 0, 0, 0, -1) 575 576 def _process_shell_reply(self, packet): 577 fd = packet[1] 578 message = packet[2] 579 if fd==1: 580 stream = sys.stdout 581 elif fd==2: 582 stream = sys.stderr 583 else: 584 raise Exception("invalid file descriptor %i" % fd) 585 s = net_utf8(message) 586 if s.endswith("\n"): 587 s = s[:-1] 588 stream.write("%s" % s) 589 stream.flush() 590 if fd==2: 591 stream.write("\n") 592 self.print_prompt() 593 594 def print_prompt(self): 595 sys.stdout.write("> ") 596 sys.stdout.flush() 597 598 599class VersionXpraClient(HelloRequestClient): 600 """ This client does one thing only: 601 it queries the server for version information and prints it out 602 """ 603 604 def hello_request(self): 605 return { 606 "version_request" : True, 607 "request" : "version", 608 "full-version-request" : True, 609 } 610 611 def parse_network_capabilities(self, *_args): 612 #don't bother checking anything - this could generate warnings 613 return True 614 615 def do_command(self, caps : typedict): 616 v = caps.strget("version") 617 if not v: 618 self.warn_and_quit(EXIT_FAILURE, "server did not provide the version information") 619 else: 620 sys.stdout.write("%s\n" % (v,)) 621 sys.stdout.flush() 622 self.quit(EXIT_OK) 623 624 625class ControlXpraClient(CommandConnectClient): 626 """ Allows us to send commands to a server. 627 """ 628 def set_command_args(self, command): 629 self.command = command 630 631 def timeout(self, *_args): 632 self.warn_and_quit(EXIT_TIMEOUT, "timeout: server did not respond") 633 634 def do_command(self, caps : typedict): 635 cr = caps.tupleget("command_response") 636 if cr is None: 637 self.warn_and_quit(EXIT_UNSUPPORTED, "server does not support control command") 638 return 639 code, text = cr 640 text = bytestostr(text) 641 if code!=0: 642 log.warn("server returned error code %s", code) 643 self.warn_and_quit(EXIT_REMOTE_ERROR, " %s" % text) 644 return 645 self.warn_and_quit(EXIT_OK, text) 646 647 def make_hello(self): 648 capabilities = super().make_hello() 649 log("make_hello() adding command request '%s' to %s", self.command, capabilities) 650 capabilities["command_request"] = tuple(self.command) 651 capabilities["request"] = "command" 652 return capabilities 653 654 655class PrintClient(SendCommandConnectClient): 656 """ Allows us to send a file to the server for printing. 657 """ 658 def set_command_args(self, command): 659 log("set_command_args(%s)", command) 660 self.filename = command[0] 661 #print command arguments: 662 #filename, file_data, mimetype, source_uuid, title, printer, no_copies, print_options_str = packet[1:9] 663 self.command = command[1:] 664 #TODO: load as needed... 665 def sizeerr(size): 666 self.warn_and_quit(EXIT_FILE_TOO_BIG, 667 "the file is too large: %sB (the file size limit is %sB)" % ( 668 std_unit(size), std_unit(self.file_size_limit))) 669 return 670 if self.filename=="-": 671 #replace with filename proposed 672 self.filename = command[2] 673 #read file from stdin 674 with open(sys.stdin.fileno(), mode='rb', closefd=False) as stdin_binary: 675 self.file_data = stdin_binary.read() 676 log("read %i bytes from stdin", len(self.file_data)) 677 else: 678 size = os.path.getsize(self.filename) 679 if size>self.file_size_limit: 680 sizeerr(size) 681 return 682 from xpra.os_util import load_binary_file 683 self.file_data = load_binary_file(self.filename) 684 log("read %i bytes from %s", len(self.file_data), self.filename) 685 size = len(self.file_data) 686 if size>self.file_size_limit: 687 sizeerr(size) 688 return 689 assert self.file_data, "no data found for '%s'" % self.filename 690 691 def client_type(self): 692 return "Python/GObject/Print" 693 694 def timeout(self, *_args): 695 self.warn_and_quit(EXIT_TIMEOUT, "timeout: server did not respond") 696 697 def do_command(self, caps : typedict): 698 printing = caps.boolget("printing") 699 if not printing: 700 self.warn_and_quit(EXIT_UNSUPPORTED, "server does not support printing") 701 return 702 #we don't compress file data 703 #(this should run locally most of the time anyway) 704 from xpra.net.compression import Compressed #pylint: disable=import-outside-toplevel 705 blob = Compressed("print", self.file_data) 706 self.send("print", self.filename, blob, *self.command) 707 log("print: sending %s as %s for printing", self.filename, blob) 708 self.idle_add(self.send, "disconnect", DONE, "detaching") 709 710 def make_hello(self): 711 capabilities = super().make_hello() 712 capabilities["wants_features"] = True #so we know if printing is supported or not 713 capabilities["print_request"] = True #marker to skip full setup 714 capabilities["request"] = "print" 715 return capabilities 716 717 718class ExitXpraClient(HelloRequestClient): 719 """ This client does one thing only: 720 it asks the server to terminate (like stop), 721 but without killing the Xvfb or clients. 722 """ 723 724 def hello_request(self): 725 return { 726 "exit_request" : True, 727 "request" : "exit", 728 } 729 730 def do_command(self, caps : typedict): 731 self.idle_add(self.send, "exit-server", os.environ.get("XPRA_EXIT_MESSAGE", SERVER_EXIT)) 732 733 734class StopXpraClient(HelloRequestClient): 735 """ stop a server """ 736 737 def hello_request(self): 738 return { 739 "stop_request" : True, 740 "request" : "stop", 741 } 742 743 def do_command(self, caps : typedict): 744 if not self.server_client_shutdown: 745 log.error("Error: cannot shutdown this server") 746 log.error(" the feature is disable on the server") 747 self.quit(EXIT_FAILURE) 748 return 749 self.timeout_add(1000, self.send_shutdown_server) 750 #self.idle_add(self.send_shutdown_server) 751 #not exiting the client here, 752 #the server should send us the shutdown disconnection message anyway 753 #and if not, we will then hit the timeout to tell us something went wrong 754 755 756class DetachXpraClient(HelloRequestClient): 757 """ run the detach subcommand """ 758 759 def hello_request(self): 760 return { 761 "detach_request" : True, 762 "request" : "detach", 763 } 764 765 def do_command(self, caps : typedict): 766 self.idle_add(self.send, "disconnect", DONE, "detaching") 767 #not exiting the client here, 768 #the server should disconnect us with the response 769 770class WaitForDisconnectXpraClient(DetachXpraClient): 771 """ we just want the connection to close """ 772 773 def _process_disconnect(self, _packet): 774 self.quit(EXIT_OK) 775 776 777class RequestStartClient(HelloRequestClient): 778 """ request the system proxy server to start a new session for us """ 779 #wait longer for this command to return: 780 from xpra.scripts.main import WAIT_SERVER_TIMEOUT 781 COMMAND_TIMEOUT = EXTRA_TIMEOUT+WAIT_SERVER_TIMEOUT 782 783 def dots(self): 784 errwrite(".") 785 return not self.connection_established 786 787 def _process_connection_lost(self, packet): 788 errwrite("\n") 789 super()._process_connection_lost(packet) 790 791 def hello_request(self): 792 if first_time("hello-request"): 793 #this can be called again if we receive a challenge, 794 #but only print this message once: 795 errwrite("requesting new session, please wait") 796 self.timeout_add(1*1000, self.dots) 797 return { 798 "start-new-session" : self.start_new_session, 799 #tells proxy servers we don't want to connect to the real / new instance: 800 "connect" : False, 801 } 802 803 def server_connection_established(self, caps : typedict): 804 #the server should respond with the display chosen 805 log("server_connection_established() exit_code=%s", self.exit_code) 806 display = caps.strget("display") 807 if display: 808 mode = caps.strget("mode") 809 session_type = { 810 "start" : "seamless ", 811 "start-desktop" : "desktop ", 812 "shadow" : "shadow ", 813 }.get(mode, "") 814 try: 815 errwrite("\n%ssession now available on display %s\n" % (session_type, display)) 816 if POSIX and not OSX and self.displayfd>0 and display and display.startswith(":"): 817 from xpra.platform.displayfd import write_displayfd 818 log("writing display %s to displayfd=%s", display, self.displayfd) 819 write_displayfd(self.displayfd, display[1:]) 820 except OSError: 821 log("server_connection_established(..)", exc_info=True) 822 if not self.exit_code: 823 self.quit(0) 824 return True 825 826 def __init__(self, opts): 827 super().__init__(opts) 828 try: 829 self.displayfd = int(opts.displayfd) 830 except (ValueError, TypeError): 831 self.displayfd = 0 832