Pillow/src/display.c
eyedav 9527ce7f8c change mode structs to enums
Structs have better type safety, but they make allocation more difficult, especially when we have multiple Python modules trying to share the same code.
2025-07-19 16:54:32 +02:00

942 lines
24 KiB
C

/*
* The Python Imaging Library.
*
* display support (and other windows-related stuff)
*
* History:
* 1996-05-13 fl Windows DIB support
* 1996-05-21 fl Added palette stuff
* 1996-05-28 fl Added display_mode stuff
* 1997-09-21 fl Added draw primitive
* 2001-09-17 fl Added ImagingGrabScreen (from _grabscreen.c)
* 2002-05-12 fl Added ImagingListWindows
* 2002-11-19 fl Added clipboard support
* 2002-11-25 fl Added GetDC/ReleaseDC helpers
* 2003-05-21 fl Added create window support (including window callback)
* 2003-09-05 fl Added fromstring/tostring methods
* 2009-03-14 fl Added WMF support (from pilwmf)
*
* Copyright (c) 1997-2003 by Secret Labs AB.
* Copyright (c) 1996-1997 by Fredrik Lundh.
*
* See the README file for information on usage and redistribution.
*/
#define PY_SSIZE_T_CLEAN
#include "Python.h"
#include "libImaging/Imaging.h"
/* -------------------------------------------------------------------- */
/* Windows DIB support */
#ifdef _WIN32
#include "libImaging/ImDib.h"
#if SIZEOF_VOID_P == 8
#define F_HANDLE "K"
#else
#define F_HANDLE "k"
#endif
typedef struct {
PyObject_HEAD ImagingDIB dib;
} ImagingDisplayObject;
static PyTypeObject ImagingDisplayType;
static ImagingDisplayObject *
_new(const ModeID mode, int xsize, int ysize) {
ImagingDisplayObject *display;
if (PyType_Ready(&ImagingDisplayType) < 0) {
return NULL;
}
display = PyObject_New(ImagingDisplayObject, &ImagingDisplayType);
if (display == NULL) {
return NULL;
}
display->dib = ImagingNewDIB(mode, xsize, ysize);
if (!display->dib) {
Py_DECREF(display);
return NULL;
}
return display;
}
static void
_delete(ImagingDisplayObject *display) {
if (display->dib) {
ImagingDeleteDIB(display->dib);
}
PyObject_Del(display);
}
static PyObject *
_expose(ImagingDisplayObject *display, PyObject *args) {
HDC hdc;
if (!PyArg_ParseTuple(args, F_HANDLE, &hdc)) {
return NULL;
}
ImagingExposeDIB(display->dib, hdc);
Py_RETURN_NONE;
}
static PyObject *
_draw(ImagingDisplayObject *display, PyObject *args) {
HDC hdc;
int dst[4];
int src[4];
if (!PyArg_ParseTuple(
args,
F_HANDLE "(iiii)(iiii)",
&hdc,
dst + 0,
dst + 1,
dst + 2,
dst + 3,
src + 0,
src + 1,
src + 2,
src + 3
)) {
return NULL;
}
ImagingDrawDIB(display->dib, hdc, dst, src);
Py_RETURN_NONE;
}
extern Imaging
PyImaging_AsImaging(PyObject *op);
static PyObject *
_paste(ImagingDisplayObject *display, PyObject *args) {
Imaging im;
PyObject *op;
int xy[4];
xy[0] = xy[1] = xy[2] = xy[3] = 0;
if (!PyArg_ParseTuple(args, "O|(iiii)", &op, xy + 0, xy + 1, xy + 2, xy + 3)) {
return NULL;
}
im = PyImaging_AsImaging(op);
if (!im) {
return NULL;
}
if (xy[2] <= xy[0]) {
xy[2] = xy[0] + im->xsize;
}
if (xy[3] <= xy[1]) {
xy[3] = xy[1] + im->ysize;
}
ImagingPasteDIB(display->dib, im, xy);
Py_RETURN_NONE;
}
static PyObject *
_query_palette(ImagingDisplayObject *display, PyObject *args) {
HDC hdc;
int status;
if (!PyArg_ParseTuple(args, F_HANDLE, &hdc)) {
return NULL;
}
status = ImagingQueryPaletteDIB(display->dib, hdc);
return Py_BuildValue("i", status);
}
static PyObject *
_getdc(ImagingDisplayObject *display, PyObject *args) {
HWND window;
HDC dc;
if (!PyArg_ParseTuple(args, F_HANDLE, &window)) {
return NULL;
}
dc = GetDC(window);
if (!dc) {
PyErr_SetString(PyExc_OSError, "cannot create dc");
return NULL;
}
return Py_BuildValue(F_HANDLE, dc);
}
static PyObject *
_releasedc(ImagingDisplayObject *display, PyObject *args) {
HWND window;
HDC dc;
if (!PyArg_ParseTuple(args, F_HANDLE F_HANDLE, &window, &dc)) {
return NULL;
}
ReleaseDC(window, dc);
Py_RETURN_NONE;
}
static PyObject *
_frombytes(ImagingDisplayObject *display, PyObject *args) {
Py_buffer buffer;
if (!PyArg_ParseTuple(args, "y*:frombytes", &buffer)) {
return NULL;
}
if (display->dib->ysize * display->dib->linesize != buffer.len) {
PyBuffer_Release(&buffer);
PyErr_SetString(PyExc_ValueError, "wrong size");
return NULL;
}
memcpy(display->dib->bits, buffer.buf, buffer.len);
PyBuffer_Release(&buffer);
Py_RETURN_NONE;
}
static PyObject *
_tobytes(ImagingDisplayObject *display, PyObject *args) {
if (!PyArg_ParseTuple(args, ":tobytes")) {
return NULL;
}
return PyBytes_FromStringAndSize(
display->dib->bits, display->dib->ysize * display->dib->linesize
);
}
static struct PyMethodDef methods[] = {
{"draw", (PyCFunction)_draw, METH_VARARGS},
{"expose", (PyCFunction)_expose, METH_VARARGS},
{"paste", (PyCFunction)_paste, METH_VARARGS},
{"query_palette", (PyCFunction)_query_palette, METH_VARARGS},
{"getdc", (PyCFunction)_getdc, METH_VARARGS},
{"releasedc", (PyCFunction)_releasedc, METH_VARARGS},
{"frombytes", (PyCFunction)_frombytes, METH_VARARGS},
{"tobytes", (PyCFunction)_tobytes, METH_VARARGS},
{NULL, NULL} /* sentinel */
};
static PyObject *
_getattr_mode(ImagingDisplayObject *self, void *closure) {
return Py_BuildValue("s", getModeData(self->dib->mode)->name);
}
static PyObject *
_getattr_size(ImagingDisplayObject *self, void *closure) {
return Py_BuildValue("ii", self->dib->xsize, self->dib->ysize);
}
static struct PyGetSetDef getsetters[] = {
{"mode", (getter)_getattr_mode}, {"size", (getter)_getattr_size}, {NULL}
};
static PyTypeObject ImagingDisplayType = {
PyVarObject_HEAD_INIT(NULL, 0).tp_name = "ImagingDisplay",
.tp_basicsize = sizeof(ImagingDisplayObject),
.tp_dealloc = (destructor)_delete,
.tp_methods = methods,
.tp_getset = getsetters,
};
PyObject *
PyImaging_DisplayWin32(PyObject *self, PyObject *args) {
ImagingDisplayObject *display;
char *mode_name;
int xsize, ysize;
if (!PyArg_ParseTuple(args, "s(ii)", &mode_name, &xsize, &ysize)) {
return NULL;
}
const ModeID mode = findModeID(mode_name);
display = _new(mode, xsize, ysize);
if (display == NULL) {
return NULL;
}
return (PyObject *)display;
}
PyObject *
PyImaging_DisplayModeWin32(PyObject *self, PyObject *args) {
int size[2];
const ModeID mode = ImagingGetModeDIB(size);
return Py_BuildValue("s(ii)", getModeData(mode)->name, size[0], size[1]);
}
/* -------------------------------------------------------------------- */
/* Windows screen grabber */
typedef HANDLE(__stdcall *Func_GetWindowDpiAwarenessContext)(HANDLE);
typedef HANDLE(__stdcall *Func_SetThreadDpiAwarenessContext)(HANDLE);
PyObject *
PyImaging_GrabScreenWin32(PyObject *self, PyObject *args) {
int x = 0, y = 0, width = -1, height;
int includeLayeredWindows = 0, screens = 0;
HBITMAP bitmap;
BITMAPCOREHEADER core;
HDC screen, screen_copy;
HWND wnd;
DWORD rop;
PyObject *buffer;
HANDLE dpiAwareness = NULL;
HMODULE user32;
Func_GetWindowDpiAwarenessContext GetWindowDpiAwarenessContext_function;
Func_SetThreadDpiAwarenessContext SetThreadDpiAwarenessContext_function;
if (!PyArg_ParseTuple(
args, "|ii" F_HANDLE, &includeLayeredWindows, &screens, &wnd
)) {
return NULL;
}
/* step 1: create a memory DC large enough to hold the
entire screen */
if (screens == -1) {
screen = GetDC(wnd);
if (screen == NULL) {
PyErr_SetString(PyExc_OSError, "unable to get device context for handle");
return NULL;
}
} else {
screen = CreateDC("DISPLAY", NULL, NULL, NULL);
}
screen_copy = CreateCompatibleDC(screen);
// added in Windows 10 (1607)
// loaded dynamically to avoid link errors
user32 = LoadLibraryA("User32.dll");
SetThreadDpiAwarenessContext_function = (Func_SetThreadDpiAwarenessContext)
GetProcAddress(user32, "SetThreadDpiAwarenessContext");
if (SetThreadDpiAwarenessContext_function != NULL) {
GetWindowDpiAwarenessContext_function = (Func_GetWindowDpiAwarenessContext)
GetProcAddress(user32, "GetWindowDpiAwarenessContext");
if (screens == -1 && GetWindowDpiAwarenessContext_function != NULL) {
dpiAwareness = GetWindowDpiAwarenessContext_function(wnd);
}
// DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE = ((DPI_CONTEXT_HANDLE)-3)
dpiAwareness = SetThreadDpiAwarenessContext_function(
dpiAwareness == NULL ? (HANDLE)-3 : dpiAwareness
);
}
if (screens == 1) {
x = GetSystemMetrics(SM_XVIRTUALSCREEN);
y = GetSystemMetrics(SM_YVIRTUALSCREEN);
width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
} else if (screens == -1) {
RECT rect;
if (GetClientRect(wnd, &rect)) {
width = rect.right;
height = rect.bottom;
}
} else {
width = GetDeviceCaps(screen, HORZRES);
height = GetDeviceCaps(screen, VERTRES);
}
if (SetThreadDpiAwarenessContext_function != NULL) {
SetThreadDpiAwarenessContext_function(dpiAwareness);
}
FreeLibrary(user32);
if (width == -1) {
goto error;
}
bitmap = CreateCompatibleBitmap(screen, width, height);
if (!bitmap) {
goto error;
}
if (!SelectObject(screen_copy, bitmap)) {
goto error;
}
/* step 2: copy bits into memory DC bitmap */
rop = SRCCOPY;
if (includeLayeredWindows) {
rop |= CAPTUREBLT;
}
if (!BitBlt(screen_copy, 0, 0, width, height, screen, x, y, rop)) {
goto error;
}
/* step 3: extract bits from bitmap */
buffer = PyBytes_FromStringAndSize(NULL, height * ((width * 3 + 3) & -4));
if (!buffer) {
return NULL;
}
core.bcSize = sizeof(core);
core.bcWidth = width;
core.bcHeight = height;
core.bcPlanes = 1;
core.bcBitCount = 24;
if (!GetDIBits(
screen_copy,
bitmap,
0,
height,
PyBytes_AS_STRING(buffer),
(BITMAPINFO *)&core,
DIB_RGB_COLORS
)) {
goto error;
}
DeleteObject(bitmap);
DeleteDC(screen_copy);
if (screens == -1) {
ReleaseDC(wnd, screen);
} else {
DeleteDC(screen);
}
return Py_BuildValue("(ii)(ii)N", x, y, width, height, buffer);
error:
PyErr_SetString(PyExc_OSError, "screen grab failed");
DeleteDC(screen_copy);
if (screens == -1) {
ReleaseDC(wnd, screen);
} else {
DeleteDC(screen);
}
return NULL;
}
/* -------------------------------------------------------------------- */
/* Windows clipboard grabber */
PyObject *
PyImaging_GrabClipboardWin32(PyObject *self, PyObject *args) {
HANDLE handle = NULL;
int size;
void *data;
PyObject *result;
UINT format;
UINT formats[] = {CF_DIB, CF_DIBV5, CF_HDROP, RegisterClipboardFormatA("PNG"), 0};
LPCSTR format_names[] = {"DIB", "DIB", "file", "png", NULL};
if (!OpenClipboard(NULL)) {
// Maybe the clipboard is temporarily in use by another process.
// Wait and try again
Sleep(500);
if (!OpenClipboard(NULL)) {
PyErr_SetString(PyExc_OSError, "failed to open clipboard");
return NULL;
}
}
// find best format as set by clipboard owner
format = 0;
while (!handle && (format = EnumClipboardFormats(format))) {
for (UINT i = 0; formats[i] != 0; i++) {
if (format == formats[i]) {
handle = GetClipboardData(format);
format = i;
break;
}
}
}
if (!handle) {
CloseClipboard();
return Py_BuildValue("zO", NULL, Py_None);
}
data = GlobalLock(handle);
size = GlobalSize(handle);
result = PyBytes_FromStringAndSize(data, size);
GlobalUnlock(handle);
CloseClipboard();
return Py_BuildValue("zN", format_names[format], result);
}
/* -------------------------------------------------------------------- */
/* Windows class */
#ifndef WM_MOUSEWHEEL
#define WM_MOUSEWHEEL 522
#endif
static int mainloop = 0;
static void
callback_error(const char *handler) {
PyObject *sys_stderr;
sys_stderr = PySys_GetObject("stderr");
if (sys_stderr) {
PyFile_WriteString("*** ImageWin: error in ", sys_stderr);
PyFile_WriteString((char *)handler, sys_stderr);
PyFile_WriteString(":\n", sys_stderr);
}
PyErr_Print();
PyErr_Clear();
}
static LRESULT CALLBACK
windowCallback(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam) {
PAINTSTRUCT ps;
PyObject *callback = NULL;
PyObject *result;
PyThreadState *threadstate;
PyThreadState *current_threadstate;
HDC dc;
RECT rect;
LRESULT status = 0;
/* set up threadstate for messages that calls back into python */
switch (message) {
case WM_CREATE:
mainloop++;
break;
case WM_DESTROY:
mainloop--;
/* fall through... */
case WM_PAINT:
case WM_SIZE:
callback = (PyObject *)GetWindowLongPtr(wnd, 0);
if (callback) {
threadstate =
(PyThreadState *)GetWindowLongPtr(wnd, sizeof(PyObject *));
current_threadstate = PyThreadState_Swap(NULL);
PyEval_RestoreThread(threadstate);
} else {
return DefWindowProc(wnd, message, wParam, lParam);
}
}
/* process message */
switch (message) {
case WM_PAINT:
/* redraw (part of) window. this generates a WCK-style
damage/clear/repair cascade */
BeginPaint(wnd, &ps);
dc = GetDC(wnd);
GetWindowRect(wnd, &rect); /* in screen coordinates */
result = PyObject_CallFunction(
callback,
"siiii",
"damage",
ps.rcPaint.left,
ps.rcPaint.top,
ps.rcPaint.right,
ps.rcPaint.bottom
);
if (result) {
Py_DECREF(result);
} else {
callback_error("window damage callback");
}
result = PyObject_CallFunction(
callback,
"s" F_HANDLE "iiii",
"clear",
dc,
0,
0,
rect.right - rect.left,
rect.bottom - rect.top
);
if (result) {
Py_DECREF(result);
} else {
callback_error("window clear callback");
}
result = PyObject_CallFunction(
callback,
"s" F_HANDLE "iiii",
"repair",
dc,
0,
0,
rect.right - rect.left,
rect.bottom - rect.top
);
if (result) {
Py_DECREF(result);
} else {
callback_error("window repair callback");
}
ReleaseDC(wnd, dc);
EndPaint(wnd, &ps);
break;
case WM_SIZE:
/* resize window */
result = PyObject_CallFunction(
callback, "sii", "resize", LOWORD(lParam), HIWORD(lParam)
);
if (result) {
InvalidateRect(wnd, NULL, 1);
Py_DECREF(result);
} else {
callback_error("window resize callback");
}
break;
case WM_DESTROY:
/* destroy window */
result = PyObject_CallFunction(callback, "s", "destroy");
if (result) {
Py_DECREF(result);
} else {
callback_error("window destroy callback");
}
Py_DECREF(callback);
break;
default:
status = DefWindowProc(wnd, message, wParam, lParam);
}
if (callback) {
/* restore thread state */
PyEval_SaveThread();
PyThreadState_Swap(current_threadstate);
}
return status;
}
PyObject *
PyImaging_CreateWindowWin32(PyObject *self, PyObject *args) {
HWND wnd;
WNDCLASS windowClass;
char *title;
PyObject *callback;
int width = 0, height = 0;
if (!PyArg_ParseTuple(args, "sO|ii", &title, &callback, &width, &height)) {
return NULL;
}
if (width <= 0) {
width = CW_USEDEFAULT;
}
if (height <= 0) {
height = CW_USEDEFAULT;
}
/* register toplevel window class */
windowClass.style = CS_CLASSDC;
windowClass.cbClsExtra = 0;
windowClass.cbWndExtra = sizeof(PyObject *) + sizeof(PyThreadState *);
windowClass.hInstance = GetModuleHandle(NULL);
/* windowClass.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1); */
windowClass.hbrBackground = NULL;
windowClass.lpszMenuName = NULL;
windowClass.lpszClassName = "pilWindow";
windowClass.lpfnWndProc = windowCallback;
windowClass.hIcon = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(1));
windowClass.hCursor = LoadCursor(NULL, IDC_ARROW); /* CROSS? */
RegisterClass(&windowClass); /* FIXME: check return status */
wnd = CreateWindowEx(
0,
windowClass.lpszClassName,
title,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
width,
height,
HWND_DESKTOP,
NULL,
NULL,
NULL
);
if (!wnd) {
PyErr_SetString(PyExc_OSError, "failed to create window");
return NULL;
}
/* register window callback */
Py_INCREF(callback);
SetWindowLongPtr(wnd, 0, (LONG_PTR)callback);
SetWindowLongPtr(wnd, sizeof(callback), (LONG_PTR)PyThreadState_Get());
Py_BEGIN_ALLOW_THREADS;
ShowWindow(wnd, SW_SHOWNORMAL);
SetForegroundWindow(wnd); /* to make sure it's visible */
Py_END_ALLOW_THREADS;
return Py_BuildValue(F_HANDLE, wnd);
}
PyObject *
PyImaging_EventLoopWin32(PyObject *self, PyObject *args) {
MSG msg;
Py_BEGIN_ALLOW_THREADS;
while (mainloop && GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
Py_END_ALLOW_THREADS;
Py_RETURN_NONE;
}
/* -------------------------------------------------------------------- */
/* windows WMF renderer */
#define GET32(p, o) ((DWORD *)(p + o))[0]
static int CALLBACK
enhMetaFileProc(
HDC hdc, HANDLETABLE *lpht, const ENHMETARECORD *lpmr, int nHandles, LPARAM data
) {
PlayEnhMetaFileRecord(hdc, lpht, lpmr, nHandles);
return 1;
}
PyObject *
PyImaging_DrawWmf(PyObject *self, PyObject *args) {
HBITMAP bitmap;
HENHMETAFILE meta;
BITMAPCOREHEADER core;
HDC dc;
RECT rect;
PyObject *buffer = NULL;
void *ptr;
char *data;
Py_ssize_t datasize;
int width, height;
int x0, y0, x1, y1;
if (!PyArg_ParseTuple(
args,
"y#(ii)(iiii):_load",
&data,
&datasize,
&width,
&height,
&x0,
&x1,
&y0,
&y1
)) {
return NULL;
}
/* step 1: copy metafile contents into METAFILE object */
if (datasize > 22 && GET32(data, 0) == 0x9ac6cdd7) {
/* placeable windows metafile (22-byte aldus header) */
meta = SetWinMetaFileBits(datasize - 22, data + 22, NULL, NULL);
} else if (datasize > 80 && GET32(data, 0) == 1 && GET32(data, 40) == 0x464d4520) {
/* enhanced metafile */
meta = SetEnhMetaFileBits(datasize, data);
} else {
/* unknown meta format */
meta = NULL;
}
if (!meta) {
PyErr_SetString(PyExc_OSError, "cannot load metafile");
return NULL;
}
/* step 2: create bitmap */
core.bcSize = sizeof(core);
core.bcWidth = width;
core.bcHeight = height;
core.bcPlanes = 1;
core.bcBitCount = 24;
dc = CreateCompatibleDC(NULL);
bitmap = CreateDIBSection(dc, (BITMAPINFO *)&core, DIB_RGB_COLORS, &ptr, NULL, 0);
if (!bitmap) {
PyErr_SetString(PyExc_OSError, "cannot create bitmap");
goto error;
}
if (!SelectObject(dc, bitmap)) {
PyErr_SetString(PyExc_OSError, "cannot select bitmap");
goto error;
}
/* step 3: render metafile into bitmap */
rect.left = rect.top = 0;
rect.right = width;
rect.bottom = height;
/* FIXME: make background transparent? configurable? */
FillRect(dc, &rect, GetStockObject(WHITE_BRUSH));
EnumEnhMetaFile(dc, meta, enhMetaFileProc, NULL, &rect);
/* step 4: extract bits from bitmap */
GdiFlush();
buffer = PyBytes_FromStringAndSize(ptr, height * ((width * 3 + 3) & -4));
error:
DeleteEnhMetaFile(meta);
if (bitmap) {
DeleteObject(bitmap);
}
DeleteDC(dc);
return buffer;
}
#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)) {
PyErr_Format(
PyExc_OSError,
"X connection failed: error %i",
xcb_connection_has_error(connection)
);
xcb_disconnect(connection);
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 || screen->root == 0) {
// this case is usually caught with "X connection failed: error 6" above
xcb_disconnect(connection);
PyErr_SetString(PyExc_OSError, "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) {
PyErr_Format(
PyExc_OSError,
"X get_image failed: error %i (%i, %i, %i)",
error->error_code,
error->major_code,
error->minor_code,
error->resource_id
);
free(error);
xcb_disconnect(connection);
return NULL;
}
/* store data in Python buffer */
if (reply->depth == 24) {
buffer = PyBytes_FromStringAndSize(
(char *)xcb_get_image_data(reply), xcb_get_image_data_length(reply)
);
} else {
PyErr_Format(PyExc_OSError, "unsupported bit depth: %i", reply->depth);
}
free(reply);
xcb_disconnect(connection);
if (!buffer) {
return NULL;
}
return Py_BuildValue("(ii)N", width, height, buffer);
}
#endif /* HAVE_XCB */