16

I want to draw simple primitives at specific pixels on the screen (similar to this question). In order to do that I draw on top of all windows using the Overlay Window of the Window Manager. I can see the shape I am drawing and mouse events pass through but I don't see for example Window movements that are below the Overlay Window (unless I kill my application). I am new to Xlib programming, sry for asking a maybe simple question.

#include <assert.h>
#include <stdio.h>
#include <X11/Xlib.h>

#include <X11/extensions/Xcomposite.h>
#include <X11/extensions/Xfixes.h>
#include <X11/extensions/shape.h>

#include <cairo.h>
#include <cairo-xlib.h>

Display *d;
Window overlay;
Window root;
int width, height;

void
allow_input_passthrough (Window w)
{
    XserverRegion region = XFixesCreateRegion (d, NULL, 0);

    XFixesSetWindowShapeRegion (d, w, ShapeBounding, 0, 0, 0);
    XFixesSetWindowShapeRegion (d, w, ShapeInput, 0, 0, region);

    XFixesDestroyRegion (d, region);
}

void
prep_overlay (void)
{
    overlay = XCompositeGetOverlayWindow (d, root);
    allow_input_passthrough (overlay);
}

void draw(cairo_t *cr) {
    int quarter_w = width / 4;
    int quarter_h = height / 4;
    cairo_set_source_rgb(cr, 1.0, 0.0, 0.0);
    cairo_rectangle(cr, quarter_w, quarter_h, quarter_w * 2, quarter_h * 2);
    cairo_fill(cr);
}

int main() {
    d = XOpenDisplay(NULL);

    int s = DefaultScreen(d);
    root = RootWindow(d, s);

    XCompositeRedirectSubwindows (d, root, CompositeRedirectAutomatic);
    XSelectInput (d, root, SubstructureNotifyMask);

    width = DisplayWidth(d, s);
    height = DisplayHeight(d, s);

    prep_overlay();

    cairo_surface_t *surf = cairo_xlib_surface_create(d, overlay,
                                  DefaultVisual(d, s),
                                  width, height);
    cairo_t *cr = cairo_create(surf);

    XSelectInput(d, overlay, ExposureMask);

    draw(cr);

    XEvent ev;
    while (1) {
    XNextEvent(d, &ev);
        if (ev.type == Expose) {
            draw(cr);
        }
    }

    cairo_destroy(cr);
    cairo_surface_destroy(surf);
    XCloseDisplay(d);
    return 0;
}

How can I draw pixels on the Overlay Window and still see the windows below?

Community
  • 1
  • 1
user1325516
  • 161
  • 1
  • 1
  • 4
  • consider adding a tag for your programming language. Many C/C++ programmers may be able to help you, but if they are filtering by their 'topics' they won't see your question. Good luck. – shellter Feb 14 '14 at 15:45
  • Can you post an entire program? – n. m. could be an AI Feb 14 '14 at 21:17
  • I've edited the original post and have include a complete program. Compile with: `gcc x11drawoverlay.c -o x11drawoverlay -lX11 -lXfixes -lXcomposite -lcairo -I /usr/include/cairo` – user1325516 Feb 15 '14 at 16:16
  • 1
    I'm getting no output whatsoever from this program. – n. m. could be an AI Feb 15 '14 at 19:34
  • It draws a red rectangle on my desktop. – user1325516 Feb 16 '14 at 17:30
  • Still does absolutely nothing on my desktop. Just han – n. m. could be an AI Feb 16 '14 at 18:37
  • I am new to X11/Xlib programming. Could you please tell me why it hangs? – user1325516 Feb 16 '14 at 20:02
  • Sorry, connection glitch. It hangs on `XNextEvent` waiting for expose events that never come. The first `draw` has no effect, no rectangle is drawn. I honestly have no idea why. I was exposed to X11 programming some years ago and am reasonably familiar with core X11, but the newer extensions like Composite are new to me. – n. m. could be an AI Feb 16 '14 at 21:03
  • I've removed the loop and simply added a loop for drawing: `while(1) { overlay = XCompositeGetOverlayWindow (d, root); draw(cr); XCompositeReleaseOverlayWindow (d, root); sleep(50); }` The overlay window still blocks the windows underneath. So again, how do I draw on the overlay window without blocking other windows? – user1325516 Feb 17 '14 at 16:55
  • 1
    This is not a correct use of the overlay window. Why don't you just create your own override-redirect window that is only as large as you need it and call XRaiseWindow on it 100 times per second? (This would still be insane, but slightly less insane than the existing solution here) – Uli Schlachter Jan 07 '19 at 20:12

