X11: Draw overlay / Remove drawings from overlay
Asked Answered
E

2

7

I need to draw a shape directly on screen (a little arrow) in X11, it serves as an overlay. I am searching for about an hour no with no good results. Could anyone provide me a good entry point for what I need? The technologies I can use are cairo, gtk or XLib.

Everything I have found so far either depends on Composition, which is not always available, or will create a white shape behind my arrow (rectangle, a window).

EDIT: I am now able to draw an X11 Overlay using Composite and Cairo. I do it this way (Note: This is a minimal example. It has few to no error checking!!!). Start from Terminal to be able to quit it!

// COMPILE WITH: g++ -o overlay overlay.cc -lXfixes -lXcomposite -lX11 `pkg-config --cflags --libs cairo`
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/Xcomposite.h>
#include <X11/extensions/Xfixes.h>
#include <X11/extensions/shape.h>
#include <cairo/cairo.h>
#include <cairo/cairo-xlib.h>

#include <stdio.h>
#include <unistd.h>

Display *display_;
int old_x_, old_y_;
cairo_surface_t *surf_;
Window overlay_;
int screen_;
int height_;
int width_;
cairo_t *cr_;


void paint_cursor(int new_x, int new_y, bool first = false)
{

    if (!first)
    {
        cairo_set_operator(cr_, CAIRO_OPERATOR_CLEAR);
        cairo_rectangle(cr_, old_x_, old_y_, 20, 20);
        cairo_fill(cr_);
    }
    old_x_ = new_x;
    old_y_ = new_y;

    cairo_set_operator(cr_, CAIRO_OPERATOR_SOURCE);
    cairo_move_to(cr_, new_x, new_y);
    cairo_line_to(cr_, new_x + 0, new_y + 16);
    cairo_line_to(cr_, new_x + 4, new_y + 13);
    cairo_line_to(cr_, new_x + 7, new_y + 18);
    cairo_line_to(cr_, new_x + 9, new_y + 17);
    cairo_line_to(cr_, new_x + 6, new_y + 12);
    cairo_line_to(cr_, new_x + 11, new_y + 12);
    cairo_line_to(cr_, new_x + 0, new_y + 0);

    cairo_set_source_rgba(cr_, 0.0, 0.0, 0.0, 0.5);
    cairo_stroke_preserve(cr_);
    cairo_set_source_rgba(cr_, 0.0, 1.0, 0.0, 0.5);
    cairo_fill(cr_);
}

int main()
{

    display_ = ::XOpenDisplay(0);
    if (!display_)
    {
        return -1;
    }

    screen_ = ::XDefaultScreen(display_);
    Window root = RootWindow(display_, screen_);

    ::XCompositeRedirectSubwindows(display_, root, CompositeRedirectAutomatic);
    ::XSelectInput(display_, root, SubstructureNotifyMask);

    overlay_ = ::XCompositeGetOverlayWindow(display_, root);

    XserverRegion region = ::XFixesCreateRegion(display_, 0, 0);
    ::XFixesSetWindowShapeRegion(display_, overlay_, ShapeBounding, 0, 0, 0);
    ::XFixesSetWindowShapeRegion(display_, overlay_, ShapeInput, 0, 0, region);
    ::XFixesDestroyRegion(display_, region);

    width_ = DisplayWidth(display_, screen_);
    height_ = DisplayHeight(display_, screen_);

    surf_ = ::cairo_xlib_surface_create(display_, overlay_, DefaultVisual(display_, screen_), width_, height_);

    cr_ = ::cairo_create(surf_);
    ::XSelectInput(display_, overlay_, ExposureMask);

    old_x_ = 0;
    old_y_ = 0;
    paint_cursor(0, 0, true);

    XEvent ev;

    Window root_return, child_return;
    int root_x_return, root_y_return;
    int win_x_return, win_y_return;
    unsigned int mask;


    for (;;)
    {
        XQueryPointer(display_, root, &root_return, &child_return, &root_x_return, &root_y_return, &win_x_return, &win_y_return, &mask);
        paint_cursor(root_x_return, root_y_return);
        printf("Paint\n");
    }

    return 0;
}

The question that is left: How to I remove the old cursor that was drawn? I tried XClearArea, I tried to overpaint with Cairo, I tried the Cairo Clear operator, etc. Nothing worked. Can someone point me here in the right direction?

Exhilarant answered 17/8, 2016 at 12:39 Comment(11)
Core X11 doesn't do this, you will need an extension of some sort. You can use Composite or Shape extension. Shape is more readily available than Composite. You can also use an overlay visual if available (requires OpenGL and overlay support in hardware).Theine
You can also draw directly over existing windows, but this is not recommended. If other program tries to repaint at the same time, the result may not look correctly.Theine
Look at the source of xeyes. Ancient but works.Theine
xeyes is just a simple window where no background is drawn. what I do is an overlay...Exhilarant
No, it's a window with a shape, not just one that has no background.Theine
ahhhh interesting. thanks!Exhilarant
btw I just tried your examples over vnc, it failed because xvnc doesn't use the composite extension. xshape worked ;)Theine
I am trying to do this atm :). But I fail at setting the _NET_WM_WINDOW_TYPE to _NET_WM_WINDOW_TYPE_UTILITY to prevent it beeing shown in the taskbarExhilarant
Screw WM, just use an override-redirect window. exampleTheine
That does not work. Its still decorated and in the taskbarExhilarant
With override redirect set to true? This cannot possibly happen.Theine
E
-3

I have solved this issue myself now using the Shape extension from X11 and a separate message loop in a thread. Its not ideal but it works. Ill post code as soon as I have access to the code again (sick atm).

