diff mbox series

[FFmpeg-devel,v6] avdevice/xcbgrab: Add select_region option

Message ID 20200711092909.115525-1-mail@OmarEmara.dev
State Accepted
Headers show
Series [FFmpeg-devel,v6] avdevice/xcbgrab: Add select_region option
Related show

Checks

Context Check Description
andriy/default pending
andriy/make success Make finished
andriy/make_fate success Make fate finished

Commit Message

Omar Emara July 11, 2020, 9:29 a.m. UTC
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@OmarEmara.dev>
---
 doc/indevs.texi       |   8 +++
 libavdevice/xcbgrab.c | 127 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 135 insertions(+)

Comments

Andriy Gelman Oct. 9, 2020, 3:40 a.m. UTC | #1
On Sat, 11. Jul 11:29, 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@OmarEmara.dev>
> ---
>  doc/indevs.texi       |   8 +++
>  libavdevice/xcbgrab.c | 127 ++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 135 insertions(+)
> 
> diff --git a/doc/indevs.texi b/doc/indevs.texi
> index 6f5afaf344..90ccc917aa 100644
> --- a/doc/indevs.texi
> +++ b/doc/indevs.texi
> @@ -1478,6 +1478,14 @@ ffmpeg -f x11grab -framerate 25 -video_size cif -i :0.0+10,20 out.mpg
>  @subsection Options
>  
>  @table @option
> +@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. A region with zero width or height will also 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..bcd91e54af 100644
> --- a/libavdevice/xcbgrab.c
> +++ b/libavdevice/xcbgrab.c
> @@ -22,6 +22,7 @@
>  #include "config.h"
>  
>  #include <stdlib.h>
> +#include <string.h>
>  #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_BOOL, { .i64 = 0 }, 0, 1, D },
>      { NULL },
>  };
>  
> @@ -668,6 +671,122 @@ static void setup_window(AVFormatContext *s)
>      draw_rectangle(s);
>  }
>  
> +#define CROSSHAIR_CURSOR 34
> +
> +static xcb_rectangle_t rectangle_from_corners(xcb_point_t *corner_a,
> +                                              xcb_point_t *corner_b)
> +{
> +    xcb_rectangle_t rectangle;
> +    rectangle.x = FFMIN(corner_a->x, corner_b->x);
> +    rectangle.y = FFMIN(corner_a->y, corner_b->y);
> +    rectangle.width = FFABS(corner_a->x - corner_b->x);
> +    rectangle.height = FFABS(corner_a->y - corner_b->y);
> +    return rectangle;
> +}
> +
> +static int select_region(AVFormatContext *s)
> +{
> +    XCBGrabContext *c = s->priv_data;
> +    xcb_connection_t *conn = c->conn;
> +    xcb_screen_t *screen = c->screen;
> +
> +    int ret = 0;
> +    int done = 0;
> +    int was_pressed = 0;
> +    xcb_cursor_t cursor;
> +    xcb_font_t cursor_font;
> +    xcb_point_t press_position;
> +    xcb_generic_event_t *event;
> +    xcb_rectangle_t rectangle = {0};
> +    xcb_grab_pointer_reply_t *reply;
> +    xcb_grab_pointer_cookie_t cookie;
> +
> +    xcb_window_t root_window = screen->root;
> +    xcb_gcontext_t gc = xcb_generate_id(conn);
> +    uint32_t mask = XCB_GC_FUNCTION | XCB_GC_SUBWINDOW_MODE;
> +    uint32_t values[] = {XCB_GX_INVERT,
> +                         XCB_SUBWINDOW_MODE_INCLUDE_INFERIORS};
> +    xcb_create_gc(conn, gc, root_window, mask, values);
> +
> +    cursor_font = xcb_generate_id(conn);
> +    xcb_open_font(conn, cursor_font, strlen("cursor"), "cursor");
> +    cursor = xcb_generate_id(conn);
> +    xcb_create_glyph_cursor(conn, cursor, cursor_font, cursor_font,
> +                            CROSSHAIR_CURSOR, CROSSHAIR_CURSOR + 1, 0, 0, 0,
> +                            0xFFFF, 0xFFFF, 0xFFFF);
> +    cookie = xcb_grab_pointer(conn, 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);
> +    reply = xcb_grab_pointer_reply(conn, cookie, NULL);
> +    if (!reply || reply->status != XCB_GRAB_STATUS_SUCCESS) {
> +        av_log(s, AV_LOG_ERROR,
> +               "Failed to select region. Could not grab pointer.\n");
> +        ret = AVERROR(EIO);
> +        free(reply);
> +        goto fail;
> +    }
> +    free(reply);
> +
> +    xcb_grab_server(conn);
> +
> +    while (!done && (event = xcb_wait_for_event(conn))) {
> +        switch (event->response_type & ~0x80) {
> +        case XCB_BUTTON_PRESS: {
> +            xcb_button_press_event_t *press = (xcb_button_press_event_t *)event;
> +            press_position = (xcb_point_t){press->event_x, press->event_y};
> +            rectangle.x = press_position.x;
> +            rectangle.y = press_position.y;
> +            xcb_poly_rectangle(conn, root_window, gc, 1, &rectangle);
> +            was_pressed = 1;
> +            break;
> +        }
> +        case XCB_MOTION_NOTIFY: {
> +            if (was_pressed) {
> +                xcb_motion_notify_event_t *motion =
> +                    (xcb_motion_notify_event_t *)event;
> +                xcb_point_t cursor_position = {motion->event_x,
> +                                               motion->event_y};
> +                xcb_poly_rectangle(conn, root_window, gc, 1, &rectangle);
> +                rectangle = rectangle_from_corners(
> +                    &press_position, &cursor_position);
> +                xcb_poly_rectangle(conn, root_window, gc, 1, &rectangle);
> +            }
> +            break;
> +        }
> +        case XCB_BUTTON_RELEASE: {
> +            xcb_poly_rectangle(conn, root_window, gc, 1, &rectangle);
> +            done = 1;
> +            break;
> +        }
> +        default:
> +            break;
> +        }
> +        xcb_flush(conn);
> +        free(event);
> +    }
> +    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;
> +    }
> +    xcb_ungrab_server(conn);
> +    xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
> +    xcb_flush(conn);
> +
> +fail:
> +    xcb_free_cursor(conn, cursor);
> +    xcb_close_font(conn, cursor_font);
> +    xcb_free_gc(conn, gc);
> +    return ret;
> +}
> +
>  static av_cold int xcbgrab_read_header(AVFormatContext *s)
>  {
>      XCBGrabContext *c = s->priv_data;
> @@ -702,6 +821,14 @@ static av_cold int xcbgrab_read_header(AVFormatContext *s)
>          return AVERROR(EIO);
>      }
>  
> +    if (c->select_region) {
> +        ret = select_region(s);
> +        if (ret < 0) {
> +            xcbgrab_read_close(s);
> +            return ret;
> +        }
> +    }
> +
>      ret = create_stream(s);
>  
>      if (ret < 0) {

Any more comments on this patch?

Thanks,
Andriy Gelman Nov. 3, 2020, 5:25 a.m. UTC | #2
On Thu, 08. Oct 23:40, Andriy Gelman wrote:
> On Sat, 11. Jul 11:29, 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@OmarEmara.dev>
> > ---
> >  doc/indevs.texi       |   8 +++
> >  libavdevice/xcbgrab.c | 127 ++++++++++++++++++++++++++++++++++++++++++
> >  2 files changed, 135 insertions(+)
> > 
> > diff --git a/doc/indevs.texi b/doc/indevs.texi
> > index 6f5afaf344..90ccc917aa 100644
> > --- a/doc/indevs.texi
> > +++ b/doc/indevs.texi
> > @@ -1478,6 +1478,14 @@ ffmpeg -f x11grab -framerate 25 -video_size cif -i :0.0+10,20 out.mpg
> >  @subsection Options
> >  
> >  @table @option
> > +@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. A region with zero width or height will also 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..bcd91e54af 100644
> > --- a/libavdevice/xcbgrab.c
> > +++ b/libavdevice/xcbgrab.c
> > @@ -22,6 +22,7 @@
> >  #include "config.h"
> >  
> >  #include <stdlib.h>
> > +#include <string.h>
> >  #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_BOOL, { .i64 = 0 }, 0, 1, D },
> >      { NULL },
> >  };
> >  
> > @@ -668,6 +671,122 @@ static void setup_window(AVFormatContext *s)
> >      draw_rectangle(s);
> >  }
> >  
> > +#define CROSSHAIR_CURSOR 34
> > +
> > +static xcb_rectangle_t rectangle_from_corners(xcb_point_t *corner_a,
> > +                                              xcb_point_t *corner_b)
> > +{
> > +    xcb_rectangle_t rectangle;
> > +    rectangle.x = FFMIN(corner_a->x, corner_b->x);
> > +    rectangle.y = FFMIN(corner_a->y, corner_b->y);
> > +    rectangle.width = FFABS(corner_a->x - corner_b->x);
> > +    rectangle.height = FFABS(corner_a->y - corner_b->y);
> > +    return rectangle;
> > +}
> > +
> > +static int select_region(AVFormatContext *s)
> > +{
> > +    XCBGrabContext *c = s->priv_data;
> > +    xcb_connection_t *conn = c->conn;
> > +    xcb_screen_t *screen = c->screen;
> > +
> > +    int ret = 0;
> > +    int done = 0;
> > +    int was_pressed = 0;
> > +    xcb_cursor_t cursor;
> > +    xcb_font_t cursor_font;
> > +    xcb_point_t press_position;
> > +    xcb_generic_event_t *event;
> > +    xcb_rectangle_t rectangle = {0};
> > +    xcb_grab_pointer_reply_t *reply;
> > +    xcb_grab_pointer_cookie_t cookie;
> > +
> > +    xcb_window_t root_window = screen->root;
> > +    xcb_gcontext_t gc = xcb_generate_id(conn);
> > +    uint32_t mask = XCB_GC_FUNCTION | XCB_GC_SUBWINDOW_MODE;
> > +    uint32_t values[] = {XCB_GX_INVERT,
> > +                         XCB_SUBWINDOW_MODE_INCLUDE_INFERIORS};
> > +    xcb_create_gc(conn, gc, root_window, mask, values);
> > +
> > +    cursor_font = xcb_generate_id(conn);
> > +    xcb_open_font(conn, cursor_font, strlen("cursor"), "cursor");
> > +    cursor = xcb_generate_id(conn);
> > +    xcb_create_glyph_cursor(conn, cursor, cursor_font, cursor_font,
> > +                            CROSSHAIR_CURSOR, CROSSHAIR_CURSOR + 1, 0, 0, 0,
> > +                            0xFFFF, 0xFFFF, 0xFFFF);
> > +    cookie = xcb_grab_pointer(conn, 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);
> > +    reply = xcb_grab_pointer_reply(conn, cookie, NULL);
> > +    if (!reply || reply->status != XCB_GRAB_STATUS_SUCCESS) {
> > +        av_log(s, AV_LOG_ERROR,
> > +               "Failed to select region. Could not grab pointer.\n");
> > +        ret = AVERROR(EIO);
> > +        free(reply);
> > +        goto fail;
> > +    }
> > +    free(reply);
> > +
> > +    xcb_grab_server(conn);
> > +
> > +    while (!done && (event = xcb_wait_for_event(conn))) {
> > +        switch (event->response_type & ~0x80) {
> > +        case XCB_BUTTON_PRESS: {
> > +            xcb_button_press_event_t *press = (xcb_button_press_event_t *)event;
> > +            press_position = (xcb_point_t){press->event_x, press->event_y};
> > +            rectangle.x = press_position.x;
> > +            rectangle.y = press_position.y;
> > +            xcb_poly_rectangle(conn, root_window, gc, 1, &rectangle);
> > +            was_pressed = 1;
> > +            break;
> > +        }
> > +        case XCB_MOTION_NOTIFY: {
> > +            if (was_pressed) {
> > +                xcb_motion_notify_event_t *motion =
> > +                    (xcb_motion_notify_event_t *)event;
> > +                xcb_point_t cursor_position = {motion->event_x,
> > +                                               motion->event_y};
> > +                xcb_poly_rectangle(conn, root_window, gc, 1, &rectangle);
> > +                rectangle = rectangle_from_corners(
> > +                    &press_position, &cursor_position);
> > +                xcb_poly_rectangle(conn, root_window, gc, 1, &rectangle);
> > +            }
> > +            break;
> > +        }
> > +        case XCB_BUTTON_RELEASE: {
> > +            xcb_poly_rectangle(conn, root_window, gc, 1, &rectangle);
> > +            done = 1;
> > +            break;
> > +        }
> > +        default:
> > +            break;
> > +        }
> > +        xcb_flush(conn);
> > +        free(event);
> > +    }
> > +    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;
> > +    }
> > +    xcb_ungrab_server(conn);
> > +    xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
> > +    xcb_flush(conn);
> > +
> > +fail:
> > +    xcb_free_cursor(conn, cursor);
> > +    xcb_close_font(conn, cursor_font);
> > +    xcb_free_gc(conn, gc);
> > +    return ret;
> > +}
> > +
> >  static av_cold int xcbgrab_read_header(AVFormatContext *s)
> >  {
> >      XCBGrabContext *c = s->priv_data;
> > @@ -702,6 +821,14 @@ static av_cold int xcbgrab_read_header(AVFormatContext *s)
> >          return AVERROR(EIO);
> >      }
> >  
> > +    if (c->select_region) {
> > +        ret = select_region(s);
> > +        if (ret < 0) {
> > +            xcbgrab_read_close(s);
> > +            return ret;
> > +        }
> > +    }
> > +
> >      ret = create_stream(s);
> >  
> >      if (ret < 0) {

> 
> Any more comments on this patch?
> 

Will apply this patch on Friday if no one objects.
Andriy Gelman Nov. 7, 2020, 4:44 a.m. UTC | #3
On Tue, 03. Nov 00:25, Andriy Gelman wrote:
> On Thu, 08. Oct 23:40, Andriy Gelman wrote:
> > On Sat, 11. Jul 11:29, 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@OmarEmara.dev>
> > > ---
> > >  doc/indevs.texi       |   8 +++
> > >  libavdevice/xcbgrab.c | 127 ++++++++++++++++++++++++++++++++++++++++++
> > >  2 files changed, 135 insertions(+)
> > > 
> > > diff --git a/doc/indevs.texi b/doc/indevs.texi
> > > index 6f5afaf344..90ccc917aa 100644
> > > --- a/doc/indevs.texi
> > > +++ b/doc/indevs.texi
> > > @@ -1478,6 +1478,14 @@ ffmpeg -f x11grab -framerate 25 -video_size cif -i :0.0+10,20 out.mpg
> > >  @subsection Options
> > >  
> > >  @table @option
> > > +@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. A region with zero width or height will also 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..bcd91e54af 100644
> > > --- a/libavdevice/xcbgrab.c
> > > +++ b/libavdevice/xcbgrab.c
> > > @@ -22,6 +22,7 @@
> > >  #include "config.h"
> > >  
> > >  #include <stdlib.h>
> > > +#include <string.h>
> > >  #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_BOOL, { .i64 = 0 }, 0, 1, D },
> > >      { NULL },
> > >  };
> > >  
> > > @@ -668,6 +671,122 @@ static void setup_window(AVFormatContext *s)
> > >      draw_rectangle(s);
> > >  }
> > >  
> > > +#define CROSSHAIR_CURSOR 34
> > > +
> > > +static xcb_rectangle_t rectangle_from_corners(xcb_point_t *corner_a,
> > > +                                              xcb_point_t *corner_b)
> > > +{
> > > +    xcb_rectangle_t rectangle;
> > > +    rectangle.x = FFMIN(corner_a->x, corner_b->x);
> > > +    rectangle.y = FFMIN(corner_a->y, corner_b->y);
> > > +    rectangle.width = FFABS(corner_a->x - corner_b->x);
> > > +    rectangle.height = FFABS(corner_a->y - corner_b->y);
> > > +    return rectangle;
> > > +}
> > > +
> > > +static int select_region(AVFormatContext *s)
> > > +{
> > > +    XCBGrabContext *c = s->priv_data;
> > > +    xcb_connection_t *conn = c->conn;
> > > +    xcb_screen_t *screen = c->screen;
> > > +
> > > +    int ret = 0;
> > > +    int done = 0;
> > > +    int was_pressed = 0;
> > > +    xcb_cursor_t cursor;
> > > +    xcb_font_t cursor_font;
> > > +    xcb_point_t press_position;
> > > +    xcb_generic_event_t *event;
> > > +    xcb_rectangle_t rectangle = {0};
> > > +    xcb_grab_pointer_reply_t *reply;
> > > +    xcb_grab_pointer_cookie_t cookie;
> > > +
> > > +    xcb_window_t root_window = screen->root;
> > > +    xcb_gcontext_t gc = xcb_generate_id(conn);
> > > +    uint32_t mask = XCB_GC_FUNCTION | XCB_GC_SUBWINDOW_MODE;
> > > +    uint32_t values[] = {XCB_GX_INVERT,
> > > +                         XCB_SUBWINDOW_MODE_INCLUDE_INFERIORS};
> > > +    xcb_create_gc(conn, gc, root_window, mask, values);
> > > +
> > > +    cursor_font = xcb_generate_id(conn);
> > > +    xcb_open_font(conn, cursor_font, strlen("cursor"), "cursor");
> > > +    cursor = xcb_generate_id(conn);
> > > +    xcb_create_glyph_cursor(conn, cursor, cursor_font, cursor_font,
> > > +                            CROSSHAIR_CURSOR, CROSSHAIR_CURSOR + 1, 0, 0, 0,
> > > +                            0xFFFF, 0xFFFF, 0xFFFF);
> > > +    cookie = xcb_grab_pointer(conn, 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);
> > > +    reply = xcb_grab_pointer_reply(conn, cookie, NULL);
> > > +    if (!reply || reply->status != XCB_GRAB_STATUS_SUCCESS) {
> > > +        av_log(s, AV_LOG_ERROR,
> > > +               "Failed to select region. Could not grab pointer.\n");
> > > +        ret = AVERROR(EIO);
> > > +        free(reply);
> > > +        goto fail;
> > > +    }
> > > +    free(reply);
> > > +
> > > +    xcb_grab_server(conn);
> > > +
> > > +    while (!done && (event = xcb_wait_for_event(conn))) {
> > > +        switch (event->response_type & ~0x80) {
> > > +        case XCB_BUTTON_PRESS: {
> > > +            xcb_button_press_event_t *press = (xcb_button_press_event_t *)event;
> > > +            press_position = (xcb_point_t){press->event_x, press->event_y};
> > > +            rectangle.x = press_position.x;
> > > +            rectangle.y = press_position.y;
> > > +            xcb_poly_rectangle(conn, root_window, gc, 1, &rectangle);
> > > +            was_pressed = 1;
> > > +            break;
> > > +        }
> > > +        case XCB_MOTION_NOTIFY: {
> > > +            if (was_pressed) {
> > > +                xcb_motion_notify_event_t *motion =
> > > +                    (xcb_motion_notify_event_t *)event;
> > > +                xcb_point_t cursor_position = {motion->event_x,
> > > +                                               motion->event_y};
> > > +                xcb_poly_rectangle(conn, root_window, gc, 1, &rectangle);
> > > +                rectangle = rectangle_from_corners(
> > > +                    &press_position, &cursor_position);
> > > +                xcb_poly_rectangle(conn, root_window, gc, 1, &rectangle);
> > > +            }
> > > +            break;
> > > +        }
> > > +        case XCB_BUTTON_RELEASE: {
> > > +            xcb_poly_rectangle(conn, root_window, gc, 1, &rectangle);
> > > +            done = 1;
> > > +            break;
> > > +        }
> > > +        default:
> > > +            break;
> > > +        }
> > > +        xcb_flush(conn);
> > > +        free(event);
> > > +    }
> > > +    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;
> > > +    }
> > > +    xcb_ungrab_server(conn);
> > > +    xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
> > > +    xcb_flush(conn);
> > > +
> > > +fail:
> > > +    xcb_free_cursor(conn, cursor);
> > > +    xcb_close_font(conn, cursor_font);
> > > +    xcb_free_gc(conn, gc);
> > > +    return ret;
> > > +}
> > > +
> > >  static av_cold int xcbgrab_read_header(AVFormatContext *s)
> > >  {
> > >      XCBGrabContext *c = s->priv_data;
> > > @@ -702,6 +821,14 @@ static av_cold int xcbgrab_read_header(AVFormatContext *s)
> > >          return AVERROR(EIO);
> > >      }
> > >  
> > > +    if (c->select_region) {
> > > +        ret = select_region(s);
> > > +        if (ret < 0) {
> > > +            xcbgrab_read_close(s);
> > > +            return ret;
> > > +        }
> > > +    }
> > > +
> > >      ret = create_stream(s);
> > >  
> > >      if (ret < 0) {
> 
> > 
> > Any more comments on this patch?
> > 
> 
> Will apply this patch on Friday if no one objects.
> 

Applied.

Thanks,
diff mbox series

Patch

diff --git a/doc/indevs.texi b/doc/indevs.texi
index 6f5afaf344..90ccc917aa 100644
--- a/doc/indevs.texi
+++ b/doc/indevs.texi
@@ -1478,6 +1478,14 @@  ffmpeg -f x11grab -framerate 25 -video_size cif -i :0.0+10,20 out.mpg
 @subsection Options
 
 @table @option
+@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. A region with zero width or height will also 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..bcd91e54af 100644
--- a/libavdevice/xcbgrab.c
+++ b/libavdevice/xcbgrab.c
@@ -22,6 +22,7 @@ 
 #include "config.h"
 
 #include <stdlib.h>
+#include <string.h>
 #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_BOOL, { .i64 = 0 }, 0, 1, D },
     { NULL },
 };
 
@@ -668,6 +671,122 @@  static void setup_window(AVFormatContext *s)
     draw_rectangle(s);
 }
 
+#define CROSSHAIR_CURSOR 34
+
+static xcb_rectangle_t rectangle_from_corners(xcb_point_t *corner_a,
+                                              xcb_point_t *corner_b)
+{
+    xcb_rectangle_t rectangle;
+    rectangle.x = FFMIN(corner_a->x, corner_b->x);
+    rectangle.y = FFMIN(corner_a->y, corner_b->y);
+    rectangle.width = FFABS(corner_a->x - corner_b->x);
+    rectangle.height = FFABS(corner_a->y - corner_b->y);
+    return rectangle;
+}
+
+static int select_region(AVFormatContext *s)
+{
+    XCBGrabContext *c = s->priv_data;
+    xcb_connection_t *conn = c->conn;
+    xcb_screen_t *screen = c->screen;
+
+    int ret = 0;
+    int done = 0;
+    int was_pressed = 0;
+    xcb_cursor_t cursor;
+    xcb_font_t cursor_font;
+    xcb_point_t press_position;
+    xcb_generic_event_t *event;
+    xcb_rectangle_t rectangle = {0};
+    xcb_grab_pointer_reply_t *reply;
+    xcb_grab_pointer_cookie_t cookie;
+
+    xcb_window_t root_window = screen->root;
+    xcb_gcontext_t gc = xcb_generate_id(conn);
+    uint32_t mask = XCB_GC_FUNCTION | XCB_GC_SUBWINDOW_MODE;
+    uint32_t values[] = {XCB_GX_INVERT,
+                         XCB_SUBWINDOW_MODE_INCLUDE_INFERIORS};
+    xcb_create_gc(conn, gc, root_window, mask, values);
+
+    cursor_font = xcb_generate_id(conn);
+    xcb_open_font(conn, cursor_font, strlen("cursor"), "cursor");
+    cursor = xcb_generate_id(conn);
+    xcb_create_glyph_cursor(conn, cursor, cursor_font, cursor_font,
+                            CROSSHAIR_CURSOR, CROSSHAIR_CURSOR + 1, 0, 0, 0,
+                            0xFFFF, 0xFFFF, 0xFFFF);
+    cookie = xcb_grab_pointer(conn, 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);
+    reply = xcb_grab_pointer_reply(conn, cookie, NULL);
+    if (!reply || reply->status != XCB_GRAB_STATUS_SUCCESS) {
+        av_log(s, AV_LOG_ERROR,
+               "Failed to select region. Could not grab pointer.\n");
+        ret = AVERROR(EIO);
+        free(reply);
+        goto fail;
+    }
+    free(reply);
+
+    xcb_grab_server(conn);
+
+    while (!done && (event = xcb_wait_for_event(conn))) {
+        switch (event->response_type & ~0x80) {
+        case XCB_BUTTON_PRESS: {
+            xcb_button_press_event_t *press = (xcb_button_press_event_t *)event;
+            press_position = (xcb_point_t){press->event_x, press->event_y};
+            rectangle.x = press_position.x;
+            rectangle.y = press_position.y;
+            xcb_poly_rectangle(conn, root_window, gc, 1, &rectangle);
+            was_pressed = 1;
+            break;
+        }
+        case XCB_MOTION_NOTIFY: {
+            if (was_pressed) {
+                xcb_motion_notify_event_t *motion =
+                    (xcb_motion_notify_event_t *)event;
+                xcb_point_t cursor_position = {motion->event_x,
+                                               motion->event_y};
+                xcb_poly_rectangle(conn, root_window, gc, 1, &rectangle);
+                rectangle = rectangle_from_corners(
+                    &press_position, &cursor_position);
+                xcb_poly_rectangle(conn, root_window, gc, 1, &rectangle);
+            }
+            break;
+        }
+        case XCB_BUTTON_RELEASE: {
+            xcb_poly_rectangle(conn, root_window, gc, 1, &rectangle);
+            done = 1;
+            break;
+        }
+        default:
+            break;
+        }
+        xcb_flush(conn);
+        free(event);
+    }
+    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;
+    }
+    xcb_ungrab_server(conn);
+    xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
+    xcb_flush(conn);
+
+fail:
+    xcb_free_cursor(conn, cursor);
+    xcb_close_font(conn, cursor_font);
+    xcb_free_gc(conn, gc);
+    return ret;
+}
+
 static av_cold int xcbgrab_read_header(AVFormatContext *s)
 {
     XCBGrabContext *c = s->priv_data;
@@ -702,6 +821,14 @@  static av_cold int xcbgrab_read_header(AVFormatContext *s)
         return AVERROR(EIO);
     }
 
+    if (c->select_region) {
+        ret = select_region(s);
+        if (ret < 0) {
+            xcbgrab_read_close(s);
+            return ret;
+        }
+    }
+
     ret = create_stream(s);
 
     if (ret < 0) {