You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ueberzug/ueberzug/X/Xshm.c

297 lines
9.7 KiB
C

#include "python.h"
#include <stdbool.h>
#include <sys/shm.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/XShm.h>
#include "math.h"
#include "util.h"
#include "display.h"
#define INVALID_SHM_ID -1
#define INVALID_SHM_ADDRESS (char*)-1
#define BYTES_PER_PIXEL 4
typedef struct {
PyObject_HEAD
int width;
int height;
int buffer_size;
DisplayObject *display_pyobject;
XShmSegmentInfo segmentInfo;
XImage *image;
} ImageObject;
static inline Display *
get_display(ImageObject *self) {
return self->display_pyobject->event_display;
}
static bool
Image_init_shared_memory(ImageObject *self) {
self->segmentInfo.shmid = shmget(
IPC_PRIVATE,
self->buffer_size,
IPC_CREAT | 0600);
return self->segmentInfo.shmid != INVALID_SHM_ID;
}
static bool
Image_map_shared_memory(ImageObject *self) {
// Map the shared memory segment into the address space of this process
self->segmentInfo.shmaddr = (char*)shmat(self->segmentInfo.shmid, 0, 0);
if (self->segmentInfo.shmaddr != INVALID_SHM_ADDRESS) {
self->segmentInfo.readOnly = true;
// Mark the shared memory segment for removal
// It will be removed even if this program crashes
shmctl(self->segmentInfo.shmid, IPC_RMID, 0);
return true;
}
return false;
}
static bool
Image_create_shared_image(ImageObject *self) {
Display *display = get_display(self);
int screen = XDefaultScreen(display);
// Allocate the memory needed for the XImage structure
self->image = XShmCreateImage(
display, XDefaultVisual(display, screen),
DefaultDepth(display, screen), ZPixmap, 0,
&self->segmentInfo, 0, 0);
if (self->image) {
self->image->data = (char*)self->segmentInfo.shmaddr;
self->image->width = self->width;
self->image->height = self->height;
// Ask the X server to attach the shared memory segment and sync
XShmAttach(display, &self->segmentInfo);
XFlush(display);
return true;
}
return false;
}
static void
Image_destroy_shared_image(ImageObject *self) {
if (self->image) {
XShmDetach(get_display(self), &self->segmentInfo);
XDestroyImage(self->image);
self->image = NULL;
}
}
static void
Image_free_shared_memory(ImageObject *self) {
if(self->segmentInfo.shmaddr != INVALID_SHM_ADDRESS) {
shmdt(self->segmentInfo.shmaddr);
self->segmentInfo.shmaddr = INVALID_SHM_ADDRESS;
}
}
static void
Image_finalise(ImageObject *self) {
Image_destroy_shared_image(self);
Image_free_shared_memory(self);
Py_CLEAR(self->display_pyobject);
}
static int
Image_init(ImageObject *self, PyObject *args, PyObject *kwds) {
static char *kwlist[] = {"display", "width", "height", NULL};
PyObject *display_pyobject;
if (!PyArg_ParseTupleAndKeywords(
args, kwds, "O!ii", kwlist,
&DisplayType, &display_pyobject,
&self->width, &self->height)) {
Py_INIT_RETURN_ERROR;
}
if (self->display_pyobject) {
Image_finalise(self);
}
Py_INCREF(display_pyobject);
self->display_pyobject = (DisplayObject*)display_pyobject;
self->buffer_size = self->width * self->height * BYTES_PER_PIXEL;
if (!Image_init_shared_memory(self)) {
raiseInit(OSError, "could not init shared memory");
}
if (!Image_map_shared_memory(self)) {
raiseInit(OSError, "could not map shared memory");
}
if (!Image_create_shared_image(self)) {
Image_free_shared_memory(self);
raiseInit(OSError, "could not allocate the XImage structure");
}
Py_INIT_RETURN_SUCCESS;
}
static void
Image_dealloc(ImageObject *self) {
Image_finalise(self);
Py_TYPE(self)->tp_free((PyObject*)self);
}
static PyObject *
Image_copy_to(ImageObject *self, PyObject *args, PyObject *kwds) {
// draws the image on the surface at x, y
static char *kwlist[] = {"drawable", "x", "y", "width", "height", NULL};
Drawable surface;
GC gc;
int x, y;
unsigned int width, height;
Display *display = get_display(self);
if (!PyArg_ParseTupleAndKeywords(
args, kwds, "kiiII", kwlist,
&surface, &x, &y, &width, &height)) {
Py_RETURN_ERROR;
}
gc = XCreateGC(display, surface, 0, NULL);
XShmPutImage(display, surface, gc,
self->image, 0, 0,
x, y, width, height, false);
XFreeGC(display, gc);
Py_RETURN_NONE;
}
static PyObject *
Image_draw(ImageObject *self, PyObject *args, PyObject *kwds) {
// puts the pixels on the image at x, y
static char *kwlist[] = {"x", "y", "width", "height", "pixels", NULL};
int offset_x, offset_y;
int width, height;
int pixels_per_row;
int source_pixels_per_row;
int destination_pixels_per_row;
int destination_offset_x_bytes;
char *pixels;
Py_ssize_t pixels_size;
if (!PyArg_ParseTupleAndKeywords(
args, kwds, "iiiis#", kwlist,
&offset_x, &offset_y, &width, &height,
&pixels, &pixels_size)) {
Py_RETURN_ERROR;
}
Py_BEGIN_ALLOW_THREADS
destination_offset_x_bytes = max(0, offset_x) * BYTES_PER_PIXEL;
source_pixels_per_row = width * BYTES_PER_PIXEL;
destination_pixels_per_row = self->width * BYTES_PER_PIXEL;
pixels_per_row = min(width + min(offset_x, 0), self->width - max(offset_x, 0)) * BYTES_PER_PIXEL;
if (offset_x + width > 0 && offset_x < self->width) {
// < 0 -> start y = 0, min(surface.height, height - abs(offset))
// > 0 -> start y = offset, height = min(surface.height, height + offset)
for (int y = max(0, offset_y); y < min(self->height, height + offset_y); y++) {
// < 0 -> first row = abs(offset) => n row = y + abs(offset)
// > 0 -> first row = - offset => n row = y - offset
// => n row = y - offset
int pixels_y = y - offset_y;
void *destination =
self->image->data + y * destination_pixels_per_row
+ destination_offset_x_bytes;
void *source = pixels + pixels_y * source_pixels_per_row;
if (! ((uintptr_t)self->image->data <= (uintptr_t)destination)) {
raise(AssertionError,
"The destination start address calculation went wrong.\n"
"It points to an address which is before the start address of the buffer.\n"
"%p not smaller than %p",
self->image->data, destination);
}
if (! ((uintptr_t)destination + pixels_per_row
<= (uintptr_t)self->image->data + self->buffer_size)) {
raise(AssertionError,
"The destination end address calculation went wrong.\n"
"It points to an address which is after the end address of the buffer.\n"
"%p not smaller than %p",
destination + pixels_per_row,
self->image->data + self->buffer_size);
}
if (! ((uintptr_t)pixels <= (uintptr_t)source)) {
raise(AssertionError,
"The source start address calculation went wrong.\n"
"It points to an address which is before the start address of the buffer.\n"
"%p not smaller than %p",
pixels, source);
}
if (! ((uintptr_t)source + pixels_per_row
<= (uintptr_t)pixels + pixels_size)) {
raise(AssertionError,
"The source end address calculation went wrong.\n"
"It points to an address which is after the end address of the buffer."
"%p not smaller than %p",
source + pixels_per_row,
pixels + pixels_size);
}
memcpy(destination, source, pixels_per_row);
}
}
Py_END_ALLOW_THREADS
Py_RETURN_NONE;
}
static PyMethodDef Image_methods[] = {
{"copy_to", (PyCFunction)Image_copy_to,
METH_VARARGS | METH_KEYWORDS,
"Draws the image on the surface at the passed coordinate.\n"
"\n"
"Args:\n"
" drawable (int): the surface to draw on\n"
" x (int): the x position where this image should be placed\n"
" y (int): the y position where this image should be placed\n"
" width (int): the width of the area\n"
" which should be copied to the drawable\n"
" height (int): the height of the area\n"
" which should be copied to the drawable"},
{"draw", (PyCFunction)Image_draw,
METH_VARARGS | METH_KEYWORDS,
"Places the pixels on the image at the passed coordinate.\n"
"\n"
"Args:\n"
" x (int): the x position where the pixels should be placed\n"
" y (int): the y position where the pixels should be placed\n"
" width (int): amount of pixels per row in the passed data\n"
" height (int): amount of pixels per column in the passed data\n"
" pixels (bytes): the pixels to place on the image"},
{NULL} /* Sentinel */
};
PyTypeObject ImageType = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "ueberzug.X.Image",
.tp_doc =
"An shared memory X11 Image\n"
"\n"
"Args:\n"
" display (ueberzug.X.Display): the X11 display\n"
" width (int): the width of this image\n"
" height (int): the height of this image",
.tp_basicsize = sizeof(ImageObject),
.tp_itemsize = 0,
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
.tp_new = PyType_GenericNew,
.tp_init = (initproc)Image_init,
.tp_dealloc = (destructor) Image_dealloc,
.tp_methods = Image_methods,
};