Exhilarant answered 31/8, 2016 at 11:39 Comment(1)
It's been a long time. I hope you are fine now. Would you post the working code?Nuggar
B
2

This answer should help you out:

"How can I draw selection rectangle on screen for my script?"

Credits go to sdbbs at askubuntu.com

#include<stdio.h>
#include<stdlib.h>
#include<X11/Xlib.h>
#include<X11/cursorfont.h>
#include<unistd.h> // added for sleep/usleep

// original from [https://bbs.archlinux.org/viewtopic.php?id=85378 Select a screen area with mouse and return the geometry of this area? / Programming & Scripting / Arch Linux Forums]
// build with (Ubuntu 14.04):
// gcc -Wall xrectsel.c -o xrectsel -lX11

int main(void)
{
  int rx = 0, ry = 0, rw = 0, rh = 0;
  int rect_x = 0, rect_y = 0, rect_w = 0, rect_h = 0;
  int btn_pressed = 0, done = 0;

  XEvent ev;
  Display *disp = XOpenDisplay(NULL);

  if(!disp)
    return EXIT_FAILURE;

  Screen *scr = NULL;
  scr = ScreenOfDisplay(disp, DefaultScreen(disp));

  Window root = 0;
  root = RootWindow(disp, XScreenNumberOfScreen(scr));

  Cursor cursor, cursor2;
  cursor = XCreateFontCursor(disp, XC_left_ptr);
  cursor2 = XCreateFontCursor(disp, XC_lr_angle);

  XGCValues gcval;
  gcval.foreground = XWhitePixel(disp, 0);
  gcval.function = GXxor;
  gcval.background = XBlackPixel(disp, 0);
  gcval.plane_mask = gcval.background ^ gcval.foreground;
  gcval.subwindow_mode = IncludeInferiors;

  GC gc;
  gc = XCreateGC(disp, root,
                 GCFunction | GCForeground | GCBackground | GCSubwindowMode,
                 &gcval);

  /* this XGrab* stuff makes XPending true ? */
  if ((XGrabPointer
       (disp, root, False,
        ButtonMotionMask | ButtonPressMask | ButtonReleaseMask, GrabModeAsync,
        GrabModeAsync, root, cursor, CurrentTime) != GrabSuccess))
    printf("couldn't grab pointer:");

  if ((XGrabKeyboard
       (disp, root, False, GrabModeAsync, GrabModeAsync,
        CurrentTime) != GrabSuccess))
    printf("couldn't grab keyboard:");

  // see also: https://mcmap.net/q/1630221/-xpending-cycle-is-making-cpu-100
  while (!done) {
    //~ while (!done && XPending(disp)) {
      //~ XNextEvent(disp, &ev);
    if (!XPending(disp)) { usleep(1000); continue; } // fixes the 100% CPU hog issue in original code
    if ( (XNextEvent(disp, &ev) >= 0) ) {
      switch (ev.type) {
        case MotionNotify:
        /* this case is purely for drawing rect on screen */
          if (btn_pressed) {
            if (rect_w) {
              /* re-draw the last rect to clear it */
              XDrawRectangle(disp, root, gc, rect_x, rect_y, rect_w, rect_h);
            } else {
              /* Change the cursor to show we're selecting a region */
              XChangeActivePointerGrab(disp,
                                       ButtonMotionMask | ButtonReleaseMask,
                                       cursor2, CurrentTime);
            }
            rect_x = rx;
            rect_y = ry;
            rect_w = ev.xmotion.x - rect_x;
            rect_h = ev.xmotion.y - rect_y;

            if (rect_w < 0) {
              rect_x += rect_w;
              rect_w = 0 - rect_w;
            }
            if (rect_h < 0) {
              rect_y += rect_h;
              rect_h = 0 - rect_h;
            }
            /* draw rectangle */
            XDrawRectangle(disp, root, gc, rect_x, rect_y, rect_w, rect_h);
            XFlush(disp);
          }
          break;
        case ButtonPress:
          btn_pressed = 1;
          rx = ev.xbutton.x;
          ry = ev.xbutton.y;
          break;
        case ButtonRelease:
          done = 1;
          break;
      }
    }
  }
  /* clear the drawn rectangle */
  if (rect_w) {
    XDrawRectangle(disp, root, gc, rect_x, rect_y, rect_w, rect_h);
    XFlush(disp);
  }
  rw = ev.xbutton.x - rx;
  rh = ev.xbutton.y - ry;
  /* cursor moves backwards */
  if (rw < 0) {
    rx += rw;
    rw = 0 - rw;
  }
  if (rh < 0) {
    ry += rh;
    rh = 0 - rh;
  }

  XCloseDisplay(disp);

  printf("%dx%d+%d+%d\n",rw,rh,rx,ry);

  return EXIT_SUCCESS;
}
Bock answered 31/8, 2016 at 8:58 Comment(4)
This draws a rectangle by XORing the screen with the rectangle. While this method works OK on a static screen, it fails with windows that are being updated continuously.Theine
I am sorry, but this is not what I want. This replaces my cursor. I want to have a second one.Exhilarant
@Exhilarant You don't have to replace the cursor. Change cursor and cursor2 to None in calls to grab functions.Theine
Works only for a static display. I need it in a more "moving" context...Exhilarant
E
-3

I have solved this issue myself now using the Shape extension from X11 and a separate message loop in a thread. Its not ideal but it works. Ill post code as soon as I have access to the code again (sick atm).

Exhilarant answered 31/8, 2016 at 11:39 Comment(1)
It's been a long time. I hope you are fine now. Would you post the working code?Nuggar

© 2022 - 2024 — McMap. All rights reserved.