pyvirtualdisplay is a python wrapper for Xvfb, Xephyr and Xvnc programs. They all use the X Window System (not Windows, not macOS) The selected program should be installed first so that it can be started without a path, otherwise pyvirtualdisplay will not find the program.
Links:
Features:
- python wrapper
- supported python versions: 3.6, 3.7, 3.8, 3.9, 3.10, 3.11, 3.12
- back-ends: Xvfb, Xephyr and Xvnc
Possible applications:
- headless run
- GUI testing
- automatic GUI screenshot
install the program:
$ python3 -m pip install pyvirtualdisplayoptional: Pillow should be installed for smartdisplay submodule:
$ python3 -m pip install pillowoptional: EasyProcess should be installed for some examples:
$ python3 -m pip install EasyProcessoptional: xmessage and gnumeric should be installed for some examples.
On Ubuntu 22.04:
$ sudo apt install x11-utils gnumericIf you get this error message on Linux then your Pillow version is old.
ImportError: ImageGrab is macOS and Windows only Install all dependencies and backends on Ubuntu 22.04:
$ sudo apt-get install xvfb xserver-xephyr tigervnc-standalone-server x11-utils gnumeric $ python3 -m pip install pyvirtualdisplay pillow EasyProcessControlling the display with context manager:
from pyvirtualdisplay import Display with Display() as disp: # display is active print(disp.is_alive()) # True # display is stopped print(disp.is_alive()) # FalseControlling the display with start() and stop() methods (not recommended):
from pyvirtualdisplay import Display disp = Display() disp.start() # display is active disp.stop() # display is stoppedAfter Xvfb display is activated "DISPLAY" environment variable is set for Xvfb. (e.g. os.environ["DISPLAY"] = :1) After Xvfb display is stopped start() and stop() are not allowed to be called again, "DISPLAY" environment variable is restored to its original value.
Selecting Xvfb backend:
disp=Display() # or disp=Display(visible=False) # or disp=Display(backend="xvfb")Selecting Xephyr backend:
disp=Display(visible=True) # or disp=Display(backend="xephyr")Selecting Xvnc backend:
disp=Display(backend="xvnc")Setting display size:
disp=Display(size=(100, 60))Setting display color depth:
disp=Display(color_depth=24)A messagebox is displayed on a hidden display.
# pyvirtualdisplay/examples/headless.py "Start Xvfb server. Open xmessage window." from easyprocess import EasyProcess from pyvirtualdisplay import Display with Display(visible=False, size=(100, 60)) as disp: with EasyProcess(["xmessage", "hello"]) as proc: proc.wait()Run it:
$ python3 -m pyvirtualdisplay.examples.headlessIf visible=True then a nested Xephyr window opens and the GUI can be controlled.
The same as headless example, but it can be controlled with a VNC client.
# pyvirtualdisplay/examples/vncserver.py "Start virtual VNC server. Connect with: vncviewer localhost:5904" from easyprocess import EasyProcess from pyvirtualdisplay import Display with Display(backend="xvnc", size=(100, 60), rfbport=5904) as disp: with EasyProcess(["xmessage", "hello"]) as proc: proc.wait()Run it:
$ python3 -m pyvirtualdisplay.examples.vncserverCheck it with vncviewer:
$ vncviewer localhost:5904# pyvirtualdisplay/examples/lowres.py "Testing gnumeric on low resolution." from easyprocess import EasyProcess from pyvirtualdisplay import Display # start Xephyr with Display(visible=True, size=(320, 240)) as disp: # start Gnumeric with EasyProcess(["gnumeric"]) as proc: proc.wait()Run it:
$ python3 -m pyvirtualdisplay.examples.lowresImage:
# pyvirtualdisplay/examples/screenshot.py "Create screenshot of xmessage in background using 'smartdisplay' submodule" from easyprocess import EasyProcess from pyvirtualdisplay.smartdisplay import SmartDisplay # 'SmartDisplay' instead of 'Display' # It has 'waitgrab()' method. # It has more dependencies than Display. with SmartDisplay() as disp: with EasyProcess(["xmessage", "hello"]): # wait until something is displayed on the virtual display (polling method) # and then take a fullscreen screenshot # and then crop it. Background is black. img = disp.waitgrab() img.save("xmessage.png")Run it:
$ python3 -m pyvirtualdisplay.examples.screenshotImage:
# pyvirtualdisplay/examples/nested.py "Nested Xephyr servers" from easyprocess import EasyProcess from pyvirtualdisplay import Display with Display(visible=True, size=(220, 180), bgcolor="black"): with Display(visible=True, size=(200, 160), bgcolor="white"): with Display(visible=True, size=(180, 140), bgcolor="black"): with Display(visible=True, size=(160, 120), bgcolor="white"): with Display(visible=True, size=(140, 100), bgcolor="black"): with Display(visible=True, size=(120, 80), bgcolor="white"): with Display(visible=True, size=(100, 60), bgcolor="black"): with EasyProcess(["xmessage", "hello"]) as proc: proc.wait()Run it:
$ python3 -m pyvirtualdisplay.examples.nestedImage:
Some programs require a functional Xauthority file. PyVirtualDisplay can generate one and set the appropriate environment variables if you pass use_xauth=True to the Display constructor. Note however that this feature needs xauth installed, otherwise a pyvirtualdisplay.xauth.NotFoundError is raised.
The cursor can be disabled in Xvfb using an extra argument which is passed directly to Xvfb:
with Display(backend="xvfb", extra_args=["-nocursor"]): ...Based on Xvfb help:
... -nocursor disable the cursor ... If more X servers are started at the same time then there is race for free display numbers.
"Recent X servers as of version 1.13 (Xvfb, too) support the -displayfd command line option: It will make the X server choose the display itself" https://stackoverflow.com/questions/2520704/find-a-free-x11-display-number/
Version 1.13 was released in 2012: https://www.x.org/releases/individual/xserver/
First help text is checked (e.g. Xvfb -help) to find if -displayfd flag is available. If -displayfd flag is available then it is used to choose the display number. If not then a free display number is generated and there are 10 retries by default which should be enough for starting 10 X servers at the same time.
displayfd usage is disabled on macOS because it doesn't work with XQuartz-2.7.11, always 0 is returned.
All previous examples are not thread-safe, because pyvirtualdisplay replaces $DISPLAY environment variable in global os.environ in start() and sets back to original value in stop(). To make it thread-safe you have to manage the $DISPLAY variable. Set manage_global_env to False in constructor.
# pyvirtualdisplay/examples/threadsafe.py "Start Xvfb server and open xmessage window. Thread safe." import threading from easyprocess import EasyProcess from pyvirtualdisplay.smartdisplay import SmartDisplay def thread_function(index): # manage_global_env=False is thread safe with SmartDisplay(manage_global_env=False) as disp: cmd = ["xmessage", str(index)] # disp.new_display_var should be used for new processes # disp.env() copies global os.environ and adds disp.new_display_var with EasyProcess(cmd, env=disp.env()): img = disp.waitgrab() img.save("xmessage{}.png".format(index)) t1 = threading.Thread(target=thread_function, args=(1,)) t2 = threading.Thread(target=thread_function, args=(2,)) t1.start() t2.start() t1.join() t2.join()Run it:
$ python3 -m pyvirtualdisplay.examples.threadsafeImages:





