#!/usr/bin/env python """ Xspice launcher and tester. Either run as "xspice" and it launches Xspice :10.0, then icewm, then gimp, and then remote-viewer, or run with "xspice test" and it will quit after two seconds from launching remote-viewer, and return a non 0 exit code for any problem (Xspice not loading). """ import sys import os import subprocess import time import atexit import argparse processes = [] def launch(*args, **kw): p = subprocess.Popen(*args, **kw) processes.append(p) return p def cleanup(*args): for p in reversed(processes): print "terminating %d" % p.pid try: p.terminate() except OSError: pass time.sleep(0.5) for p in reversed(processes): try: p.kill() except OSError: pass atexit.register(cleanup) VALGRIND=None #VALGRIND="valgrind --leak-check=full --track-origins=yes --log-file=/tmp/xspice.valgrind.log".split() #--trace-children=yes --tool=callgrind def which(x): return subprocess.check_output(['which', x]).strip() ################################### os.system("xspice-local-xkbcomp") def launch_xspice(dnum, port, args): display = ":%(dnum)s.0" % locals() which_python = which('python') which_Xspice = which('Xspice') cmd = [x % locals() for x in ["%(which_python)s", "%(which_Xspice)s", display, "--port", "%(port)s", "--disable-ticketing", ]] + args TLS_PORT_PARAMS = ["--tls-port", "0"] if VALGRIND: cmd = VALGRIND + cmd print ' '.join(cmd) logfile = open(os.path.expanduser('~/.Xspice.%(dnum)s.log' % locals()), 'w+') Xspice = launch(cmd, stdout=logfile, stderr=logfile) #$VALGRIND `which python` `which Xspice` :$DNUM.0 --port $PORT --disable-ticketing --tls-port 0 $* > spiceqxl = None with open('/proc/%s/maps' % Xspice.pid) as fd: spiceqxl_cand = [l.strip().split()[-1] for l in fd.readlines() if 'spiceqxl' in l] if len(spiceqxl_cand) == 1: spiceqxl = spiceqxl_cand[0] if not spiceqxl: # Cannot read /proc/Xspice.pid/maps since it's running as root, go the # second route for lib in ['lib', 'lib64']: spiceqxl = os.path.join(which_Xspice.rsplit('/', 2)[0], lib, 'xorg/modules/drivers/spiceqxl_drv.so') if os.path.exists(spiceqxl): break print spiceqxl assert(os.path.exists(spiceqxl)) Xspice.display = display Xspice.spiceqxl = spiceqxl Xspice.dnum = dnum return Xspice def port_available_via_netstat(port): """ (Not all processes could be identified, non-owned process info will not be shown, you would have to be root to see it all.) tcp 0 0 0.0.0.0:5900 0.0.0.0:* LISTEN 23216/Xorg off (0.00/0/0) tcp 0 0 0.0.0.0:6010 0.0.0.0:* LISTEN 23216/Xorg off (0.00/0/0) tcp6 0 0 :::6010 :::* LISTEN 23216/Xorg off (0.00/0/0) """ lines = subprocess.check_output(['netstat', '-ltUnop']).split('\n') port_lines = [l for l in lines if str(port) in l] return len(port_lines) > 0 def wait_for_xspice_load(xspice): expected = 6000 + xspice.dnum while True: if port_available_via_netstat(expected): return time.sleep(0.5) print "." if xspice.poll() is not None: # process has quit raise Exception("Xspice died") def test_xspice(): spiceqxl, Xspice = launch_xspice(':10.0', args = []) def main(wait=True): parser = argparse.ArgumentParser() parser.add_argument('--num', type=int, default=10) args, rest = parser.parse_known_args(sys.argv[1:]) DNUM = args.num PORT = 15000 + DNUM print "%s %s" % (DNUM, PORT) Xspice = launch_xspice(dnum=DNUM, port=PORT, args=rest) spiceqxl = Xspice.spiceqxl display = Xspice.display seconds_ago = time.time() - os.stat(spiceqxl).st_ctime print "age: %d:%2d:%2d old (%s)" % (seconds_ago // 3600, (seconds_ago // 60) % 60, seconds_ago % 60, spiceqxl) #time.sleep(3) wait_for_xspice_load(Xspice) client_display = os.environ['DISPLAY'] os.environ['DISPLAY'] = display icewm = launch(['icewm']) #DISPLAY=:$DNUM.0 firefox --no-remote -P spice_xpi_test & red = launch(['test-red']) gimp = launch(['gimp']) os.environ['DISPLAY'] = client_display remote_viewer = launch(['remote-viewer', 'spice://localhost:%s' % PORT]) if wait: remote_viewer.wait() else: time.sleep(2.0) # and quit if __name__ == '__main__': main()