Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2011 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. |
| 11 | 11 |
| 12 import atexit | 12 import atexit |
| 13 import getpass | 13 import getpass |
| 14 import hashlib | 14 import hashlib |
| 15 import json | 15 import json |
| 16 import logging | 16 import logging |
| 17 import optparse | |
| 17 import os | 18 import os |
| 18 import random | 19 import random |
| 19 import signal | 20 import signal |
| 20 import socket | 21 import socket |
| 21 import subprocess | 22 import subprocess |
| 22 import sys | 23 import sys |
| 23 import time | 24 import time |
| 24 import urllib2 | 25 import urllib2 |
| 25 import uuid | 26 import uuid |
| 26 | 27 |
| (...skipping 167 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 194 logging.info("Terminating Xvfb") | 195 logging.info("Terminating Xvfb") |
| 195 desktop.x_proc.terminate() | 196 desktop.x_proc.terminate() |
| 196 | 197 |
| 197 def signal_handler(signum, stackframe): | 198 def signal_handler(signum, stackframe): |
| 198 # Exit cleanly so the atexit handler, cleanup(), gets called. | 199 # Exit cleanly so the atexit handler, cleanup(), gets called. |
| 199 raise SystemExit | 200 raise SystemExit |
| 200 | 201 |
| 201 | 202 |
| 202 class Desktop: | 203 class Desktop: |
| 203 """Manage a single virtual desktop""" | 204 """Manage a single virtual desktop""" |
| 204 def __init__(self): | 205 def __init__(self, width, height): |
| 205 self.x_proc = None | 206 self.x_proc = None |
| 207 self.width = width | |
| 208 self.height = height | |
| 206 g_desktops.append(self) | 209 g_desktops.append(self) |
| 207 | 210 |
| 208 @staticmethod | 211 @staticmethod |
| 209 def get_unused_display_number(): | 212 def get_unused_display_number(): |
| 210 """Return a candidate display number for which there is currently no | 213 """Return a candidate display number for which there is currently no |
| 211 X Server lock file""" | 214 X Server lock file""" |
| 212 display = FIRST_X_DISPLAY_NUMBER | 215 display = FIRST_X_DISPLAY_NUMBER |
| 213 while os.path.exists(X_LOCK_FILE_TEMPLATE % display): | 216 while os.path.exists(X_LOCK_FILE_TEMPLATE % display): |
| 214 display += 1 | 217 display += 1 |
| 215 return display | 218 return display |
| 216 | 219 |
| 217 def launch_x_server(self): | 220 def launch_x_server(self): |
| 218 display = self.get_unused_display_number() | 221 display = self.get_unused_display_number() |
| 219 ret_code = subprocess.call("xauth add :%d . `mcookie`" % display, | 222 ret_code = subprocess.call("xauth add :%d . `mcookie`" % display, |
| 220 shell=True) | 223 shell=True) |
| 221 if ret_code != 0: | 224 if ret_code != 0: |
| 222 raise Exception("xauth failed with code %d" % ret_code) | 225 raise Exception("xauth failed with code %d" % ret_code) |
| 223 | 226 |
| 224 logging.info("Starting Xvfb on display :%d" % display); | 227 logging.info("Starting Xvfb on display :%d" % display); |
| 228 screen_option = "%dx%dx24" % (self.width, self.height) | |
| 225 self.x_proc = subprocess.Popen(["Xvfb", ":%d" % display, | 229 self.x_proc = subprocess.Popen(["Xvfb", ":%d" % display, |
| 226 "-auth", X_AUTH_FILE, | 230 "-auth", X_AUTH_FILE, |
| 227 "-nolisten", "tcp", | 231 "-nolisten", "tcp", |
| 228 "-screen", "0", "1024x768x24", | 232 "-screen", "0", screen_option |
| 229 ]) | 233 ]) |
| 230 if not self.x_proc.pid: | 234 if not self.x_proc.pid: |
| 231 raise Exception("Could not start Xvfb.") | 235 raise Exception("Could not start Xvfb.") |
| 232 | 236 |
| 233 # Create clean environment for new session, so it is cleanly separated from | 237 # Create clean environment for new session, so it is cleanly separated from |
| 234 # the user's console X session. | 238 # the user's console X session. |
| 235 self.child_env = {"DISPLAY": ":%d" % display} | 239 self.child_env = {"DISPLAY": ":%d" % display} |
| 236 for key in [ | 240 for key in [ |
| 237 "HOME", | 241 "HOME", |
| 238 "LOGNAME", | 242 "LOGNAME", |
| (...skipping 28 matching lines...) Expand all Loading... | |
| 267 def launch_host(self, host): | 271 def launch_host(self, host): |
| 268 # Start remoting host | 272 # Start remoting host |
| 269 args = [locate_executable(REMOTING_COMMAND), | 273 args = [locate_executable(REMOTING_COMMAND), |
| 270 "--%s=%s" % (HOST_CONFIG_SWITCH_NAME, host.config_file)] | 274 "--%s=%s" % (HOST_CONFIG_SWITCH_NAME, host.config_file)] |
| 271 self.host_proc = subprocess.Popen(args, env=self.child_env) | 275 self.host_proc = subprocess.Popen(args, env=self.child_env) |
| 272 if not self.host_proc.pid: | 276 if not self.host_proc.pid: |
| 273 raise Exception("Could not start remoting host") | 277 raise Exception("Could not start remoting host") |
| 274 | 278 |
| 275 | 279 |
| 276 def main(): | 280 def main(): |
| 281 parser = optparse.OptionParser() | |
| 282 parser.add_option("-s", "--size", dest="size", default="1280x1024", | |
| 283 help="dimensions of virtual desktop (default: %default)") | |
| 284 (options, args) = parser.parse_args() | |
| 285 | |
| 286 size_components = options.size.split("x") | |
| 287 if len(size_components) != 2: | |
| 288 parser.error("Incorrect size format, should be WIDTHxHEIGHT"); | |
| 289 | |
| 290 try: | |
| 291 width = int(size_components[0]) | |
| 292 height = int(size_components[1]) | |
| 293 | |
| 294 # Enforce minimum desktop size, as a sanity-check. The limit of 100 will | |
| 295 # detect typos of 2 instead of 3 digits. | |
| 296 if width < 100 or height < 100: | |
| 297 raise ValueError | |
| 298 except ValueError: | |
| 299 parser.error("Dimensions should be 100 pixels or greater") | |
|
Wez
2011/12/13 01:43:52
nit: I'd actually use "Width and height" rather th
| |
| 300 | |
| 277 atexit.register(cleanup) | 301 atexit.register(cleanup) |
| 278 | 302 |
| 279 for s in [signal.SIGHUP, signal.SIGINT, signal.SIGTERM]: | 303 for s in [signal.SIGHUP, signal.SIGINT, signal.SIGTERM]: |
| 280 signal.signal(s, signal_handler) | 304 signal.signal(s, signal_handler) |
| 281 | 305 |
| 282 # Ensure full path to config directory exists. | 306 # Ensure full path to config directory exists. |
| 283 if not os.path.exists(CONFIG_DIR): | 307 if not os.path.exists(CONFIG_DIR): |
| 284 os.makedirs(CONFIG_DIR, mode=0700) | 308 os.makedirs(CONFIG_DIR, mode=0700) |
| 285 | 309 |
| 286 auth = Authentication(os.path.join(CONFIG_DIR, "auth.json")) | 310 auth = Authentication(os.path.join(CONFIG_DIR, "auth.json")) |
| 287 if not auth.load_config(): | 311 if not auth.load_config(): |
| 288 try: | 312 try: |
| 289 auth.refresh_tokens() | 313 auth.refresh_tokens() |
| 290 except: | 314 except: |
| 291 logging.error("Authentication failed.") | 315 logging.error("Authentication failed.") |
| 292 return 1 | 316 return 1 |
| 293 auth.save_config() | 317 auth.save_config() |
| 294 | 318 |
| 295 host_hash = hashlib.md5(socket.gethostname()).hexdigest() | 319 host_hash = hashlib.md5(socket.gethostname()).hexdigest() |
| 296 host = Host(os.path.join(CONFIG_DIR, "host#%s.json" % host_hash)) | 320 host = Host(os.path.join(CONFIG_DIR, "host#%s.json" % host_hash)) |
| 297 | 321 |
| 298 if not host.load_config(): | 322 if not host.load_config(): |
| 299 host.create_config(auth) | 323 host.create_config(auth) |
| 300 host.save_config() | 324 host.save_config() |
| 301 | 325 |
| 302 logging.info("Using host_id: " + host.host_id) | 326 logging.info("Using host_id: " + host.host_id) |
| 303 | 327 |
| 304 desktop = Desktop() | 328 desktop = Desktop(width, height) |
| 305 desktop.launch_x_server() | 329 desktop.launch_x_server() |
| 306 desktop.launch_x_session() | 330 desktop.launch_x_session() |
| 307 desktop.launch_host(host) | 331 desktop.launch_host(host) |
| 308 | 332 |
| 309 while True: | 333 while True: |
| 310 pid, status = os.wait() | 334 pid, status = os.wait() |
| 311 logging.info("wait() returned (%s,%s)" % (pid, status)) | 335 logging.info("wait() returned (%s,%s)" % (pid, status)) |
| 312 | 336 |
| 313 if pid == desktop.x_proc.pid: | 337 if pid == desktop.x_proc.pid: |
| 314 logging.info("X server process terminated with code %d", status) | 338 logging.info("X server process terminated with code %d", status) |
| 315 break | 339 break |
| 316 | 340 |
| 317 if pid == desktop.host_proc.pid: | 341 if pid == desktop.host_proc.pid: |
| 318 logging.info("Host process terminated, relaunching") | 342 logging.info("Host process terminated, relaunching") |
| 319 desktop.launch_host(host) | 343 desktop.launch_host(host) |
| 320 | 344 |
| 321 if __name__ == "__main__": | 345 if __name__ == "__main__": |
| 322 logging.basicConfig(level=logging.DEBUG) | 346 logging.basicConfig(level=logging.DEBUG) |
| 323 sys.exit(main()) | 347 sys.exit(main()) |
| OLD | NEW |