drop python-xlib

Python-xlib misses some essential features like shm support.
This is why the Xshm module was written,
but this leads to redundant dependencies.
So this commit removes python-xlib as dependency.

During the translation of the code the determination of the pid of a
window was also improved.
The XRes extension is now used to determine the pid of a window.
pull/146/head
seebye 3 years ago
parent 08fb245467
commit 11aad51903

@ -1 +1,3 @@
include ueberzug/lib/lib.sh
include ueberzug/X/*.h
include ueberzug/X/*.c

@ -35,6 +35,7 @@ Libraries used in the c extension:
- python
- X11
- Xext
- XRes
There are also other direct dependencies,
but they will be installed by pip.

@ -1,6 +1,7 @@
#!/usr/bin/env python3
import distutils.core
import setuptools
import glob
import ueberzug
# To use a consistent encoding
@ -34,7 +35,10 @@ setuptools.setup(
},
ext_modules=[
distutils.core.Extension(
"Xshm", ["Xshm/Xshm.c"], libraries=["X11", "Xext"]),
"ueberzug.X",
glob.glob("ueberzug/X/*.c"),
libraries=["X11", "Xext", "XRes"],
include_dirs=["ueberzug/X"]),
],
# Versions should comply with PEP 440:
@ -96,8 +100,7 @@ setuptools.setup(
#
# For an analysis of "install_requires" vs pip's requirements files see:
# https://packaging.python.org/en/latest/requirements.html
install_requires=['python-xlib', 'pillow', 'docopt',
'attrs>=18.2.0'], # Optional
install_requires=['pillow', 'docopt', 'attrs>=18.2.0'], # Optional
python_requires='>=3.6',
# List additional URLs that are relevant to your project as a dict.

@ -0,0 +1,36 @@
#include "python.h"
#include "display.h"
#include "window.h"
#include "Xshm.h"
static PyModuleDef module = {
PyModuleDef_HEAD_INIT,
.m_name = "ueberzug.X",
.m_doc = "Modul which implements the interaction with the Xshm extension.",
.m_size = -1,
};
PyMODINIT_FUNC
PyInit_X(void) {
PyObject *module_instance;
if (PyType_Ready(&DisplayType) < 0 ||
PyType_Ready(&WindowType) < 0 ||
PyType_Ready(&ImageType) < 0) {
return NULL;
}
module_instance = PyModule_Create(&module);
if (module_instance == NULL) {
return NULL;
}
Py_INCREF(&DisplayType);
Py_INCREF(&WindowType);
Py_INCREF(&ImageType);
PyModule_AddObject(module_instance, "Display", (PyObject*)&DisplayType);
PyModule_AddObject(module_instance, "OverlayWindow", (PyObject*)&WindowType);
PyModule_AddObject(module_instance, "Image", (PyObject*)&ImageType);
return module_instance;
}

@ -0,0 +1,7 @@
#ifndef __X_H__
#define __X_H__
#include "python.h"
PyModuleDef module;
#endif

@ -1,58 +1,38 @@
#define PY_SSIZE_T_CLEAN // Make "s#" use Py_ssize_t rather than int.
#include <Python.h>
#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
#define min(a,b) (((a) < (b)) ? (a) : (b))
#define max(a,b) (((a) > (b)) ? (a) : (b))
#define Py_INIT_ERROR -1
#define Py_INIT_SUCCESS 0
#define __raise(return_value, Exception, message...) { \
char errorMessage[500]; \
snprintf(errorMessage, 500, message); \
PyErr_SetString( \
PyExc_##Exception, \
errorMessage); \
return return_value; \
}
#define raise(Exception, message...) __raise(NULL, Exception, message)
#define raiseInit(Exception, message...) __raise(Py_INIT_ERROR, Exception, message)
static Display* display = NULL;
typedef struct {
PyObject_HEAD
int width;
int height;
int buffer_size;
DisplayObject *display_pyobject;
XShmSegmentInfo segmentInfo;
XImage *image;
} Image;
} ImageObject;
static bool
init_display() {
if (display == NULL) {
display = XOpenDisplay(NULL);
if (display == NULL) {
return false;
}
}
return true;
static inline Display *
get_display(ImageObject *self) {
return self->display_pyobject->event_display;
}
static bool
Image_init_shared_memory(Image *self) {
Image_init_shared_memory(ImageObject *self) {
self->segmentInfo.shmid = shmget(
IPC_PRIVATE,
self->buffer_size,
@ -61,7 +41,7 @@ Image_init_shared_memory(Image *self) {
}
static bool
Image_map_shared_memory(Image *self) {
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);
@ -77,7 +57,8 @@ Image_map_shared_memory(Image *self) {
}
static bool
Image_create_shared_image(Image *self) {
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(
@ -92,44 +73,56 @@ Image_create_shared_image(Image *self) {
// Ask the X server to attach the shared memory segment and sync
XShmAttach(display, &self->segmentInfo);
XSync(display, false);
XFlush(display);
return true;
}
return false;
}
static void
Image_destroy_shared_image(Image *self) {
Image_destroy_shared_image(ImageObject *self) {
if (self->image) {
XShmDetach(display, &self->segmentInfo);
XShmDetach(get_display(self), &self->segmentInfo);
XDestroyImage(self->image);
self->image = NULL;
}
}
static void
Image_free_shared_memory(Image *self) {
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(Image *self, PyObject *args, PyObject *kwds) {
static char *kwlist[] = {"width", "height", NULL};
Image_init(ImageObject *self, PyObject *args, PyObject *kwds) {
static char *kwlist[] = {"display", "width", "height", NULL};
PyObject *display_pyobject;
if (!PyArg_ParseTupleAndKeywords(
args, kwds, "ii", kwlist,
args, kwds, "O!ii", kwlist,
&DisplayType, &display_pyobject,
&self->width, &self->height)) {
return Py_INIT_ERROR;
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 (!init_display()) {
raiseInit(OSError, "could not open a connection to the X server");
}
if (!Image_init_shared_memory(self)) {
raiseInit(OSError, "could not init shared memory");
}
@ -143,43 +136,42 @@ Image_init(Image *self, PyObject *args, PyObject *kwds) {
raiseInit(OSError, "could not allocate the XImage structure");
}
return Py_INIT_SUCCESS;
Py_INIT_RETURN_SUCCESS;
}
static void
Image_dealloc(Image *self) {
Image_destroy_shared_image(self);
Image_free_shared_memory(self);
Image_dealloc(ImageObject *self) {
Image_finalise(self);
Py_TYPE(self)->tp_free((PyObject*)self);
}
static PyObject *
Image_copyTo(Image *self, PyObject *args, PyObject *kwds) {
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;
int width, height;
unsigned int width, height;
Display *display = get_display(self);
if (!PyArg_ParseTupleAndKeywords(
args, kwds, "liiii", kwlist,
args, kwds, "kiiII", kwlist,
&surface, &x, &y, &width, &height)) {
return NULL;
Py_RETURN_ERROR;
}
gc = XCreateGC(display, surface, 0, NULL);
XShmPutImage(display, surface, gc,
self->image, 0, 0,
x, y, width, height, False);
x, y, width, height, false);
XFreeGC(display, gc);
XSync(display, false);
Py_RETURN_NONE;
}
static PyObject *
Image_draw(Image *self, PyObject *args, PyObject *kwds) {
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;
@ -195,9 +187,10 @@ Image_draw(Image *self, PyObject *args, PyObject *kwds) {
args, kwds, "iiiis#", kwlist,
&offset_x, &offset_y, &width, &height,
&pixels, &pixels_size)) {
return NULL;
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;
@ -252,12 +245,13 @@ Image_draw(Image *self, PyObject *args, PyObject *kwds) {
memcpy(destination, source, pixels_per_row);
}
}
Py_END_ALLOW_THREADS
Py_RETURN_NONE;
}
static PyMethodDef Image_methods[] = {
{"copy_to", (PyCFunction)Image_copyTo,
{"copy_to", (PyCFunction)Image_copy_to,
METH_VARARGS | METH_KEYWORDS,
"Draws the image on the surface at the passed coordinate.\n"
"\n"
@ -282,16 +276,17 @@ static PyMethodDef Image_methods[] = {
{NULL} /* Sentinel */
};
static PyTypeObject ImageType = {
PyTypeObject ImageType = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "Xshm.Image",
.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(Image),
.tp_basicsize = sizeof(ImageObject),
.tp_itemsize = 0,
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
.tp_new = PyType_GenericNew,
@ -299,25 +294,3 @@ static PyTypeObject ImageType = {
.tp_dealloc = (destructor) Image_dealloc,
.tp_methods = Image_methods,
};
static PyModuleDef module = {
PyModuleDef_HEAD_INIT,
.m_name = "Xshm",
.m_doc = "Modul which implements the interaction with the Xshm extension.",
.m_size = -1,
};
PyMODINIT_FUNC
PyInit_Xshm(void) {
PyObject *module_instance;
if (PyType_Ready(&ImageType) < 0)
return NULL;
module_instance = PyModule_Create(&module);
if (module_instance == NULL)
return NULL;
Py_INCREF(&ImageType);
PyModule_AddObject(module_instance, "Image", (PyObject*)&ImageType);
return module_instance;
}

@ -0,0 +1,7 @@
#ifndef __XSHM_H__
#define __XSHM_H__
#include "python.h"
PyTypeObject ImageType;
#endif

@ -0,0 +1,276 @@
#include "python.h"
#include <X11/Xlib.h>
#include <X11/extensions/XRes.h>
#include <X11/extensions/XShm.h>
#include "util.h"
#include "display.h"
#define INVALID_PID (pid_t)-1
#define REOPEN_DISPLAY(display) \
if (display != NULL) { \
XCloseDisplay(display); \
} \
display = XOpenDisplay(NULL);
#define CLOSE_DISPLAY(display) \
if (display != NULL) { \
XCloseDisplay(display); \
display = NULL; \
}
static int
Display_init(DisplayObject *self, PyObject *args, PyObject *kwds) {
// Two connections are opened as
// a death lock can occur
// if you listen for events
// (this will happen in parallel in asyncio worker threads)
// and request information (e.g. XGetGeometry)
// simultaneously.
REOPEN_DISPLAY(self->event_display);
REOPEN_DISPLAY(self->info_display);
if (self->event_display == NULL ||
self->info_display == NULL) {
raiseInit(OSError, "could not open a connection to the X server");
}
int _;
if (!XResQueryExtension(self->info_display, &_, &_)) {
raiseInit(OSError, "the extension XRes is required");
}
if (!XShmQueryExtension(self->event_display)) {
raiseInit(OSError, "the extension Xext is required");
}
int screen = XDefaultScreen(self->info_display);
self->screen_width = XDisplayWidth(self->info_display, screen);
self->screen_height = XDisplayHeight(self->info_display, screen);
self->bitmap_pad = XBitmapPad(self->info_display);
self->bitmap_unit = XBitmapUnit(self->info_display);
self->wm_class = XInternAtom(self->info_display, "WM_CLASS", False);
self->wm_name = XInternAtom(self->info_display, "WM_NAME", False);
self->wm_locale_name = XInternAtom(self->info_display, "WM_LOCALE_NAME", False);
self->wm_normal_hints = XInternAtom(self->info_display, "WM_NORMAL_HINTS", False);
Py_INIT_RETURN_SUCCESS;
}
static void
Display_dealloc(DisplayObject *self) {
CLOSE_DISPLAY(self->event_display);
CLOSE_DISPLAY(self->info_display);
Py_TYPE(self)->tp_free((PyObject*)self);
}
static int
has_property(DisplayObject *self, Window window, Atom property) {
Atom actual_type_return;
int actual_format_return;
unsigned long bytes_after_return;
unsigned char* prop_to_return = NULL;
unsigned long nitems_return;
int status = XGetWindowProperty(
self->info_display, window, property, 0,
0L, False,
AnyPropertyType,
&actual_type_return,
&actual_format_return,
&nitems_return, &bytes_after_return, &prop_to_return);
if (status == Success && prop_to_return) {
XFree(prop_to_return);
}
return status == Success && !(actual_type_return == None && actual_format_return == 0);
}
static PyObject *
Display_get_child_window_ids(DisplayObject *self, PyObject *args, PyObject *kwds) {
static char *kwlist[] = {"parent_id", NULL};
Window parent = XDefaultRootWindow(self->info_display);
Window _, *children;
unsigned int children_count;
if (!PyArg_ParseTupleAndKeywords(
args, kwds, "|k", kwlist,
&parent)) {
Py_RETURN_ERROR;
}
if (!XQueryTree(
self->info_display, parent,
&_, &_, &children, &children_count)) {
raise(OSError, "failed to query child windows of %lu", parent);
}
PyObject *child_ids = PyList_New(0);
if (children) {
for (unsigned int i = 0; i < children_count; i++) {
// assume that windows without essential properties
// like the window title aren't shown to the user
int is_helper_window = (
!has_property(self, children[i], self->wm_class) &&
!has_property(self, children[i], self->wm_name) &&
!has_property(self, children[i], self->wm_locale_name) &&
!has_property(self, children[i], self->wm_normal_hints));
if (is_helper_window) {
continue;
}
PyObject *py_window_id = Py_BuildValue("k", children[i]);
PyList_Append(child_ids, py_window_id);
Py_XDECREF(py_window_id);
}
XFree(children);
}
return child_ids;
}
static PyObject *
Display_get_window_pid(DisplayObject *self, PyObject *args, PyObject *kwds) {
static char *kwlist[] = {"window_id", NULL};
Window window;
long num_ids;
int num_specs = 1;
XResClientIdValue *client_ids;
XResClientIdSpec client_specs[1];
pid_t window_creator_pid = INVALID_PID;
if (!PyArg_ParseTupleAndKeywords(
args, kwds, "k", kwlist,
&window)) {
Py_RETURN_ERROR;
}
client_specs[0].client = window;
client_specs[0].mask = XRES_CLIENT_ID_PID_MASK;
if (Success != XResQueryClientIds(
self->info_display, num_specs, client_specs,
&num_ids, &client_ids)) {
Py_RETURN_NONE;
}
for(int i = 0; i < num_ids; i++) {
XResClientIdValue *value = client_ids + i;
XResClientIdType type = XResGetClientIdType(value);
if (type == XRES_CLIENT_ID_PID) {
window_creator_pid = XResGetClientPid(value);
}
}
XFree(client_ids);
if (window_creator_pid != INVALID_PID) {
return Py_BuildValue("i", window_creator_pid);
}
Py_RETURN_NONE;
}
static PyObject *
Display_wait_for_event(DisplayObject *self) {
Py_BEGIN_ALLOW_THREADS
XEvent event;
XPeekEvent(self->event_display, &event);
Py_END_ALLOW_THREADS
Py_RETURN_NONE;
}
static PyObject *
Display_discard_event(DisplayObject *self) {
Py_BEGIN_ALLOW_THREADS
XEvent event;
XNextEvent(self->event_display, &event);
Py_END_ALLOW_THREADS
Py_RETURN_NONE;
}
static PyObject *
Display_get_bitmap_format_scanline_pad(DisplayObject *self, void *closure) {
return Py_BuildValue("i", self->bitmap_pad);
}
static PyObject *
Display_get_bitmap_format_scanline_unit(DisplayObject *self, void *closure) {
return Py_BuildValue("i", self->bitmap_unit);
}
static PyObject *
Display_get_screen_width(DisplayObject *self, void *closure) {
return Py_BuildValue("i", self->screen_width);
}
static PyObject *
Display_get_screen_height(DisplayObject *self, void *closure) {
return Py_BuildValue("i", self->screen_height);
}
static PyGetSetDef Display_properties[] = {
{"bitmap_format_scanline_pad", (getter)Display_get_bitmap_format_scanline_pad,
.doc = "int: Each scanline must be padded to a multiple of bits of this value."},
{"bitmap_format_scanline_unit", (getter)Display_get_bitmap_format_scanline_unit,
.doc = "int:\n"
" The size of a bitmap's scanline unit in bits.\n"
" The scanline is calculated in multiples of this value."},
{"screen_width", (getter)Display_get_screen_width,
.doc = "int: The width of the default screen at the time the connection to X11 was opened."},
{"screen_height", (getter)Display_get_screen_width,
.doc = "int: The height of the default screen at the time the connection to X11 was opened."},
{NULL} /* Sentinel */
};
static PyMethodDef Display_methods[] = {
{"wait_for_event", (PyCFunction)Display_wait_for_event,
METH_NOARGS,
"Waits for an event to occur. till an event occur."},
{"discard_event", (PyCFunction)Display_discard_event,
METH_NOARGS,
"Discards the first event from the event queue."},
{"get_child_window_ids", (PyCFunction)Display_get_child_window_ids,
METH_VARARGS | METH_KEYWORDS,
"Queries for the ids of the children of the window with the passed identifier.\n"
"\n"
"Args:\n"
" parent_id (int): optional\n"
" the id of the window for which to query for the ids of its children\n"
" if it's not specified the id of the default root window will be used\n"
"\n"
"Returns:\n"
" list of ints: the ids of the child windows"},
{"get_window_pid", (PyCFunction)Display_get_window_pid,
METH_VARARGS | METH_KEYWORDS,
"Tries to figure out the pid of the process which created the window with the passed id.\n"
"\n"
"Args:\n"
" window_id (int): the window id for which to retrieve information\n"
"\n"
"Returns:\n"
" int or None: the pid of the creator of the window"},
{NULL} /* Sentinel */
};
PyTypeObject DisplayType = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "ueberzug.X.Display",
.tp_doc = "X11 display\n",
.tp_basicsize = sizeof(DisplayObject),
.tp_itemsize = 0,
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
.tp_new = PyType_GenericNew,
.tp_init = (initproc)Display_init,
.tp_dealloc = (destructor)Display_dealloc,
.tp_getset = Display_properties,
.tp_methods = Display_methods,
};

