mirror of https://github.com/seebye/ueberzug
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.
297 lines
9.7 KiB
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,
|
|
};
|