From 07de796b5d1f4db239bd986f553760b868a908f0 Mon Sep 17 00:00:00 2001 From: sgerwk Date: Wed, 10 Feb 2021 17:36:00 +0100 Subject: [PATCH] avdevice/xcbgrab: Add option for grabbing a window The option allows to select a specific window instead of the whole screen. Reviewed-by: Andriy Gelman Signed-off-by: Andriy Gelman --- doc/indevs.texi | 15 +++++++++- libavdevice/xcbgrab.c | 70 ++++++++++++++++++++++++++++++++----------- 2 files changed, 66 insertions(+), 19 deletions(-) diff --git a/doc/indevs.texi b/doc/indevs.texi index 3924d03908..b377924c2f 100644 --- a/doc/indevs.texi +++ b/doc/indevs.texi @@ -1564,8 +1564,21 @@ With @var{follow_mouse}: ffmpeg -f x11grab -follow_mouse centered -show_region 1 -framerate 25 -video_size cif -i :0.0 out.mpg @end example +@item window_id +Grab this window, instead of the whole screen. Default value is 0, which maps to +the whole screen (root window). + +The id of a window can be found using the @command{xwininfo} program, possibly with options -tree and +-root. + +If the window is later enlarged, the new area is not recorded. Video ends when +the window is closed, unmapped (i.e., iconified) or shrunk beyond the video +size (which defaults to the initial window size). + +This option disables options @option{follow_mouse} and @option{select_region}. + @item video_size -Set the video frame size. Default is the full desktop. +Set the video frame size. Default is the full desktop or window. @item grab_x @item grab_y diff --git a/libavdevice/xcbgrab.c b/libavdevice/xcbgrab.c index f9ea5a56b2..71f0902f8e 100644 --- a/libavdevice/xcbgrab.c +++ b/libavdevice/xcbgrab.c @@ -60,6 +60,7 @@ typedef struct XCBGrabContext { AVRational time_base; int64_t frame_duration; + xcb_window_t window_id; int x, y; int width, height; int frame_size; @@ -82,6 +83,7 @@ typedef struct XCBGrabContext { #define OFFSET(x) offsetof(XCBGrabContext, x) #define D AV_OPT_FLAG_DECODING_PARAM static const AVOption options[] = { + { "window_id", "Window to capture.", OFFSET(window_id), AV_OPT_TYPE_INT, { .i64 = XCB_NONE }, 0, UINT32_MAX, D }, { "x", "Initial x coordinate.", OFFSET(x), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, D }, { "y", "Initial y coordinate.", OFFSET(y), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, D }, { "grab_x", "Initial x coordinate.", OFFSET(x), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, D }, @@ -157,7 +159,7 @@ static int xcbgrab_frame(AVFormatContext *s, AVPacket *pkt) XCBGrabContext *c = s->priv_data; xcb_get_image_cookie_t iq; xcb_get_image_reply_t *img; - xcb_drawable_t drawable = c->screen->root; + xcb_drawable_t drawable = c->window_id; xcb_generic_error_t *e = NULL; uint8_t *data; int length; @@ -267,7 +269,7 @@ static int xcbgrab_frame_shm(AVFormatContext *s, AVPacket *pkt) XCBGrabContext *c = s->priv_data; xcb_shm_get_image_cookie_t iq; xcb_shm_get_image_reply_t *img; - xcb_drawable_t drawable = c->screen->root; + xcb_drawable_t drawable = c->window_id; xcb_generic_error_t *e = NULL; AVBufferRef *buf; xcb_shm_seg_t segment; @@ -333,7 +335,8 @@ static int check_xfixes(xcb_connection_t *conn) static void xcbgrab_draw_mouse(AVFormatContext *s, AVPacket *pkt, xcb_query_pointer_reply_t *p, - xcb_get_geometry_reply_t *geo) + xcb_get_geometry_reply_t *geo, + int win_x, int win_y) { XCBGrabContext *gr = s->priv_data; uint32_t *cursor; @@ -355,17 +358,17 @@ static void xcbgrab_draw_mouse(AVFormatContext *s, AVPacket *pkt, cx = ci->x - ci->xhot; cy = ci->y - ci->yhot; - x = FFMAX(cx, gr->x); - y = FFMAX(cy, gr->y); + x = FFMAX(cx, win_x + gr->x); + y = FFMAX(cy, win_y + gr->y); - w = FFMIN(cx + ci->width, gr->x + gr->width) - x; - h = FFMIN(cy + ci->height, gr->y + gr->height) - y; + w = FFMIN(cx + ci->width, win_x + gr->x + gr->width) - x; + h = FFMIN(cy + ci->height, win_y + gr->y + gr->height) - y; c_off = x - cx; - i_off = x - gr->x; + i_off = x - gr->x - win_x; cursor += (y - cy) * ci->width; - image += (y - gr->y) * gr->width * stride; + image += (y - gr->y - win_y) * gr->width * stride; for (y = 0; y < h; y++) { cursor += c_off; @@ -400,11 +403,11 @@ static void xcbgrab_draw_mouse(AVFormatContext *s, AVPacket *pkt, } #endif /* CONFIG_LIBXCB_XFIXES */ -static void xcbgrab_update_region(AVFormatContext *s) +static void xcbgrab_update_region(AVFormatContext *s, int win_x, int win_y) { XCBGrabContext *c = s->priv_data; - const uint32_t args[] = { c->x - c->region_border, - c->y - c->region_border }; + const uint32_t args[] = { win_x + c->x - c->region_border, + win_y + c->y - c->region_border }; xcb_configure_window(c->conn, c->window, @@ -417,17 +420,20 @@ static int xcbgrab_read_packet(AVFormatContext *s, AVPacket *pkt) XCBGrabContext *c = s->priv_data; xcb_query_pointer_cookie_t pc; xcb_get_geometry_cookie_t gc; + xcb_translate_coordinates_cookie_t tc; xcb_query_pointer_reply_t *p = NULL; xcb_get_geometry_reply_t *geo = NULL; + xcb_translate_coordinates_reply_t *translate = NULL; int ret = 0; int64_t pts; + int win_x = 0, win_y = 0; wait_frame(s, pkt); pts = av_gettime(); if (c->follow_mouse || c->draw_mouse) { - pc = xcb_query_pointer(c->conn, c->screen->root); - gc = xcb_get_geometry(c->conn, c->screen->root); + pc = xcb_query_pointer(c->conn, c->window_id); + gc = xcb_get_geometry(c->conn, c->window_id); p = xcb_query_pointer_reply(c->conn, pc, NULL); if (!p) { av_log(s, AV_LOG_ERROR, "Failed to query xcb pointer\n"); @@ -440,12 +446,25 @@ static int xcbgrab_read_packet(AVFormatContext *s, AVPacket *pkt) return AVERROR_EXTERNAL; } } + if (c->window_id != c->screen->root) { + tc = xcb_translate_coordinates(c->conn, c->window_id, c->screen->root, 0, 0); + translate = xcb_translate_coordinates_reply(c->conn, tc, NULL); + if (!translate) { + free(p); + free(geo); + av_log(s, AV_LOG_ERROR, "Failed to translate xcb geometry\n"); + return AVERROR_EXTERNAL; + } + win_x = translate->dst_x; + win_y = translate->dst_y; + free(translate); + } if (c->follow_mouse && p->same_screen) xcbgrab_reposition(s, p, geo); if (c->show_region) - xcbgrab_update_region(s); + xcbgrab_update_region(s, win_x, win_y); #if CONFIG_LIBXCB_SHM if (c->has_shm && xcbgrab_frame_shm(s, pkt) < 0) { @@ -460,7 +479,7 @@ static int xcbgrab_read_packet(AVFormatContext *s, AVPacket *pkt) #if CONFIG_LIBXCB_XFIXES if (ret >= 0 && c->draw_mouse && p->same_screen) - xcbgrab_draw_mouse(s, pkt, p, geo); + xcbgrab_draw_mouse(s, pkt, p, geo, win_x, win_y); #endif free(p); @@ -572,10 +591,12 @@ static int create_stream(AVFormatContext *s) avpriv_set_pts_info(st, 64, 1, 1000000); - gc = xcb_get_geometry(c->conn, c->screen->root); + gc = xcb_get_geometry(c->conn, c->window_id); geo = xcb_get_geometry_reply(c->conn, gc, NULL); - if (!geo) + if (!geo) { + av_log(s, AV_LOG_ERROR, "Can't find window '0x%x', aborting.\n", c->window_id); return AVERROR_EXTERNAL; + } if (!c->width || !c->height) { c->width = geo->width; @@ -831,6 +852,19 @@ static av_cold int xcbgrab_read_header(AVFormatContext *s) return AVERROR(EIO); } + if (c->window_id == XCB_NONE) + c->window_id = c->screen->root; + else { + if (c->select_region) { + av_log(s, AV_LOG_WARNING, "select_region ignored with window_id.\n"); + c->select_region = 0; + } + if (c->follow_mouse) { + av_log(s, AV_LOG_WARNING, "follow_mouse ignored with window_id.\n"); + c->follow_mouse = 0; + } + } + if (c->select_region) { ret = select_region(s); if (ret < 0) {