@ -0,0 +1,29 @@
#ifndef __DISPLAY_H__
#define __DISPLAY_H__
#include "python.h"
#include <X11/Xlib.h>
typedef struct {
PyObject_HEAD
// Always use the event_display
// except for functions which return information
// e.g. XGetGeometry.
Display *event_display;
Display *info_display;
int bitmap_pad;
int bitmap_unit;
int screen;
int screen_width;
int screen_height;
Atom wm_class;
Atom wm_name;
Atom wm_locale_name;
Atom wm_normal_hints;
} DisplayObject;
PyTypeObject DisplayType;
#endif

@ -0,0 +1,7 @@
#ifndef __MATH_H__
#define __MATH_H__
#define min(a,b) (((a) < (b)) ? (a) : (b))
#define max(a,b) (((a) > (b)) ? (a) : (b))
#endif

@ -0,0 +1,11 @@
#ifndef __PYTHON_H__
#define __PYTHON_H__
#ifndef __linux__
#error OS unsupported
#endif
#define PY_SSIZE_T_CLEAN // Make "s#" use Py_ssize_t rather than int.
#include <Python.h>
#endif

@ -0,0 +1,28 @@
#ifndef __UTIL_H__
#define __UTIL_H__
#define Py_INIT_ERROR -1
#define Py_INIT_SUCCESS 0
#define Py_ERROR NULL
#define Py_RETURN_ERROR return Py_ERROR
#define Py_INIT_RETURN_ERROR return Py_INIT_ERROR
#define Py_INIT_RETURN_SUCCESS return Py_INIT_SUCCESS
#define __raise(return_value, Exception, message...) { \
char errorMessage[500]; \
snprintf(errorMessage, 500, message); \
PyErr_SetString( \
PyExc_##Exception, \
errorMessage); \
return return_value; \
}
#define raise(Exception, message...) __raise(Py_ERROR, Exception, message)
#define raiseInit(Exception, message...) __raise(Py_INIT_ERROR, Exception, message)
#define ARRAY_LENGTH(stack_array) \
(sizeof stack_array \
? sizeof stack_array / sizeof *stack_array \
: 0)
#endif

