8

I am trying to make a simple screencasting bash script. I want to get screen selection coordinates by drawing a red rectangle with mouse. I can use xdotools to get coordinates but how can I draw rectangle on screen?

4
  • Why not use video editing tools after you have created the screencast?
    – jobin
    Jun 24, 2014 at 17:46
  • 1
    @Jobin Thank you for your comment. I didn't get what you meant, what will be benefit of video editing tool? Oh, you got my question wrong. I want to draw a rectangle during area selection with my mouse on screen, I don't want to edit video.
    – kenn
    Jun 24, 2014 at 18:20
  • 1
    Are you looking for something like this askubuntu.com/questions/328543/drawing-over-the-desktop ?
    – TuKsn
    Jun 24, 2014 at 19:09
  • @Tuknutx Yes, it's close to my need. I think a simple python script will do the job
    – kenn
    Jun 24, 2014 at 19:46

2 Answers 2

5

I just found https://www.opengl.org/discussion_boards/showthread.php/157014-Compiling-with-gcc, and I modified the code to get rid of the 100% CPU hog; it seems to work fine on Ubuntu 14.04 - simply run it from a terminal, draw your rectangle area, and the program will exit, printing the coordinates in the terminal:

xrectsel.c:

#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: http://stackoverflow.com/questions/19659486/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;
}
4
  • 1
    Works like a charm. Tight code, tight answer.
    – DragonLord
    Jan 21, 2021 at 18:07
  • does have XOR problems if a popup appears over the outline, though, such as if program instance menus unfold. But good enough for almost all cases. Very useful, thanks.
    – DragonLord
    Feb 10, 2021 at 19:11
  • The last bit, re-calculating rx, ry, ... etc seems unnecessary? You can just display rect_* I guess? The results are incorrect with r*. But thanks for the code, it's very neat and useful! Oct 3, 2023 at 2:02
  • Notice that after XOpenDisplay you can no longer use Xdotool as the keyboard is intercepted. You have to XCloseDisplay to do that and perhaps to re-initialize everything again. Oct 3, 2023 at 2:06
3

you can install python cli package

pip3 install python-xrectsel

Check format options

xrectsel --help

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .