[FFmpeg-devel] [PATCH v2] avdevice/xcbgrab: Add select_region option.
Omar Emara
mail at OmarEmara.dev
Sun Jun 28 14:26:15 EEST 2020
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>
#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;
+
+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;
+ }
+ 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;
+
+ 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};
+ 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;
+ 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};
+ xcb_rectangle_t rectangle = rectangle_from_corners(
+ &press_position, ¤t_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
More information about the ffmpeg-devel
mailing list