@ -0,0 +1,316 @@
#include "python.h"
#include <stdbool.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/shape.h>
#include "util.h"
#include "display.h"
typedef struct {
PyObject_HEAD
DisplayObject *display_pyobject;
Window parent;
Window window;
unsigned int width;
unsigned int height;
} WindowObject;
static inline Display *
get_event_display(WindowObject *self) {
return self->display_pyobject->event_display;
}
static inline Display *
get_info_display(WindowObject *self) {
return self->display_pyobject->info_display;
}
static void
Window_create(WindowObject *self) {
Window _0; int _1; unsigned int _2;
XGetGeometry(
get_info_display(self),
self->parent,
&_0, &_1, &_1,
&self->width, &self->height,
&_2, &_2);
Display *display = get_event_display(self);
int screen = XDefaultScreen(display);
Visual *visual = XDefaultVisual(display, screen);
unsigned long attributes_mask =
CWEventMask | CWBackPixel | CWColormap | CWBorderPixel;
XSetWindowAttributes attributes;
attributes.event_mask = ExposureMask;
attributes.colormap = XCreateColormap(
display, XDefaultRootWindow(display),
visual, AllocNone);
attributes.background_pixel = 0;
attributes.border_pixel = 0;
self->window = XCreateWindow(
display, self->parent,
0, 0, self->width, self->height, 0,
DefaultDepth(display, screen),
InputOutput, visual,
attributes_mask, &attributes);
}
static void
set_subscribed_events(Display *display, Window window, long event_mask) {
XSetWindowAttributes attributes;
attributes.event_mask = event_mask;
XChangeWindowAttributes(
display, window,
CWEventMask , &attributes);
}
static void
Window_finalise(WindowObject *self) {
if (self->window) {
Display *display = get_event_display(self);
set_subscribed_events(
display, self->parent, NoEventMask);
XDestroyWindow(display, self->window);
XFlush(display);
}
Py_CLEAR(self->display_pyobject);
self->window = 0;
}
static inline void
set_xshape_mask(Display *display, Window window, int kind, XRectangle area[], int area_length) {
XShapeCombineRectangles(
display, window,
kind,
0, 0, area, area_length,
ShapeSet, 0);
}
static inline void
set_input_mask(Display *display, Window window, XRectangle area[], int area_length) {
set_xshape_mask(
display, window, ShapeInput, area, area_length);
}
static inline void
set_visibility_mask(Display *display, Window window, XRectangle area[], int area_length) {
set_xshape_mask(
display, window, ShapeBounding, area, area_length);
}
static int
Window_init(WindowObject *self, PyObject *args, PyObject *kwds) {
static XRectangle empty_area[0] = {};
static char *kwlist[] = {"display", "parent", NULL};
PyObject *display_pyobject;
Window parent;
if (!PyArg_ParseTupleAndKeywords(
args, kwds, "O!k", kwlist,
&DisplayType, &display_pyobject, &parent)) {
Py_INIT_RETURN_ERROR;
}
if (self->display_pyobject) {
Window_finalise(self);
}
Py_INCREF(display_pyobject);
self->display_pyobject = (DisplayObject*)display_pyobject;
Display *display = get_event_display(self);
self->parent = parent;
Window_create(self);
set_subscribed_events(
display, self->parent, StructureNotifyMask);
set_input_mask(
display, self->window,
empty_area, ARRAY_LENGTH(empty_area));
set_visibility_mask(
display, self->window,
empty_area, ARRAY_LENGTH(empty_area));
XMapWindow(display, self->window);
Py_INIT_RETURN_SUCCESS;
}
static void
Window_dealloc(WindowObject *self) {
Window_finalise(self);
Py_TYPE(self)->tp_free((PyObject*)self);
}
static PyObject *
Window_set_visibility_mask(WindowObject *self, PyObject *args, PyObject *kwds) {
static char *kwlist[] = {"area", NULL};
PyObject *py_area;
Py_ssize_t area_length;
if (!PyArg_ParseTupleAndKeywords(
args, kwds, "O!", kwlist,
&PyList_Type, &py_area)) {
Py_RETURN_ERROR;
}
area_length = PyList_Size(py_area);
XRectangle area[area_length];
for (Py_ssize_t i = 0; i < area_length; i++) {
short x, y;
unsigned short width, height;
PyObject *py_rectangle = PyList_GetItem(py_area, i);
if (!PyObject_TypeCheck(py_rectangle, &PyTuple_Type)) {
raise(ValueError, "Expected a list of a tuple of ints!");
}
if (!PyArg_ParseTuple(
py_rectangle, "hhHH",
&x, &y, &width, &height)) {
raise(ValueError,
"Expected a rectangle to be a "
"tuple of (x: int, y: int, width: int, height: int)!");
}
area[i].x = x;
area[i].y = y;
area[i].width = width;
area[i].height = height;
}
set_visibility_mask(
get_event_display(self),
self->window,
area, area_length);
Py_RETURN_NONE;
}
static PyObject *
Window_draw(WindowObject *self) {
XFlush(get_event_display(self));
Py_RETURN_NONE;
}
static PyObject *
Window_get_id(WindowObject *self, void *closure) {
return Py_BuildValue("k", self->window);
}
static PyObject *
Window_get_parent_id(WindowObject *self, void *closure) {
return Py_BuildValue("k", self->parent);
}
static PyObject *
Window_get_width(WindowObject *self, void *closure) {
return Py_BuildValue("I", self->width);
}
static PyObject *
Window_get_height(WindowObject *self, void *closure) {
return Py_BuildValue("I", self->height);
}
static PyObject *
Window_process_event(WindowObject *self) {
XEvent event;
XAnyEvent *metadata = &event.xany;
Display *display = get_event_display(self);
if (!XPending(display)) {
Py_RETURN_FALSE;
}
XPeekEvent(display, &event);
if (! (event.type == Expose && metadata->window == self->window) &&
! (event.type == ConfigureNotify && metadata->window == self->parent)) {
Py_RETURN_FALSE;
}
XNextEvent(display, &event);
switch (event.type) {
case Expose:
if(event.xexpose.count == 0) {
Py_XDECREF(PyObject_CallMethod(
(PyObject*)self, "draw", NULL));
}
break;
case ConfigureNotify: {
unsigned int delta_width =
((unsigned int)event.xconfigure.width) - self->width;
unsigned int delta_height =
((unsigned int)event.xconfigure.height) - self->height;
if (delta_width != 0 || delta_height != 0) {
self->width = (unsigned int)event.xconfigure.width;
self->height = (unsigned int)event.xconfigure.height;
XResizeWindow(display, self->window, self->width, self->height);
}
if (delta_width > 0 || delta_height > 0) {
Py_XDECREF(PyObject_CallMethod(
(PyObject*)self, "draw", NULL));
}
else {
XFlush(display);
}
}
break;
}
Py_RETURN_TRUE;
}
static PyGetSetDef Window_properties[] = {
{"id", (getter)Window_get_id, .doc = "int: the X11 id of this window."},
{"parent_id", (getter)Window_get_parent_id, .doc = "int: the X11 id of the parent window."},
{"width", (getter)Window_get_width, .doc = "int: the width of this window."},
{"height", (getter)Window_get_height, .doc = "int: the height of this window."},
{NULL} /* Sentinel */
};
static PyMethodDef Window_methods[] = {
{"draw", (PyCFunction)Window_draw,
METH_NOARGS,
"Redraws the window."},
{"set_visibility_mask", (PyCFunction)Window_set_visibility_mask,
METH_VARARGS | METH_KEYWORDS,
"Specifies the part of the window which should be visible.\n"
"\n"
"Args:\n"
" area (tuple of (tuple of (x: int, y: int, width: int, height: int))):\n"
" the visible area specified by rectangles"},
{"process_event", (PyCFunction)Window_process_event,
METH_NOARGS,
"Processes the next X11 event if it targets this window.\n"
"\n"
"Returns:\n"
" bool: True if an event was processed"},
{NULL} /* Sentinel */
};
PyTypeObject WindowType = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "ueberzug.X.OverlayWindow",
.tp_doc =
"Basic implementation of an overlay window\n"
"\n"
"Args:\n"
" display (ueberzug.X.Display): the X11 display\n"
" parent (int): the parent window of this window",
.tp_basicsize = sizeof(WindowObject),
.tp_itemsize = 0,
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
.tp_new = PyType_GenericNew,
.tp_init = (initproc)Window_init,
.tp_dealloc = (destructor)Window_dealloc,
.tp_getset = Window_properties,
.tp_methods = Window_methods,
};

