deb-python-xvfbwrapper/xvfbwrapper.py

95 lines
3.0 KiB
Python

#!/usr/bin/env python
#
# * Corey Goldberg, 2012, 2013, 2015, 2016
#
# * inspired by: PyVirtualDisplay
"""wrapper for running display inside X virtual framebuffer (Xvfb)"""
import os
import fnmatch
import subprocess
import tempfile
import time
class Xvfb:
def __init__(self, width=800, height=680, colordepth=24, **kwargs):
self.width = width
self.height = height
self.colordepth = colordepth
if not self.xvfb_exists():
msg = 'Can not find Xvfb. Please install it and try again.'
raise EnvironmentError(msg)
self.extra_xvfb_args = ['-screen', '0', '{}x{}x{}'.format(
self.width, self.height, self.colordepth)]
for key, value in kwargs.items():
self.extra_xvfb_args += ['-{}'.format(key), value]
if 'DISPLAY' in os.environ:
self.orig_display = os.environ['DISPLAY'].split(':')[1]
else:
self.orig_display = None
self.proc = None
def __enter__(self):
self.start()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.stop()
def start(self):
self.new_display = self._get_next_unused_display()
display_var = ':{}'.format(self.new_display)
self.xvfb_cmd = ['Xvfb', display_var] + self.extra_xvfb_args
with open(os.devnull, 'w') as fnull:
self.proc = subprocess.Popen(self.xvfb_cmd,
stdout=fnull,
stderr=fnull,
close_fds=True)
time.sleep(0.1) # give Xvfb time to start
ret_code = self.proc.poll()
if ret_code is None:
self._set_display_var(self.new_display)
else:
raise RuntimeError('Xvfb did not start')
def stop(self):
if self.orig_display is None:
del os.environ['DISPLAY']
else:
self._set_display_var(self.orig_display)
# TODO:
# fix leaking X displays.
# (killing Xvfb process doesn't clean up the underlying X11 server)
if self.proc is not None:
self.proc.kill()
self.proc.wait()
self.proc = None
def _get_next_unused_display(self):
tmpdir = tempfile.gettempdir()
pattern = '.X*-lock'
lockfile_names = fnmatch.filter(os.listdir(tmpdir), pattern)
existing_displays = [int(name.split('X')[1].split('-')[0])
for name in lockfile_names]
highest_display = max(existing_displays) if existing_displays else 0
return highest_display + 1
def xvfb_exists(self):
"""Check that Xvfb is available on PATH and is executable."""
paths = os.environ['PATH'].split(os.pathsep)
return any(os.access(os.path.join(path, 'Xvfb'), os.X_OK)
for path in paths)
def _set_display_var(self, display):
os.environ['DISPLAY'] = ':{}'.format(display)