From 04b37b5d7fcd5ffd141054177611d5168629e4ac Mon Sep 17 00:00:00 2001 From: Omar Emara Date: Sat, 11 Jul 2020 11:29:09 +0200 Subject: avdevice/xcbgrab: Add select_region option 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. Reviewed-by: Andriy Gelman Signed-off-by: Omar Emara --- libavdevice/xcbgrab.c | 122 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) (limited to 'libavdevice') diff --git a/libavdevice/xcbgrab.c b/libavdevice/xcbgrab.c index 8ef2a30d02..95bdc8ab9d 100644 --- a/libavdevice/xcbgrab.c +++ b/libavdevice/xcbgrab.c @@ -22,6 +22,7 @@ #include "config.h" #include +#include #include #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 }, }; @@ -677,6 +680,117 @@ 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, done = 0, 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; @@ -711,6 +825,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) { -- cgit v1.2.3