Skip to content

Instantly share code, notes, and snippets.

@liftoff
Last active January 21, 2021 10:51
Show Gist options
  • Save liftoff/4741790 to your computer and use it in GitHub Desktop.
Save liftoff/4741790 to your computer and use it in GitHub Desktop.
An example of how to capture a screenshot using xpyb (xcb bindings for Python). UPDATED 2013-02-13: Now using PIL's Image.frombuffer() method on the raw image data object given by xcb. It is an order of magnitude faster than converting the xcb object to a Python string and using PIL's Image.fromstring().
#!/usr/bin/env python
"""
An example demonstrating how to use xpyb (xcb bindings for Python) to take a
full-screen screenshot.
"""
# Meta
__version__ = '1.0'
__version_info__ = (1, 0)
__license__ = "Apache 2.0"
__author__ = 'Dan McDougall <[email protected]>'
# Stdlib imports
import sys, os
# 3rd party imports
import xcb, xcb.xproto
import Image # PIL (I also recommend Pillow which is 100% compatible with PIL)
def xcb_screenshot_full(conn, path="/tmp/screenshot.png"):
"""
Given an XCB connection (*conn*), saves a full-screen screenshot to *path*.
"""
# XCB boilerplate stuff:
setup = conn.get_setup()
screen = setup.roots[0]
width = screen.width_in_pixels
height = screen.height_in_pixels
root = screen.root
# GetImage requires an output format as the first arg. We want ZPixmap:
output_format = xcb.xproto.ImageFormat.ZPixmap
plane_mask = 2**32 - 1 # No idea what this is but it works!
reply = conn.core.GetImage(
output_format, root, 0, 0, width, height, plane_mask).reply()
# The reply contains the image data in ZPixmap format. We need to convert
# it into something PIL can use:
image_data = reply.data.buf()
im = Image.frombuffer(
"RGBX", (width, height), image_data, "raw", "BGRX").convert("RGB")
with open(path, 'w') as f:
im.save(f, format='png')
if __name__ == "__main__":
x11_display = os.environ['DISPLAY']
print("Taking screenshot...")
try:
conn = xcb.connect(display=x11_display)
xcb_screenshot_full(conn)
except Exception as e:
print("Got an exception trying to take a screenshot!")
print("This might be helpful:")
print(e)
import traceback
traceback.print_exc(file=sys.stdout)
sys.exit(1)
sys.exit(0)
@etale-cohomology
Copy link

I wonder if there's any way to make it faster? The bottleneck seems to be conn.core.GetImage(output_format, root, 0, 0, width, height, plane_mask).reply(), which takes about 260ms to run on my PC.

@quadrupleslap
Copy link

  1. You can make it faster with the xcb-shm (the shared memory extension.)
  2. The plane mask is a filter for the RGB(A) planes, so you can potentially exclude them or reduce the number of bits in them, individually.
  3. Thanks!

@etale-cohomology
Copy link

@quadrupleslap I did use xcb-shm (the C bindings, though, not the Python ones), and, for large images, I saw a speedup of ~90x. Thank you!

(If anyone is interested, here's the code: https://github.com/etale-cohomology/fast-vision)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment