[FFmpeg-devel] [PATCH v2] avdevice/xcbgrab: Add select_region option.

Andriy Gelman andriy.gelman at gmail.com
Wed Jul 1 07:00:31 EEST 2020


Hi Omar,

Thanks for your patch.

nit: please remove "." at the end of the commit title

On Sun, 28. Jun 13:26, Omar Emara wrote:
> This patch adds a select_region option to the xcbgrab input device.
> If set to 1, the user will be prompted to select the grabbing area
> graphically by clicking and dragging. A rectangle will be drawn to
> mark the grabbing area. A single click with no dragging will select
> the whole screen. The option overwrites the video_size, grab_x, and
> grab_y options if set by the user.
> 
> For testing, just set the select_region option as follows:
> 
> ffmpeg -f x11grab -select_region 1 -i :0.0 output.mp4
> 
> The drawing happens directly on the root window using standard rubber
> banding techniques, so it is very efficient and doesn't depend on any
> X extensions or compositors.
> 
> Signed-off-by: Omar Emara <mail at OmarEmara.dev>
> ---
>  doc/indevs.texi       |   7 +++
>  libavdevice/xcbgrab.c | 133 ++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 140 insertions(+)
> 
> diff --git a/doc/indevs.texi b/doc/indevs.texi
> index 6f5afaf344..b5df111801 100644
> --- a/doc/indevs.texi
> +++ b/doc/indevs.texi
> @@ -1478,6 +1478,13 @@ ffmpeg -f x11grab -framerate 25 -video_size cif -i :0.0+10,20 out.mpg
>  @subsection Options
>  
>  @table @option
> + at item select_region
> +Specify whether to select the grabbing area graphically using the pointer.
> +A value of @code{1} prompts the user to select the grabbing area graphically
> +by clicking and dragging. A single click with no dragging will select the
> +whole screen. This option overwrites the @var{video_size}, @var{grab_x},
> +and @var{grab_y} options. Default value is @code{0}.
> +
>  @item draw_mouse
>  Specify whether to draw the mouse pointer. A value of @code{0} specifies
>  not to draw the pointer. Default value is @code{1}.
> diff --git a/libavdevice/xcbgrab.c b/libavdevice/xcbgrab.c
> index 6f6b2dbf15..71259b986c 100644
> --- a/libavdevice/xcbgrab.c
> +++ b/libavdevice/xcbgrab.c
> @@ -22,6 +22,7 @@
>  #include "config.h"
>  
>  #include <stdlib.h>

> +#include <string.h>

this include is not used

>  #include <xcb/xcb.h>
>  
>  #if CONFIG_LIBXCB_XFIXES
> @@ -69,6 +70,7 @@ typedef struct XCBGrabContext {
>      int show_region;
>      int region_border;
>      int centered;
> +    int select_region;
>  
>      const char *framerate;
>  
> @@ -92,6 +94,7 @@ static const AVOption options[] = {
>      { "centered", "Keep the mouse pointer at the center of grabbing region when following.", 0, AV_OPT_TYPE_CONST, { .i64 = -1 }, INT_MIN, INT_MAX, D, "follow_mouse" },
>      { "show_region", "Show the grabbing region.", OFFSET(show_region), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, D },
>      { "region_border", "Set the region border thickness.", OFFSET(region_border), AV_OPT_TYPE_INT, { .i64 = 3 }, 1, 128, D },
> +    { "select_region", "Select the grabbing region graphically using the pointer.", OFFSET(select_region), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, D },
>      { NULL },
>  };
>  
> @@ -668,6 +671,123 @@ static void setup_window(AVFormatContext *s)
>      draw_rectangle(s);
>  }
>  
> +#define CROSSHAIR_CURSOR 34
> +

> +typedef struct {
> +    int16_t x;
> +    int16_t y;
> +} Position;
> +

you can just reuse xcb_point_t struct 

> +static xcb_rectangle_t rectangle_from_corners(Position *corner_a, Position *corner_b) {
> +    xcb_rectangle_t rectangle;

> +    if (corner_a->x < corner_b->x) {
> +        rectangle.x = corner_a->x;
> +        rectangle.width = corner_b->x - corner_a->x;
> +    } else {
> +        rectangle.x = corner_b->x;
> +        rectangle.width = corner_a->x - corner_b->x;
> +    }
> +    if (corner_a->y < corner_b->y) {
> +        rectangle.y = corner_a->y;
> +        rectangle.height = corner_b->y - corner_a->y;
> +    } else {
> +        rectangle.y = corner_b->y;
> +        rectangle.height = corner_a->y - corner_b->y;
> +    }

can be simplified:
rectangle.x = FFMIN(corner_a->x, corner_b->x);
rectangle.width = FFABS(corner_a->x - corner_b->x);
...

> +    return rectangle;
> +}
> +
> +static xcb_rectangle_t select_region(xcb_connection_t *connection,
> +                                     xcb_screen_t *screen) {
> +    int done = 0;
> +    int was_drawn = 0;
> +    int was_pressed = 0;
> +    xcb_cursor_t cursor;
> +    xcb_font_t cursor_font;
> +    Position press_position;
> +    xcb_generic_event_t *event;

> +    xcb_rectangle_t old_rectangle;

zero initialize old_rectangle because 
event = xcb_wait_for_event(connection) can return NULL in the while loop below

> +
> +    xcb_window_t root_window = screen->root;
> +    xcb_gcontext_t graphics_context = xcb_generate_id(connection);
> +    uint32_t graphics_context_mask = XCB_GC_FUNCTION | XCB_GC_SUBWINDOW_MODE;
> +    uint32_t graphics_context_values[] = {XCB_GX_INVERT,
> +                                          XCB_SUBWINDOW_MODE_INCLUDE_INFERIORS};

please use same style as other functions
s/graphics_context/gc
s/graphics_context_mask/mask
s/graphics_context_values/values

> +    xcb_create_gc(connection, graphics_context, root_window,
> +                  graphics_context_mask, graphics_context_values);
> +
> +    cursor_font = xcb_generate_id(connection);
> +    xcb_open_font(connection, cursor_font, strlen("cursor"), "cursor");
> +    cursor = xcb_generate_id(connection);
> +    xcb_create_glyph_cursor(connection, cursor, cursor_font, cursor_font,
> +                            CROSSHAIR_CURSOR, CROSSHAIR_CURSOR + 1, 0, 0, 0,
> +                            0xFFFF, 0xFFFF, 0xFFFF);
> +    xcb_grab_pointer(connection, 0, root_window,
> +                     XCB_EVENT_MASK_BUTTON_PRESS |
> +                     XCB_EVENT_MASK_BUTTON_RELEASE |
> +                     XCB_EVENT_MASK_BUTTON_MOTION,
> +                     XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC,
> +                     root_window, cursor, XCB_CURRENT_TIME);
> +    xcb_grab_server(connection);
> +    xcb_flush(connection);
> +
> +    while ((event = xcb_wait_for_event(connection))) {
> +        switch (event->response_type & ~0x80) {
> +        case XCB_BUTTON_PRESS: {
> +            xcb_button_press_event_t *press = (xcb_button_press_event_t *)event;
> +            press_position = (Position){press->event_x, press->event_y};

> +            old_rectangle.x = press_position.x;
> +            old_rectangle.y = press_position.y;
> +            old_rectangle.width = 0;
> +            old_rectangle.height = 0;
> +            was_pressed = 1;

by drawing the zero sized rectangle at this point you don't have to keep
track of two versions of rectangle.. and could drop was_drawn variable

> +            break;
> +        }
> +        case XCB_MOTION_NOTIFY: {
> +            xcb_motion_notify_event_t *motion =
> +                (xcb_motion_notify_event_t *)event;
> +            if (was_pressed) {
> +                Position current_cursor_position = {motion->event_x,
> +                                                    motion->event_y};

i.e. always undraw the rectangle at this point before getting the coordinates of
the new rectangle below

> +                xcb_rectangle_t rectangle = rectangle_from_corners(
> +                    &press_position, &current_cursor_position);
> +                if (was_drawn) {
> +                    xcb_poly_rectangle(connection, root_window,
> +                                       graphics_context, 1, &old_rectangle);
> +                }
> +                xcb_poly_rectangle(connection, root_window, graphics_context, 1,
> +                                   &rectangle);
> +                old_rectangle = rectangle;
> +                was_drawn = 1;
> +                xcb_flush(connection);
> +            }
> +            break;
> +        }
> +        case XCB_BUTTON_RELEASE: {
> +            if (was_drawn) {
> +                xcb_poly_rectangle(connection, root_window, graphics_context, 1,
> +                                   &old_rectangle);
> +                xcb_flush(connection);
> +            }
> +            done = 1;
> +            break;
> +        }
> +        default:
> +            break;
> +        }
> +        free(event);
> +        if (done) {
> +            break;
> +        }
> +    }
> +    xcb_ungrab_server(connection);
> +    xcb_ungrab_pointer(connection, XCB_CURRENT_TIME);
> +    xcb_free_cursor(connection, cursor);
> +    xcb_close_font(connection, cursor_font);
> +    xcb_free_gc(connection, graphics_context);
> +    return old_rectangle;
> +}
> +
>  static av_cold int xcbgrab_read_header(AVFormatContext *s)
>  {
>      XCBGrabContext *c = s->priv_data;
> @@ -702,6 +822,19 @@ static av_cold int xcbgrab_read_header(AVFormatContext *s)
>          return AVERROR(EIO);
>      }
>  
> +    if (c->select_region) {
> +        xcb_rectangle_t rectangle = select_region(c->conn, c->screen);
> +        c->width = rectangle.width;
> +        c->height = rectangle.height;
> +        if (c->width && c->height) {
> +            c->x = rectangle.x;
> +            c->y = rectangle.y;
> +        } else {
> +            c->x = 0;
> +            c->y = 0;
> +        }
> +    }
> +
>      ret = create_stream(s);
>  
>      if (ret < 0) {
> -- 
> 2.27.0
> 

Thanks,
-- 
Andriy


More information about the ffmpeg-devel mailing list