xcb screengrab support

This commit is contained in:
nulano 2019-09-19 18:57:59 +02:00 committed by Andrew Murray
parent 291f1eb1e2
commit 3c39e6fcf6
5 changed files with 161 additions and 34 deletions

View File

@ -286,6 +286,7 @@ class pil_build_ext(build_ext):
"webpmux",
"jpeg2000",
"imagequant",
"xcb",
]
required = {"jpeg", "zlib"}
@ -681,6 +682,12 @@ class pil_build_ext(build_ext):
):
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"):
@ -715,6 +722,9 @@ class pil_build_ext(build_ext):
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:

View File

@ -22,38 +22,62 @@ import tempfile
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
@ -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

View File

@ -56,6 +56,7 @@ features = {
"raqm": ("PIL._imagingft", "HAVE_RAQM"),
"libjpeg_turbo": ("PIL._imaging", "HAVE_LIBJPEGTURBO"),
"libimagequant": ("PIL._imaging", "HAVE_LIBIMAGEQUANT"),
"xcb": ("PIL._imaging", "HAVE_XCB"),
}
@ -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)

View File

@ -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);
@ -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},
@ -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;

View File

@ -815,3 +815,82 @@ error:
}
#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 */