Skip to content

Repository files navigation

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:

workflow

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

Installation

install the program:

$ python3 -m pip install pyvirtualdisplay

optional: Pillow should be installed for smartdisplay submodule:

$ python3 -m pip install pillow

optional: EasyProcess should be installed for some examples:

$ python3 -m pip install EasyProcess

optional: xmessage and gnumeric should be installed for some examples.

On Ubuntu 22.04:

$ sudo apt install x11-utils gnumeric

If 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 EasyProcess

Usage

Controlling the display with context manager:

frompyvirtualdisplayimportDisplaywithDisplay() asdisp:
# display is activeprint(disp.is_alive()) # True# display is stoppedprint(disp.is_alive()) # False

Controlling the display with start() and stop() methods (not recommended):

frompyvirtualdisplayimportDisplaydisp=Display()
disp.start()
# display is activedisp.stop()
# display is stopped

After 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()
# ordisp=Display(visible=False)
# ordisp=Display(backend="xvfb")

Selecting Xephyr backend:

disp=Display(visible=True)
# ordisp=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)

Headless run

A messagebox is displayed on a hidden display.

# pyvirtualdisplay/examples/headless.py"Start Xvfb server. Open xmessage window."fromeasyprocessimportEasyProcessfrompyvirtualdisplayimportDisplaywithDisplay(visible=False, size=(100, 60)) asdisp:
withEasyProcess(["xmessage", "hello"]) asproc:
proc.wait()

Run it:

$ python3 -m pyvirtualdisplay.examples.headless

If visible=True then a nested Xephyr window opens and the GUI can be controlled.

vncserver

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"fromeasyprocessimportEasyProcessfrompyvirtualdisplayimportDisplaywithDisplay(backend="xvnc", size=(100, 60), rfbport=5904) asdisp:
withEasyProcess(["xmessage", "hello"]) asproc:
proc.wait()

Run it:

$ python3 -m pyvirtualdisplay.examples.vncserver

Check it with vncviewer:

$ vncviewer localhost:5904

GUI Test

# pyvirtualdisplay/examples/lowres.py"Testing gnumeric on low resolution."fromeasyprocessimportEasyProcessfrompyvirtualdisplayimportDisplay# start XephyrwithDisplay(visible=True, size=(320, 240)) asdisp:
# start GnumericwithEasyProcess(["gnumeric"]) asproc:
proc.wait()

Run it:

$ python3 -m pyvirtualdisplay.examples.lowres

Image:

Screenshot

# pyvirtualdisplay/examples/screenshot.py"Create screenshot of xmessage in background using 'smartdisplay' submodule"fromeasyprocessimportEasyProcessfrompyvirtualdisplay.smartdisplayimportSmartDisplay# 'SmartDisplay' instead of 'Display'# It has 'waitgrab()' method.# It has more dependencies than Display.withSmartDisplay() asdisp:
withEasyProcess(["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.screenshot

Image:

Nested Xephyr

# pyvirtualdisplay/examples/nested.py"Nested Xephyr servers"fromeasyprocessimportEasyProcessfrompyvirtualdisplayimportDisplaywithDisplay(visible=True, size=(220, 180), bgcolor="black"):
withDisplay(visible=True, size=(200, 160), bgcolor="white"):
withDisplay(visible=True, size=(180, 140), bgcolor="black"):
withDisplay(visible=True, size=(160, 120), bgcolor="white"):
withDisplay(visible=True, size=(140, 100), bgcolor="black"):
withDisplay(visible=True, size=(120, 80), bgcolor="white"):
withDisplay(visible=True, size=(100, 60), bgcolor="black"):
withEasyProcess(["xmessage", "hello"]) asproc:
proc.wait()

Run it:

$ python3 -m pyvirtualdisplay.examples.nested

Image:

xauth

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.

Mouse cursor

The cursor can be disabled in Xvfb using an extra argument which is passed directly to Xvfb:

withDisplay(backend="xvfb", extra_args=["-nocursor"]):
...

Based on Xvfb help:

...
-nocursor disable the cursor
...

Concurrency

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.

Thread safety

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."importthreadingfromeasyprocessimportEasyProcessfrompyvirtualdisplay.smartdisplayimportSmartDisplaydefthread_function(index):
# manage_global_env=False is thread safewithSmartDisplay(manage_global_env=False) asdisp:
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_varwithEasyProcess(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.threadsafe

Images:

Hierarchy

Alt text

About

Python wrapper for Xvfb, Xephyr and Xvnc

Resources

Stars

780 stars

Watchers

18 watching

Forks

Releases

Packages

Used by

Contributors

Languages