2 Answers2

11

May I suggest a simpler, pure X11 solution that does not have the flickering problem I experienced and also mentioned here. It uses the override_redirect functionality in Xlib:

#include <assert.h>
#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/X.h>
#include <X11/Xutil.h>

#include <cairo.h>
#include <cairo-xlib.h>

#include <chrono>
#include <thread>

void draw(cairo_t *cr) {
    cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 0.5);
    cairo_rectangle(cr, 0, 0, 200, 200);
    cairo_fill(cr);
}

int main() {
    Display *d = XOpenDisplay(NULL);
    Window root = DefaultRootWindow(d);
    int default_screen = XDefaultScreen(d);

    // these two lines are really all you need
    XSetWindowAttributes attrs;
    attrs.override_redirect = true;

    XVisualInfo vinfo;
    if (!XMatchVisualInfo(d, DefaultScreen(d), 32, TrueColor, &vinfo)) {
        printf("No visual found supporting 32 bit color, terminating\n");
        exit(EXIT_FAILURE);
    }
    // these next three lines add 32 bit depth, remove if you dont need and change the flags below
    attrs.colormap = XCreateColormap(d, root, vinfo.visual, AllocNone);
    attrs.background_pixel = 0;
    attrs.border_pixel = 0;

    // Window XCreateWindow(
    //     Display *display, Window parent,
    //     int x, int y, unsigned int width, unsigned int height, unsigned int border_width,
    //     int depth, unsigned int class, 
    //     Visual *visual,
    //     unsigned long valuemask, XSetWindowAttributes *attributes
    // );
    Window overlay = XCreateWindow(
        d, root,
        0, 0, 200, 200, 0,
        vinfo.depth, InputOutput, 
        vinfo.visual,
        CWOverrideRedirect | CWColormap | CWBackPixel | CWBorderPixel, &attrs
    );

    XMapWindow(d, overlay);

    cairo_surface_t* surf = cairo_xlib_surface_create(d, overlay,
                                  vinfo.visual,
                                  200, 200);
    cairo_t* cr = cairo_create(surf);

    draw(cr);
    XFlush(d);

    // show the window for 10 seconds
    std::this_thread::sleep_for(std::chrono::milliseconds(10000));

    cairo_destroy(cr);
    cairo_surface_destroy(surf);

    XUnmapWindow(d, overlay);
    XCloseDisplay(d);
    return 0;
}

I went ahead and added 32 bit depth, but you get the picture. You can remove it if you desire.

Note that as far as the original question is concerned (drawing on the overlay window) this does not do. This draws a window that looks and behaves pretty much exactly like the other requirements in the question. However, it does not guarantee any direct drawing on the composite overlay window that is part of X11.

What it does do is just tell the window manager "don't mess with or decorate this window in any way", which itself is a suggestion, and window managers are not obliged to obey this rule (though almost all do). You probably don't want to directly draw on the overlay window as that would interfere with the compositor and almost certainly not draw anything correctly. I am not even sure how you would do that if the compositor has already requested access to the composite overlay, only one process at a time can access it.

Also, the overlay window is not relevant to the window manager, it is part of the compositor (Xcomposite).

