Skip to content

Commit

Permalink
xcb screengrab support
Browse files Browse the repository at this point in the history
  • Loading branch information
nulano authored and radarhere committed Mar 24, 2020
1 parent 291f1eb commit 3c39e6f
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 34 deletions.
10 changes: 10 additions & 0 deletions setup.py
Expand Up @@ -286,6 +286,7 @@ class feature:
"webpmux",
"jpeg2000",
"imagequant",
"xcb",
]

required = {"jpeg", "zlib"}
Expand Down Expand Up @@ -681,6 +682,12 @@ def build_extensions(self):
):
feature.webpmux = "libwebpmux"

if feature.want("xcb"):
_dbg("Looking for xcb")
if _find_include_file(self, "xcb/xcb.h"):
if _find_library_file(self, "xcb"):
feature.xcb = "xcb"

for f in feature:
if not getattr(feature, f) and feature.require(f):
if f in ("jpeg", "zlib"):
Expand Down Expand Up @@ -715,6 +722,9 @@ def build_extensions(self):
if feature.tiff:
libs.append(feature.tiff)
defs.append(("HAVE_LIBTIFF", None))
if feature.xcb:
libs.append(feature.xcb)
defs.append(("HAVE_XCB", None))
if sys.platform == "win32":
libs.extend(["kernel32", "user32", "gdi32"])
if struct.unpack("h", b"\0\1")[0] == 1:
Expand Down
88 changes: 56 additions & 32 deletions src/PIL/ImageGrab.py
Expand Up @@ -22,38 +22,62 @@

from . import Image

if sys.platform not in ["win32", "darwin"]:
raise ImportError("ImageGrab is macOS and Windows only")
if sys.platform == "win32":
pass
elif sys.platform == "darwin":
import os
import tempfile
import subprocess
elif not Image.core.HAVE_XCB:
raise ImportError("ImageGrab requires Windows, macOS, or the XCB library")


def grab(bbox=None, include_layered_windows=False, all_screens=False):
if sys.platform == "darwin":
fh, filepath = tempfile.mkstemp(".png")
os.close(fh)
subprocess.call(["screencapture", "-x", filepath])
im = Image.open(filepath)
im.load()
os.unlink(filepath)
if bbox:
im_cropped = im.crop(bbox)
im.close()
return im_cropped
else:
offset, size, data = Image.core.grabscreen(include_layered_windows, all_screens)
im = Image.frombytes(
"RGB",
size,
data,
# RGB, 32-bit line padding, origin lower left corner
"raw",
"BGR",
(size[0] * 3 + 3) & -4,
-1,
)
if bbox:
x0, y0 = offset
left, top, right, bottom = bbox
im = im.crop((left - x0, top - y0, right - x0, bottom - y0))
def grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=None):
if xdisplay is None:
if sys.platform == "darwin":
fh, filepath = tempfile.mkstemp(".png")
os.close(fh)
subprocess.call(["screencapture", "-x", filepath])
im = Image.open(filepath)
im.load()
os.unlink(filepath)
if bbox:
im_cropped = im.crop(bbox)
im.close()
return im_cropped
return im
elif sys.platform == "win32":
offset, size, data = Image.core.grabscreen(include_layered_windows, all_screens)
im = Image.frombytes(
"RGB",
size,
data,
# RGB, 32-bit line padding, origin lower left corner
"raw",
"BGR",
(size[0] * 3 + 3) & -4,
-1,
)
if bbox:
x0, y0 = offset
left, top, right, bottom = bbox
im = im.crop((left - x0, top - y0, right - x0, bottom - y0))
return im
# use xdisplay=None for default display on non-win32/macOS systems
if not Image.core.HAVE_XCB:
raise OSError("XCB support not included")
size, data = Image.core.grabscreen_x11(xdisplay)
im = Image.frombytes(
"RGB",
size,
data,
"raw",
"BGRX",
size[0] * 4,
1,
)
if bbox:
im = im.crop(bbox)
return im


Expand Down Expand Up @@ -81,8 +105,8 @@ def grabclipboard():
im.load()
os.unlink(filepath)
return im
else:
data = Image.core.grabclipboard()
elif sys.platform == "win32":
data = Image.core.grabclipboard_win32()
if isinstance(data, bytes):
from . import BmpImagePlugin
import io
Expand Down
2 changes: 2 additions & 0 deletions src/PIL/features.py
Expand Up @@ -56,6 +56,7 @@ def get_supported_codecs():
"raqm": ("PIL._imagingft", "HAVE_RAQM"),
"libjpeg_turbo": ("PIL._imaging", "HAVE_LIBJPEGTURBO"),
"libimagequant": ("PIL._imaging", "HAVE_LIBIMAGEQUANT"),
"xcb": ("PIL._imaging", "HAVE_XCB"),
}