@ -0,0 +1,7 @@
#ifndef __WINDOW_H__
#define __WINDOW_H__
#include "python.h"
PyTypeObject WindowType;
#endif

@ -60,7 +60,7 @@ class DrawAction(Action, Drawable, metaclass=abc.ABCMeta):
which does the same.
Args:
windows (batch.BatchList of ui.OverlayWindow):
windows (batch.BatchList of ui.CanvasWindow):
the windows to be redrawn
Returns:
@ -148,7 +148,7 @@ class AddImageAction(ImageAction):
after applying the changes of this command.
Args:
old_placement (ui.OverlayWindow.Placement):
old_placement (ui.CanvasWindow.Placement):
the old data of the placement
Returns:
@ -164,7 +164,7 @@ class AddImageAction(ImageAction):
the image of the placement to properly render the placement.
Args:
old_placement (ui.OverlayWindow.Placement):
old_placement (ui.CanvasWindow.Placement):
the old data of the placement
screen_columns (float):
the maximum amount of columns the screen can display
@ -186,7 +186,7 @@ class AddImageAction(ImageAction):
the image of the placement to render the placement more quickly.
Args:
old_placement (ui.OverlayWindow.Placement):
old_placement (ui.CanvasWindow.Placement):
the old data of the placement
screen_columns (float):
the maximum amount of columns the screen can display
@ -256,7 +256,7 @@ class AddImageAction(ImageAction):
self.path, upper_bound_size, image_post_load_processor)
cache = None
view.media[self.identifier] = ui.OverlayWindow.Placement(
view.media[self.identifier] = ui.CanvasWindow.Placement(
self.x, self.y, self.width, self.height,
geometry.Point(self.scaling_position_x,
self.scaling_position_y),

@ -4,6 +4,7 @@ for each element of a list of objects of the same class.
"""
import abc
import collections.abc
import functools
class SubclassingMeta(abc.ABCMeta):
@ -104,17 +105,35 @@ class BatchList(collections.abc.MutableSequence, metaclass=SubclassingMeta):
setattr(type(self), name, decorator)
def __init_attributes__(self, target_instance):
for name in self.__get_public_attributes(target_instance):
self.__declare_decorator__(name, BatchList.BatchField(self, name))
@staticmethod
def __get_public_attributes(target_instance):
attributes = (vars(target_instance)
if hasattr(target_instance, '__dict__')
else [])
for name in filter(lambda name: not name.startswith('_'),
attributes):
self.__declare_decorator__(name, BatchList.BatchField(self, name))
return (name for name in attributes
if not name.startswith('_'))
@staticmethod
@functools.lru_cache()
def __get_public_members(target_type):
members = {
name: member
for type_members in
map(vars, reversed(target_type.mro()))
for name, member in type_members.items()
}
return {
name: member
for name, member in members.items()
if not name.startswith('_')
}
def __init_methods__(self, target_instance):
for name, value in filter(lambda i: not i[0].startswith('_'),
vars(type(target_instance)).items()):
public_members = self.__get_public_members(type(target_instance))
for name, value in public_members.items():
if callable(value):
self.__declare_decorator__(
name, BatchList.BatchMethod(self, name))

@ -16,12 +16,14 @@ import ueberzug.action as action
import ueberzug.tmux_util as tmux_util
import ueberzug.geometry as geometry
import ueberzug.loading as loading
import ueberzug.X as X
async def process_xevents(loop, display, windows):
"""Coroutine which processes X11 events"""
async for event in xutil.Events(loop, display):
windows.process_event(event)
async for _ in xutil.Events(loop, display):
if not any(windows.process_event()):
display.discard_event()
async def process_commands(loop, shutdown_routine_factory,
@ -43,18 +45,18 @@ async def process_commands(loop, shutdown_routine_factory,
asyncio.ensure_future(shutdown_routine_factory())
async def query_windows(window_factory, windows, view):
async def query_windows(display: X.Display, window_factory, windows, view):
"""Signal handler for SIGUSR1.
Searches for added and removed tmux clients.
Added clients: additional windows will be mapped
Removed clients: existing windows will be destroyed
"""
parent_window_infos = xutil.get_parent_window_infos()
parent_window_infos = xutil.get_parent_window_infos(display)
view.offset = tmux_util.get_offset()
map_parent_window_id_info = {info.window_id: info
for info in parent_window_infos}
parent_window_ids = map_parent_window_id_info.keys()
map_current_windows = {window.parent_window.id: window
map_current_windows = {window.parent_id: window
for window in windows}
current_window_ids = map_current_windows.keys()
diff_window_ids = parent_window_ids ^ current_window_ids
@ -201,9 +203,6 @@ def main(options):
finally:
os.close(outfile)
display = xutil.get_display()
screen = display.screen()
window_infos = xutil.get_parent_window_infos()
loop = asyncio.get_event_loop()
executor = thread.DaemonThreadPoolExecutor(max_workers=2)
parser_object = (parser.ParserOption(options['--parser'])
@ -213,11 +212,13 @@ def main(options):
error_handler = error_processor_factory(parser_object)
view = View()
tools = Tools(image_loader, parser_object, error_handler)
window_factory = ui.OverlayWindow.Factory(display, view)
display = X.Display()
window_factory = ui.CanvasWindow.Factory(display, view)
window_infos = xutil.get_parent_window_infos(display)
windows = batch.BatchList(window_factory.create(*window_infos))
image_loader.register_error_handler(error_handler)
view.screen_width = screen.width_in_pixels
view.screen_height = screen.height_in_pixels
view.screen_width = display.screen_width
view.screen_height = display.screen_height
if tmux_util.is_used():
atexit.register(setup_tmux_hooks())
@ -233,7 +234,7 @@ def main(options):
loop.add_signal_handler(
signal.SIGUSR1,
lambda: asyncio.ensure_future(query_windows(
window_factory, windows, view)))
display, window_factory, windows, view)))
loop.add_signal_handler(
signal.SIGWINCH,

@ -4,35 +4,18 @@ import abc
import weakref
import attr
import Xlib.X as X
import Xlib.display as Xdisplay
import Xlib.ext.shape as Xshape
import Xlib.protocol.event as Xevent
import PIL.Image as Image
import ueberzug.xutil as xutil
import ueberzug.geometry as geometry
import ueberzug.scaling as scaling
import Xshm
import ueberzug.X as X
def roundup(value, unit):
return ((value + (unit - 1)) & ~(unit - 1)) >> 3
def get_visual_id(screen, depth: int):
"""Determines the visual id
for the given screen and - depth.
"""
try:
return next(filter(lambda i: i.depth == depth,
screen.allowed_depths)) \
.visuals[0].visual_id
except StopIteration:
raise ValueError(
'Screen does not support depth %d' % depth)
class WindowFactory:
"""Window factory class"""
def __init__(self, display):
@ -44,18 +27,16 @@ class WindowFactory:
raise NotImplementedError()
class OverlayWindow:
class CanvasWindow(X.OverlayWindow):
"""Ensures unmapping of windows"""
SCREEN_DEPTH = 24
class Factory(WindowFactory):
"""OverlayWindows factory class"""
"""CanvasWindows factory class"""
def __init__(self, display, view):
super().__init__(display)
self.view = view
def create(self, *window_infos: xutil.TerminalWindowInfo):
return [OverlayWindow(self.display, self.view, info)
return [CanvasWindow(self.display, self.view, info)
for info in window_infos]
class Placement:
@ -139,154 +120,58 @@ class OverlayWindow:
return (x, y, *self.transform_image(
term_info, width, height, format_scanline))
def __init__(self, display: Xdisplay.Display,
view, term_info: xutil.TerminalWindowInfo):
def __init__(self, display: X.Display,
view, parent_info: xutil.TerminalWindowInfo):
"""Changes the foreground color of the gc object.
Args:
display (Xlib.display.Display): any created instance
display (X.Display): any created instance
parent_id (int): the X11 window id of the parent window
"""
self._display = display
self._screen = display.screen()
self._colormap = None
self.parent_info = term_info
self.parent_window = None
self.window = None
super().__init__(display, parent_info.window_id)
self.parent_info = parent_info
self._view = view
self._width = 1
self._height = 1
self._image = Xshm.Image(
self._screen.width_in_pixels,
self._screen.height_in_pixels)
self.create()
self.scanline_pad = display.bitmap_format_scanline_pad
self.scanline_unit = display.bitmap_format_scanline_unit
self.screen_width = display.screen_width
self.screen_height = display.screen_height
self._image = X.Image(
display,
self.screen_width,
self.screen_height)
def __enter__(self):
self.map()
self.draw()
return self
def __exit__(self, *args):
self.destroy()
pass
def draw(self):
"""Draws the window and updates the visibility mask."""
rectangles = []
scanline_pad = self.window.display.info.bitmap_format_scanline_pad
scanline_unit = self.window.display.info.bitmap_format_scanline_unit
if not self.parent_info.ready:
self.parent_info.calculate_sizes(
self._width, self._height)
self.width, self.height)
for placement in self._view.media.values():
# x, y are useful names in this case
# pylint: disable=invalid-name
x, y, width, height, image = \
placement.resolve(self._view.offset, self.parent_info,
(scanline_pad, scanline_unit))
(self.scanline_pad, self.scanline_unit))
rectangles.append((x, y, width, height))
self._image.draw(x, y, width, height, image)
self._image.copy_to(
self.window.id,
self.id,
0, 0,
min(self._width, self._screen.width_in_pixels),
min(self._height, self._screen.height_in_pixels))
self.window.shape_rectangles(
Xshape.SO.Set, Xshape.SK.Bounding, 0,
0, 0, rectangles)
self._display.flush()
def create(self):
"""Creates the window and gc"""
if self.window:
return
visual_id = get_visual_id(self._screen, OverlayWindow.SCREEN_DEPTH)
self._colormap = self._screen.root.create_colormap(
visual_id, X.AllocNone)
self.parent_window = self._display.create_resource_object(
'window', self.parent_info.window_id)
parent_size = None
with xutil.get_display() as display:
parent_window = display.create_resource_object(
'window', self.parent_info.window_id)
parent_size = parent_window.get_geometry()
self._width, self._height = parent_size.width, parent_size.height
self.window = self.parent_window.create_window(
0, 0, parent_size.width, parent_size.height, 0,
OverlayWindow.SCREEN_DEPTH,
X.InputOutput,
visual_id,
background_pixmap=0,
colormap=self._colormap,
background_pixel=0,
border_pixel=0,
event_mask=X.ExposureMask)
self.parent_window.change_attributes(
event_mask=X.StructureNotifyMask)
self._set_click_through()
self._set_invisible()
self._display.sync()
min(self.width, self.screen_width),
min(self.height, self.screen_height))
self.set_visibility_mask(rectangles)
super().draw()
def reset_terminal_info(self):
"""Resets the terminal information of this window."""
self.parent_info.reset()
def process_event(self, event):
if (isinstance(event, Xevent.Expose) and
event.window.id == self.window.id and
event.count == 0):
self.draw()
elif (isinstance(event, Xevent.ConfigureNotify) and
event.window.id == self.parent_window.id):
delta_width = event.width - self._width
delta_height = event.height - self._height
if delta_width != 0 or delta_height != 0:
self._width, self._height = event.width, event.height
self.window.configure(
width=event.width,
height=event.height)
self._display.flush()
if delta_width > 0 or delta_height > 0:
self.draw()
def map(self):
self.window.map()
self._display.flush()
def unmap(self):
self.window.unmap()
self._display.flush()
def destroy(self):
"""Destroys the window and it's resources"""
if self.window:
self.window.unmap()
self.window.destroy()
self.window = None
if self._colormap:
self._colormap.free()
self._colormap = None
self._display.flush()
def _set_click_through(self):
"""Sets the input processing area to an area
of 1x1 pixel by using the XShape extension.
So nearly the full window is click-through.
"""
self.window.shape_rectangles(
Xshape.SO.Set, Xshape.SK.Input, 0,
0, 0, [])
def _set_invisible(self):
"""Makes the window invisible."""
self.window.shape_rectangles(
Xshape.SO.Set, Xshape.SK.Bounding, 0,
0, 0, [])

@ -2,17 +2,12 @@
import functools
import asyncio
import Xlib
import Xlib.display as Xdisplay
import ueberzug.tmux_util as tmux_util
import ueberzug.terminal as terminal
import ueberzug.process as process
import ueberzug.X as X
Xdisplay.Display.__enter__ = lambda self: self
Xdisplay.Display.__exit__ = lambda self, *args: self.close()
PREPARED_DISPLAYS = []
DISPLAY_SUPPLIES = 1
@ -20,20 +15,20 @@ DISPLAY_SUPPLIES = 1
class Events:
"""Async iterator class for x11 events"""
def __init__(self, loop, display: Xdisplay.Display):
def __init__(self, loop, display: X.Display):
self._loop = loop
self._display = display
@staticmethod
async def receive_event(loop, display):
async def wait_for_event(loop, display: X.Display):
"""Waits asynchronously for an x11 event and returns it"""
return await loop.run_in_executor(None, display.next_event)
return await loop.run_in_executor(None, display.wait_for_event)
def __aiter__(self):
return self
async def __anext__(self):
return await Events.receive_event(self._loop, self._display)
return await Events.wait_for_event(self._loop, self._display)
class TerminalWindowInfo(terminal.TerminalInfo):
@ -42,25 +37,6 @@ class TerminalWindowInfo(terminal.TerminalInfo):
self.window_id = window_id
async def prepare_display():
"""Fills up the display supplies."""
if len(PREPARED_DISPLAYS) < DISPLAY_SUPPLIES:
PREPARED_DISPLAYS.append(Xdisplay.Display())
def get_display():
"""Unfortunately, Xlib tends to produce death locks
on requests with an expected reply.
(e.g. Drawable#get_geometry)
Use for each request a new display as workaround.
"""
for _ in range(len(PREPARED_DISPLAYS), DISPLAY_SUPPLIES):
asyncio.ensure_future(prepare_display())
if not PREPARED_DISPLAYS:
return Xdisplay.Display()
return PREPARED_DISPLAYS.pop()
@functools.lru_cache()
def get_parent_pids(pid):
"""Determines all parent pids of this process.
@ -76,34 +52,15 @@ def get_parent_pids(pid):
return pids
def get_pid_by_window_id(display: Xdisplay.Display, window_id: int):
window = display.create_resource_object('window', window_id)
prop = window.get_full_property(display.intern_atom('_NET_WM_PID'),
Xlib.X.AnyPropertyType)
return (prop.value[0] if prop
else None)
def get_pid_window_id_map():
def get_pid_window_id_map(display: X.Display):
"""Determines the pid of each mapped window.
Returns:
dict of {pid: window_id}
"""
with get_display() as display:
root = display.screen().root
visible_window_ids = \
(root.get_full_property(
display.intern_atom('_NET_CLIENT_LIST'),
Xlib.X.AnyPropertyType)
.value)
return {**{
get_pid_by_window_id(display, window.id): window.id
for window in root.query_tree().children
}, **{
get_pid_by_window_id(display, window_id): window_id
for window_id in visible_window_ids
}}
return {
display.get_window_pid(window_id): window_id
for window_id in display.get_child_window_ids()}
def sort_by_key_list(mapping: dict, key_list: list):
@ -151,7 +108,7 @@ def get_first_pty(pids: list):
return None
def get_parent_window_infos():
def get_parent_window_infos(display: X.Display):
"""Determines the window id of each
terminal which displays the program using
this layer.
@ -168,7 +125,7 @@ def get_parent_window_infos():
client_pids = {process.get_own_pid()}
if client_pids:
pid_window_id_map = get_pid_window_id_map()
pid_window_id_map = get_pid_window_id_map(display)
for pid in client_pids:
ppids = get_parent_pids(pid)

Loading…
Cancel
Save