Asad-ullah Khan
  • 1,573
  • 18
  • 22
  • For me, this blocks mouse events on Ubuntu 18.04 with GNOME 3. – David Zhao Akeley Jul 07 '20 at 23:16
  • You mean you can't move mouse at all while it is running? Or that you cannot interact with things drawn on the overlay? Only thing I can think of is my sleep call possibly interfering, maybe try a different sleep call? – Asad-ullah Khan Jul 08 '20 at 08:31
  • Mouse works fine outside the overlay window; what I experience is that mouse clicks in the region of the window do not get passed on to the window behind the transparent window. On re-reading, I'm not sure this was actually a design goal of this program. – David Zhao Akeley Jul 09 '20 at 23:48
  • Ah yes you will probably need to pass the events down to window below somehow. I am not sure how to do this. This answer may be of use: https://stackoverflow.com/questions/16400937/click-through-transparent-xlib-windows – Asad-ullah Khan Jul 10 '20 at 22:12
3

sleep(50)! that's too much, it's 50 seconds. I used 5ms delay which works well.

Your problem seems with the runtime environment. You should have a composite display manager running already. (Not all display managers work as expected, better to try on different ones)

I confirm that screen below updated without any problem and I could interact with it.

This was run on:

Ubuntu 15.10
Kernel 4.2.0-18-generic
X.Org X Server 1.17.2
Compiz 0.9.12.2

Here the full code with just delay modification:

#include <assert.h>
#include <stdio.h>
#include <time.h>
#include <X11/Xlib.h>

#include <X11/extensions/Xcomposite.h>
#include <X11/extensions/Xfixes.h>
#include <X11/extensions/shape.h>

#include <cairo.h>
#include <cairo-xlib.h>

Display *d;
Window overlay;
Window root;
int width, height;

void
allow_input_passthrough (Window w)
{
    XserverRegion region = XFixesCreateRegion (d, NULL, 0);

    XFixesSetWindowShapeRegion (d, w, ShapeBounding, 0, 0, 0);
    XFixesSetWindowShapeRegion (d, w, ShapeInput, 0, 0, region);

    XFixesDestroyRegion (d, region);
}

void
prep_overlay (void)
{
    overlay = XCompositeGetOverlayWindow (d, root);
    allow_input_passthrough (overlay);
}

void draw(cairo_t *cr) {
    int quarter_w = width / 4;
    int quarter_h = height / 4;
    cairo_set_source_rgb(cr, 1.0, 0.0, 0.0);
    cairo_rectangle(cr, quarter_w, quarter_h, quarter_w * 2, quarter_h * 2);
    cairo_fill(cr);
}

int main() {
    struct timespec ts = {0, 5000000};

    d = XOpenDisplay(NULL);

    int s = DefaultScreen(d);
    root = RootWindow(d, s);

    XCompositeRedirectSubwindows (d, root, CompositeRedirectAutomatic);
    XSelectInput (d, root, SubstructureNotifyMask);

    width = DisplayWidth(d, s);
    height = DisplayHeight(d, s);

    prep_overlay();

    cairo_surface_t *surf = cairo_xlib_surface_create(d, overlay,
                                  DefaultVisual(d, s),
                                  width, height);
    cairo_t *cr = cairo_create(surf);

    XSelectInput(d, overlay, ExposureMask);

    draw(cr);

    XEvent ev;
    while(1) {
      overlay = XCompositeGetOverlayWindow (d, root);
      draw(cr);
      XCompositeReleaseOverlayWindow (d, root);
      nanosleep(&ts, NULL);
    }

    cairo_destroy(cr);
    cairo_surface_destroy(surf);
    XCloseDisplay(d);
    return 0;
}

X11 overlay on Ubuntu 15.10

X11 overlay on Ubuntu 15.10, desktop overview

user.dz
  • 962
  • 2
  • 19
  • 39
  • This somehow depends on the desktop environment, it works in Ubuntu (Unity), but does not work in Cinnamon. I have a question: do you know if it is possible to draw without flickering? If it is implemented the way it's described in your answer, it will for sure work, but the user will be able to see the image blinking. – Daniel Feb 20 '18 at 13:17
  • 1
    @ScienceSE I have posted a solution that does not have flickering below, if you are still interested – Asad-ullah Khan Sep 04 '19 at 02:41