Expand Down Expand Up @@ -132,6 +133,7 @@ def pilinfo(out=None, supported_formats=True):
("libtiff", "LIBTIFF"),
("raqm", "RAQM (Bidirectional Text)"),
("libimagequant", "LIBIMAGEQUANT (Quantization method)"),
("xcb", "XCB (X protocol)"),
]:
if check(name):
print("---", feature, "support ok", file=out)
Expand Down
16 changes: 14 additions & 2 deletions src/_imaging.c
Expand Up @@ -3781,6 +3781,9 @@ extern PyObject* PyImaging_ListWindowsWin32(PyObject* self, PyObject* args);
extern PyObject* PyImaging_EventLoopWin32(PyObject* self, PyObject* args);
extern PyObject* PyImaging_DrawWmf(PyObject* self, PyObject* args);
#endif
#ifdef HAVE_XCB
extern PyObject* PyImaging_GrabScreenX11(PyObject* self, PyObject* args);
#endif

/* Experimental path stuff (in path.c) */
extern PyObject* PyPath_Create(ImagingObject* self, PyObject* args);
Expand Down Expand Up @@ -3853,13 +3856,16 @@ static PyMethodDef functions[] = {
#ifdef _WIN32
{"display", (PyCFunction)PyImaging_DisplayWin32, 1},
{"display_mode", (PyCFunction)PyImaging_DisplayModeWin32, 1},
{"grabscreen", (PyCFunction)PyImaging_GrabScreenWin32, 1},
{"grabclipboard", (PyCFunction)PyImaging_GrabClipboardWin32, 1},
{"grabscreen_win32", (PyCFunction)PyImaging_GrabScreenWin32, 1},
{"grabclipboard_win32", (PyCFunction)PyImaging_GrabClipboardWin32, 1},
{"createwindow", (PyCFunction)PyImaging_CreateWindowWin32, 1},
{"eventloop", (PyCFunction)PyImaging_EventLoopWin32, 1},
{"listwindows", (PyCFunction)PyImaging_ListWindowsWin32, 1},
{"drawwmf", (PyCFunction)PyImaging_DrawWmf, 1},
#endif
#ifdef HAVE_XCB
{"grabscreen_x11", (PyCFunction)PyImaging_GrabScreenX11, 1},
#endif

/* Utilities */
{"getcodecstatus", (PyCFunction)_getcodecstatus, 1},
Expand Down Expand Up @@ -3979,6 +3985,12 @@ setup_module(PyObject* m) {
}
#endif

#ifdef HAVE_XCB
PyModule_AddObject(m, "HAVE_XCB", Py_True);
#else
PyModule_AddObject(m, "HAVE_XCB", Py_False);
#endif

PyDict_SetItemString(d, "PILLOW_VERSION", PyUnicode_FromString(version));

return 0;
Expand Down
79 changes: 79 additions & 0 deletions src/display.c
Expand Up @@ -815,3 +815,82 @@ PyImaging_DrawWmf(PyObject* self, PyObject* args)
}

#endif /* _WIN32 */

/* -------------------------------------------------------------------- */
/* X11 support */

#ifdef HAVE_XCB
#include <xcb/xcb.h>

/* -------------------------------------------------------------------- */
/* X11 screen grabber */

PyObject*
PyImaging_GrabScreenX11(PyObject* self, PyObject* args)
{
int width, height;
char* display_name;
xcb_connection_t* connection;
int screen_number;
xcb_screen_iterator_t iter;
xcb_screen_t* screen = NULL;
xcb_get_image_reply_t* reply;
xcb_generic_error_t* error;
PyObject* buffer = NULL;

if (!PyArg_ParseTuple(args, "|z", &display_name))
return NULL;

/* connect to X and get screen data */

connection = xcb_connect(display_name, &screen_number);
if (xcb_connection_has_error(connection)) {
xcb_disconnect(connection);
PyErr_SetString(PyExc_IOError, "X connection failed");
return NULL;
}

iter = xcb_setup_roots_iterator(xcb_get_setup(connection));
for (; iter.rem; --screen_number, xcb_screen_next(&iter)) {
if (screen_number == 0) {
screen = iter.data;
break;
}
}
if (screen == NULL) {
xcb_disconnect(connection);
PyErr_SetString(PyExc_IOError, "X screen not found");
return NULL;
}

width = screen->width_in_pixels;
height = screen->height_in_pixels;

/* get image data */

reply = xcb_get_image_reply(connection,
xcb_get_image(connection, XCB_IMAGE_FORMAT_Z_PIXMAP, screen->root,
0, 0, width, height, 0x00ffffff),
&error);
if (reply == NULL) {
free(error);
xcb_disconnect(connection);
PyErr_SetString(PyExc_IOError, "X get_image failed");
return NULL;
}

/* store data in Python buffer */

buffer = PyBytes_FromStringAndSize((char*)xcb_get_image_data(reply),
xcb_get_image_data_length(reply));

free(reply);
xcb_disconnect(connection);

if (!buffer)
return NULL;

return Py_BuildValue("(ii)N", width, height, buffer);
}

#endif /* HAVE_XCB */

0 comments on commit 3c39e6f

Please sign in to comment.