From patchwork Sun Jun 28 11:26:15 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Omar Emara X-Patchwork-Id: 20659 Return-Path: X-Original-To: patchwork@ffaux-bg.ffmpeg.org Delivered-To: patchwork@ffaux-bg.ffmpeg.org Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org [79.124.17.100]) by ffaux.localdomain (Postfix) with ESMTP id 335DC44838F for ; Sun, 28 Jun 2020 14:23:24 +0300 (EEST) Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 02C9F68B67B; Sun, 28 Jun 2020 14:23:24 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from relay6-d.mail.gandi.net (relay6-d.mail.gandi.net [217.70.183.198]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 10BA768B5FE for ; Sun, 28 Jun 2020 14:23:16 +0300 (EEST) X-Originating-IP: 197.57.37.239 Received: from localhost.localdomain (unknown [197.57.37.239]) (Authenticated sender: mail@omaremara.dev) by relay6-d.mail.gandi.net (Postfix) with ESMTPSA id 2693DC0003; Sun, 28 Jun 2020 11:23:14 +0000 (UTC) From: Omar Emara To: ffmpeg-devel@ffmpeg.org Date: Sun, 28 Jun 2020 13:26:15 +0200 Message-Id: <20200628112615.18212-1-mail@OmarEmara.dev> X-Mailer: git-send-email 2.27.0 MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH v2] avdevice/xcbgrab: Add select_region option. X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Cc: Omar Emara Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" 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 --- 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 +@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 +#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_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) {