| OLD | NEW |
| 1 #!/usr/bin/python | 1 #!/usr/bin/python |
| 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 # Virtual Me2Me implementation. This script runs and manages the processes | 6 # Virtual Me2Me implementation. This script runs and manages the processes |
| 7 # required for a Virtual Me2Me desktop, which are: X server, X desktop | 7 # required for a Virtual Me2Me desktop, which are: X server, X desktop |
| 8 # session, and Host process. | 8 # session, and Host process. |
| 9 # This script is intended to run continuously as a background daemon | 9 # This script is intended to run continuously as a background daemon |
| 10 # process, running under an ordinary (non-root) user account. | 10 # process, running under an ordinary (non-root) user account. |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 48 # defaults can be overridden in ~/.profile. | 48 # defaults can be overridden in ~/.profile. |
| 49 DEFAULT_SIZES = "1600x1200,3840x2560" | 49 DEFAULT_SIZES = "1600x1200,3840x2560" |
| 50 | 50 |
| 51 # If RANDR is not available, use a smaller default size. Only a single | 51 # If RANDR is not available, use a smaller default size. Only a single |
| 52 # resolution is supported in this case. | 52 # resolution is supported in this case. |
| 53 DEFAULT_SIZE_NO_RANDR = "1600x1200" | 53 DEFAULT_SIZE_NO_RANDR = "1600x1200" |
| 54 | 54 |
| 55 SCRIPT_PATH = os.path.abspath(sys.argv[0]) | 55 SCRIPT_PATH = os.path.abspath(sys.argv[0]) |
| 56 SCRIPT_DIR = os.path.dirname(SCRIPT_PATH) | 56 SCRIPT_DIR = os.path.dirname(SCRIPT_PATH) |
| 57 | 57 |
| 58 IS_INSTALLED = (os.path.basename(sys.argv[0]) != 'linux_me2me_host.py') | 58 HOST_BINARY_NAME = "chrome-remote-desktop-host" |
| 59 | |
| 60 if IS_INSTALLED: | |
| 61 HOST_BINARY_NAME = "chrome-remote-desktop-host" | |
| 62 else: | |
| 63 HOST_BINARY_NAME = "remoting_me2me_host" | |
| 64 | |
| 65 CHROME_REMOTING_GROUP_NAME = "chrome-remote-desktop" | 59 CHROME_REMOTING_GROUP_NAME = "chrome-remote-desktop" |
| 66 | 60 |
| 67 HOME_DIR = os.environ["HOME"] | 61 HOME_DIR = os.environ["HOME"] |
| 68 CONFIG_DIR = os.path.join(HOME_DIR, ".config/chrome-remote-desktop") | 62 CONFIG_DIR = os.path.join(HOME_DIR, ".config/chrome-remote-desktop") |
| 69 SESSION_FILE_PATH = os.path.join(HOME_DIR, ".chrome-remote-desktop-session") | 63 SESSION_FILE_PATH = os.path.join(HOME_DIR, ".chrome-remote-desktop-session") |
| 70 SYSTEM_SESSION_FILE_PATH = "/etc/chrome-remote-desktop-session" | 64 SYSTEM_SESSION_FILE_PATH = "/etc/chrome-remote-desktop-session" |
| 71 | 65 |
| 72 X_LOCK_FILE_TEMPLATE = "/tmp/.X%d-lock" | 66 X_LOCK_FILE_TEMPLATE = "/tmp/.X%d-lock" |
| 73 FIRST_X_DISPLAY_NUMBER = 20 | 67 FIRST_X_DISPLAY_NUMBER = 20 |
| 74 | 68 |
| (...skipping 23 matching lines...) Expand all Loading... |
| 98 return True | 92 return True |
| 99 | 93 |
| 100 # The host has been tested only on Ubuntu. | 94 # The host has been tested only on Ubuntu. |
| 101 distribution = platform.linux_distribution() | 95 distribution = platform.linux_distribution() |
| 102 return (distribution[0]).lower() == 'ubuntu' | 96 return (distribution[0]).lower() == 'ubuntu' |
| 103 | 97 |
| 104 | 98 |
| 105 def get_randr_supporting_x_server(): | 99 def get_randr_supporting_x_server(): |
| 106 """Returns a path to an X server that supports the RANDR extension, if this | 100 """Returns a path to an X server that supports the RANDR extension, if this |
| 107 is found on the system. Otherwise returns None.""" | 101 is found on the system. Otherwise returns None.""" |
| 108 try: | 102 |
| 109 xvfb = "/usr/bin/Xvfb-randr" | 103 xvfb = "/usr/bin/Xvfb-randr" |
| 110 if not os.path.exists(xvfb): | 104 if os.path.exists(xvfb): |
| 111 xvfb = locate_executable("Xvfb-randr") | |
| 112 return xvfb | 105 return xvfb |
| 113 except Exception: | 106 |
| 114 return None | 107 xvfb = os.path.join(SCRIPT_DIR, "Xvfb-randr") |
| 108 if os.path.exists(xvfb): |
| 109 return xvfb |
| 110 |
| 111 return None |
| 115 | 112 |
| 116 | 113 |
| 117 class Config: | 114 class Config: |
| 118 def __init__(self, path): | 115 def __init__(self, path): |
| 119 self.path = path | 116 self.path = path |
| 120 self.data = {} | 117 self.data = {} |
| 121 self.changed = False | 118 self.changed = False |
| 122 | 119 |
| 123 def load(self): | 120 def load(self): |
| 124 """Loads the config from file. | 121 """Loads the config from file. |
| (...skipping 365 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 490 | 487 |
| 491 def launch_session(self, x_args): | 488 def launch_session(self, x_args): |
| 492 self._init_child_env() | 489 self._init_child_env() |
| 493 self._setup_pulseaudio() | 490 self._setup_pulseaudio() |
| 494 self._setup_gnubby() | 491 self._setup_gnubby() |
| 495 self._launch_x_server(x_args) | 492 self._launch_x_server(x_args) |
| 496 self._launch_x_session() | 493 self._launch_x_session() |
| 497 | 494 |
| 498 def launch_host(self, host_config): | 495 def launch_host(self, host_config): |
| 499 # Start remoting host | 496 # Start remoting host |
| 500 args = [locate_executable(HOST_BINARY_NAME), "--host-config=-"] | 497 host_path = os.path.join(SCRIPT_DIR, HOST_BINARY_NAME) |
| 498 args = [host_path, "--host-config=-"] |
| 501 if self.pulseaudio_pipe: | 499 if self.pulseaudio_pipe: |
| 502 args.append("--audio-pipe-name=%s" % self.pulseaudio_pipe) | 500 args.append("--audio-pipe-name=%s" % self.pulseaudio_pipe) |
| 503 if self.server_supports_exact_resize: | 501 if self.server_supports_exact_resize: |
| 504 args.append("--server-supports-exact-resize") | 502 args.append("--server-supports-exact-resize") |
| 505 if self.ssh_auth_sockname: | 503 if self.ssh_auth_sockname: |
| 506 args.append("--ssh-auth-sockname=%s" % self.ssh_auth_sockname) | 504 args.append("--ssh-auth-sockname=%s" % self.ssh_auth_sockname) |
| 507 | 505 |
| 508 # Have the host process use SIGUSR1 to signal a successful start. | 506 # Have the host process use SIGUSR1 to signal a successful start. |
| 509 def sigusr1_handler(signum, frame): | 507 def sigusr1_handler(signum, frame): |
| 510 _ = signum, frame | 508 _ = signum, frame |
| 511 logging.info("Host ready to receive connections.") | 509 logging.info("Host ready to receive connections.") |
| 512 self.host_ready = True | 510 self.host_ready = True |
| 513 if (ParentProcessLogger.instance() and | 511 if (ParentProcessLogger.instance() and |
| 514 False not in [desktop.host_ready for desktop in g_desktops]): | 512 False not in [desktop.host_ready for desktop in g_desktops]): |
| 515 ParentProcessLogger.instance().release_parent(True) | 513 ParentProcessLogger.instance().release_parent(True) |
| 516 | 514 |
| 517 signal.signal(signal.SIGUSR1, sigusr1_handler) | 515 signal.signal(signal.SIGUSR1, sigusr1_handler) |
| 518 args.append("--signal-parent") | 516 args.append("--signal-parent") |
| 519 | 517 |
| 518 logging.info(args) |
| 520 self.host_proc = subprocess.Popen(args, env=self.child_env, | 519 self.host_proc = subprocess.Popen(args, env=self.child_env, |
| 521 stdin=subprocess.PIPE) | 520 stdin=subprocess.PIPE) |
| 522 logging.info(args) | |
| 523 if not self.host_proc.pid: | 521 if not self.host_proc.pid: |
| 524 raise Exception("Could not start Chrome Remote Desktop host") | 522 raise Exception("Could not start Chrome Remote Desktop host") |
| 525 | 523 |
| 526 try: | 524 try: |
| 527 self.host_proc.stdin.write(json.dumps(host_config.data).encode('UTF-8')) | 525 self.host_proc.stdin.write(json.dumps(host_config.data).encode('UTF-8')) |
| 528 self.host_proc.stdin.flush() | 526 self.host_proc.stdin.flush() |
| 529 except IOError as e: | 527 except IOError as e: |
| 530 # This can occur in rare situations, for example, if the machine is | 528 # This can occur in rare situations, for example, if the machine is |
| 531 # heavily loaded and the host process dies quickly (maybe if the X | 529 # heavily loaded and the host process dies quickly (maybe if the X |
| 532 # connection failed), the host process might be gone before this code | 530 # connection failed), the host process might be gone before this code |
| (...skipping 90 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 623 # corrupt displays). So if the ubuntu-2d session is available, | 621 # corrupt displays). So if the ubuntu-2d session is available, |
| 624 # choose it explicitly. | 622 # choose it explicitly. |
| 625 return [session_wrapper, "/usr/bin/gnome-session --session=ubuntu-2d"] | 623 return [session_wrapper, "/usr/bin/gnome-session --session=ubuntu-2d"] |
| 626 else: | 624 else: |
| 627 # Use the session wrapper by itself, and let the system choose a | 625 # Use the session wrapper by itself, and let the system choose a |
| 628 # session. | 626 # session. |
| 629 return [session_wrapper] | 627 return [session_wrapper] |
| 630 return None | 628 return None |
| 631 | 629 |
| 632 | 630 |
| 633 def locate_executable(exe_name): | |
| 634 if IS_INSTALLED: | |
| 635 # If the script is running from its installed location, search the host | |
| 636 # binary only in the same directory. | |
| 637 paths_to_try = [ SCRIPT_DIR ] | |
| 638 else: | |
| 639 paths_to_try = map(lambda p: os.path.join(SCRIPT_DIR, p), | |
| 640 [".", | |
| 641 "../../../out/Debug", | |
| 642 "../../../out/Default", | |
| 643 "../../../out/Release"]) | |
| 644 for path in paths_to_try: | |
| 645 exe_path = os.path.join(path, exe_name) | |
| 646 if os.path.exists(exe_path): | |
| 647 return exe_path | |
| 648 | |
| 649 raise Exception("Could not locate executable '%s'" % exe_name) | |
| 650 | |
| 651 | |
| 652 class ParentProcessLogger(object): | 631 class ParentProcessLogger(object): |
| 653 """Redirects logs to the parent process, until the host is ready or quits. | 632 """Redirects logs to the parent process, until the host is ready or quits. |
| 654 | 633 |
| 655 This class creates a pipe to allow logging from the daemon process to be | 634 This class creates a pipe to allow logging from the daemon process to be |
| 656 copied to the parent process. The daemon process adds a log-handler that | 635 copied to the parent process. The daemon process adds a log-handler that |
| 657 directs logging output to the pipe. The parent process reads from this pipe | 636 directs logging output to the pipe. The parent process reads from this pipe |
| 658 until and writes the content to stderr. When the pipe is no longer needed | 637 until and writes the content to stderr. When the pipe is no longer needed |
| 659 (for example, the host signals successful launch or permanent failure), the | 638 (for example, the host signals successful launch or permanent failure), the |
| 660 daemon removes the log-handler and closes the pipe, causing the the parent | 639 daemon removes the log-handler and closes the pipe, causing the the parent |
| 661 process to reach end-of-file while reading the pipe and exit. | 640 process to reach end-of-file while reading the pipe and exit. |
| (...skipping 730 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1392 else: | 1371 else: |
| 1393 logging.info("Host exited with status %s." % os.WEXITSTATUS(status)) | 1372 logging.info("Host exited with status %s." % os.WEXITSTATUS(status)) |
| 1394 elif os.WIFSIGNALED(status): | 1373 elif os.WIFSIGNALED(status): |
| 1395 logging.info("Host terminated by signal %s." % os.WTERMSIG(status)) | 1374 logging.info("Host terminated by signal %s." % os.WTERMSIG(status)) |
| 1396 | 1375 |
| 1397 | 1376 |
| 1398 if __name__ == "__main__": | 1377 if __name__ == "__main__": |
| 1399 logging.basicConfig(level=logging.DEBUG, | 1378 logging.basicConfig(level=logging.DEBUG, |
| 1400 format="%(asctime)s:%(levelname)s:%(message)s") | 1379 format="%(asctime)s:%(levelname)s:%(message)s") |
| 1401 sys.exit(main()) | 1380 sys.exit(main()) |
| OLD | NEW |