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, extra_x_args): |
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 ] + extra_x_args) |
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", |
239 "PATH", | 243 "PATH", |
(...skipping 27 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 "Usage: %prog [options] [ -- [ X server options ] ]") |
| 283 parser.add_option("-s", "--size", dest="size", default="1280x1024", |
| 284 help="dimensions of virtual desktop (default: %default)") |
| 285 (options, args) = parser.parse_args() |
| 286 |
| 287 size_components = options.size.split("x") |
| 288 if len(size_components) != 2: |
| 289 parser.error("Incorrect size format, should be WIDTHxHEIGHT"); |
| 290 |
| 291 try: |
| 292 width = int(size_components[0]) |
| 293 height = int(size_components[1]) |
| 294 |
| 295 # Enforce minimum desktop size, as a sanity-check. The limit of 100 will |
| 296 # detect typos of 2 instead of 3 digits. |
| 297 if width < 100 or height < 100: |
| 298 raise ValueError |
| 299 except ValueError: |
| 300 parser.error("Width and height should be 100 pixels or greater") |
| 301 |
277 atexit.register(cleanup) | 302 atexit.register(cleanup) |
278 | 303 |
279 for s in [signal.SIGHUP, signal.SIGINT, signal.SIGTERM]: | 304 for s in [signal.SIGHUP, signal.SIGINT, signal.SIGTERM]: |
280 signal.signal(s, signal_handler) | 305 signal.signal(s, signal_handler) |
281 | 306 |
282 # Ensure full path to config directory exists. | 307 # Ensure full path to config directory exists. |
283 if not os.path.exists(CONFIG_DIR): | 308 if not os.path.exists(CONFIG_DIR): |
284 os.makedirs(CONFIG_DIR, mode=0700) | 309 os.makedirs(CONFIG_DIR, mode=0700) |
285 | 310 |
286 auth = Authentication(os.path.join(CONFIG_DIR, "auth.json")) | 311 auth = Authentication(os.path.join(CONFIG_DIR, "auth.json")) |
287 if not auth.load_config(): | 312 if not auth.load_config(): |
288 try: | 313 try: |
289 auth.refresh_tokens() | 314 auth.refresh_tokens() |
290 except: | 315 except: |
291 logging.error("Authentication failed.") | 316 logging.error("Authentication failed.") |
292 return 1 | 317 return 1 |
293 auth.save_config() | 318 auth.save_config() |
294 | 319 |
295 host_hash = hashlib.md5(socket.gethostname()).hexdigest() | 320 host_hash = hashlib.md5(socket.gethostname()).hexdigest() |
296 host = Host(os.path.join(CONFIG_DIR, "host#%s.json" % host_hash)) | 321 host = Host(os.path.join(CONFIG_DIR, "host#%s.json" % host_hash)) |
297 | 322 |
298 if not host.load_config(): | 323 if not host.load_config(): |
299 host.create_config(auth) | 324 host.create_config(auth) |
300 host.save_config() | 325 host.save_config() |
301 | 326 |
302 logging.info("Using host_id: " + host.host_id) | 327 logging.info("Using host_id: " + host.host_id) |
303 | 328 |
304 desktop = Desktop() | 329 desktop = Desktop(width, height) |
305 desktop.launch_x_server() | 330 desktop.launch_x_server(args) |
306 desktop.launch_x_session() | 331 desktop.launch_x_session() |
307 desktop.launch_host(host) | 332 desktop.launch_host(host) |
308 | 333 |
309 while True: | 334 while True: |
310 pid, status = os.wait() | 335 pid, status = os.wait() |
311 logging.info("wait() returned (%s,%s)" % (pid, status)) | 336 logging.info("wait() returned (%s,%s)" % (pid, status)) |
312 | 337 |
313 if pid == desktop.x_proc.pid: | 338 if pid == desktop.x_proc.pid: |
314 logging.info("X server process terminated with code %d", status) | 339 logging.info("X server process terminated with code %d", status) |
315 break | 340 break |
316 | 341 |
317 if pid == desktop.host_proc.pid: | 342 if pid == desktop.host_proc.pid: |
318 logging.info("Host process terminated, relaunching") | 343 logging.info("Host process terminated, relaunching") |
319 desktop.launch_host(host) | 344 desktop.launch_host(host) |
320 | 345 |
321 if __name__ == "__main__": | 346 if __name__ == "__main__": |
322 logging.basicConfig(level=logging.DEBUG) | 347 logging.basicConfig(level=logging.DEBUG) |
323 sys.exit(main()) | 348 sys.exit(main()) |
OLD | NEW |