]> git.sesse.net Git - ffmpeg/blobdiff - libavdevice/xcbgrab.c
avdevice: Constify all devices
[ffmpeg] / libavdevice / xcbgrab.c
index 6d142abd4fd3a94c535f0d3ae96ba54d4a1a70b3..8164101c5e55d89e048d0eced6d05db520b561b9 100644 (file)
@@ -22,6 +22,7 @@
 #include "config.h"
 
 #include <stdlib.h>
+#include <string.h>
 #include <xcb/xcb.h>
 
 #if CONFIG_LIBXCB_XFIXES
 typedef struct XCBGrabContext {
     const AVClass *class;
 
-    uint8_t *buffer;
-
     xcb_connection_t *conn;
     xcb_screen_t *screen;
     xcb_window_t window;
 #if CONFIG_LIBXCB_SHM
-    xcb_shm_seg_t segment;
+    AVBufferPool *shm_pool;
 #endif
     int64_t time_frame;
     AVRational time_base;
+    int64_t frame_duration;
 
+    xcb_window_t window_id;
     int x, y;
     int width, height;
     int frame_size;
@@ -70,8 +71,8 @@ typedef struct XCBGrabContext {
     int show_region;
     int region_border;
     int centered;
+    int select_region;
 
-    const char *video_size;
     const char *framerate;
 
     int has_shm;
@@ -82,11 +83,12 @@ 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 },
     { "grab_y", "Initial y coordinate.", OFFSET(y), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, D },
-    { "video_size", "A string describing frame size, such as 640x480 or hd720.", OFFSET(video_size), AV_OPT_TYPE_STRING, {.str = "vga" }, 0, 0, D },
+    { "video_size", "A string describing frame size, such as 640x480 or hd720.", OFFSET(width), AV_OPT_TYPE_IMAGE_SIZE, {.str = NULL }, 0, 0, D },
     { "framerate", "", OFFSET(framerate), AV_OPT_TYPE_STRING, {.str = "ntsc" }, 0, 0, D },
     { "draw_mouse", "Draw the mouse pointer.", OFFSET(draw_mouse), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, 1, D },
     { "follow_mouse", "Move the grabbing region when the mouse pointer reaches within specified amount of pixels to the edge of region.",
@@ -94,6 +96,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 },
 };
 
@@ -127,7 +130,7 @@ static int xcbgrab_reposition(AVFormatContext *s,
         int left   = x + f;
         int right  = x + w - f;
         int top    = y + f;
-        int bottom = y + h + f;
+        int bottom = y + h - f;
         if (p_x > right) {
             x += p_x - right;
         } else if (p_x < left) {
@@ -146,15 +149,20 @@ static int xcbgrab_reposition(AVFormatContext *s,
     return 0;
 }
 
+static void xcbgrab_image_reply_free(void *opaque, uint8_t *data)
+{
+    free(opaque);
+}
+
 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, ret;
+    int length;
 
     iq  = xcb_get_image(c->conn, XCB_IMAGE_FORMAT_Z_PIXMAP, drawable,
                         c->x, c->y, c->width, c->height, ~0);
@@ -168,6 +176,7 @@ static int xcbgrab_frame(AVFormatContext *s, AVPacket *pkt)
                "sequence:%u resource_id:%u minor_code:%u major_code:%u.\n",
                e->response_type, e->error_code,
                e->sequence, e->resource_id, e->minor_code, e->major_code);
+        free(e);
         return AVERROR(EACCES);
     }
 
@@ -177,33 +186,34 @@ static int xcbgrab_frame(AVFormatContext *s, AVPacket *pkt)
     data   = xcb_get_image_data(img);
     length = xcb_get_image_data_length(img);
 
-    ret = av_new_packet(pkt, length);
-
-    if (!ret)
-        memcpy(pkt->data, data, length);
+    pkt->buf = av_buffer_create(data, length, xcbgrab_image_reply_free, img, 0);
+    if (!pkt->buf) {
+        free(img);
+        return AVERROR(ENOMEM);
+    }
 
-    free(img);
+    pkt->data = data;
+    pkt->size = length;
 
-    return ret;
+    return 0;
 }
 
-static void wait_frame(AVFormatContext *s, AVPacket *pkt)
+static int64_t wait_frame(AVFormatContext *s, AVPacket *pkt)
 {
     XCBGrabContext *c = s->priv_data;
     int64_t curtime, delay;
-    int64_t frame_time = av_rescale_q(1, c->time_base, AV_TIME_BASE_Q);
 
-    c->time_frame += frame_time;
+    c->time_frame += c->frame_duration;
 
     for (;;) {
-        curtime = av_gettime();
+        curtime = av_gettime_relative();
         delay   = c->time_frame - curtime;
         if (delay <= 0)
             break;
         av_usleep(delay);
     }
 
-    pkt->pts = curtime;
+    return curtime;
 }
 
 #if CONFIG_LIBXCB_SHM
@@ -221,31 +231,35 @@ static int check_shm(xcb_connection_t *conn)
     return 0;
 }
 
-static int allocate_shm(AVFormatContext *s)
+static void free_shm_buffer(void *opaque, uint8_t *data)
 {
-    XCBGrabContext *c = s->priv_data;
-    int size = c->frame_size + AV_INPUT_BUFFER_PADDING_SIZE;
+    shmdt(data);
+}
+
+static AVBufferRef *allocate_shm_buffer(void *opaque, size_t size)
+{
+    xcb_connection_t *conn = opaque;
+    xcb_shm_seg_t segment;
+    AVBufferRef *ref;
     uint8_t *data;
     int id;
 
-    if (c->buffer)
-        return 0;
     id = shmget(IPC_PRIVATE, size, IPC_CREAT | 0777);
-    if (id == -1) {
-        char errbuf[1024];
-        int err = AVERROR(errno);
-        av_strerror(err, errbuf, sizeof(errbuf));
-        av_log(s, AV_LOG_ERROR, "Cannot get %d bytes of shared memory: %s.\n",
-               size, errbuf);
-        return err;
-    }
-    xcb_shm_attach(c->conn, c->segment, id, 0);
+    if (id == -1)
+        return NULL;
+
+    segment = xcb_generate_id(conn);
+    xcb_shm_attach(conn, segment, id, 0);
     data = shmat(id, NULL, 0);
     shmctl(id, IPC_RMID, 0);
     if ((intptr_t)data == -1 || !data)
-        return AVERROR(errno);
-    c->buffer = data;
-    return 0;
+        return NULL;
+
+    ref = av_buffer_create(data, size, free_shm_buffer, (void *)(ptrdiff_t)segment, 0);
+    if (!ref)
+        shmdt(data);
+
+    return ref;
 }
 
 static int xcbgrab_frame_shm(AVFormatContext *s, AVPacket *pkt)
@@ -253,17 +267,21 @@ 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;
-    int ret;
+    AVBufferRef *buf;
+    xcb_shm_seg_t segment;
 
-    ret = allocate_shm(s);
-    if (ret < 0)
-        return ret;
+    buf = av_buffer_pool_get(c->shm_pool);
+    if (!buf) {
+        av_log(s, AV_LOG_ERROR, "Could not get shared memory buffer.\n");
+        return AVERROR(ENOMEM);
+    }
+    segment = (xcb_shm_seg_t)av_buffer_pool_buffer_get_opaque(buf);
 
     iq = xcb_shm_get_image(c->conn, drawable,
                            c->x, c->y, c->width, c->height, ~0,
-                           XCB_IMAGE_FORMAT_Z_PIXMAP, c->segment, 0);
+                           XCB_IMAGE_FORMAT_Z_PIXMAP, segment, 0);
     img = xcb_shm_get_image_reply(c->conn, iq, &e);
 
     xcb_flush(c->conn);
@@ -276,12 +294,15 @@ static int xcbgrab_frame_shm(AVFormatContext *s, AVPacket *pkt)
                e->response_type, e->error_code,
                e->sequence, e->resource_id, e->minor_code, e->major_code);
 
+        free(e);
+        av_buffer_unref(&buf);
         return AVERROR(EACCES);
     }
 
     free(img);
 
-    pkt->data = c->buffer;
+    pkt->buf = buf;
+    pkt->data = buf->data;
     pkt->size = c->frame_size;
 
     return 0;
@@ -310,7 +331,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;
@@ -332,17 +354,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;
@@ -377,11 +399,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,
@@ -394,35 +416,66 @@ 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");
+            return AVERROR_EXTERNAL;
+        }
         geo = xcb_get_geometry_reply(c->conn, gc, NULL);
+        if (!geo) {
+            av_log(s, AV_LOG_ERROR, "Failed to get xcb geometry\n");
+            free(p);
+            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)
+    if (c->has_shm && xcbgrab_frame_shm(s, pkt) < 0) {
+        av_log(s, AV_LOG_WARNING, "Continuing without shared memory.\n");
         c->has_shm = 0;
+    }
 #endif
     if (!c->has_shm)
         ret = xcbgrab_frame(s, pkt);
+    pkt->dts = pkt->pts = pts;
+    pkt->duration = c->frame_duration;
 
 #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);
@@ -436,9 +489,7 @@ static av_cold int xcbgrab_read_close(AVFormatContext *s)
     XCBGrabContext *ctx = s->priv_data;
 
 #if CONFIG_LIBXCB_SHM
-    if (ctx->buffer) {
-        shmdt(ctx->buffer);
-    }
+    av_buffer_pool_uninit(&ctx->shm_pool);
 #endif
 
     xcb_disconnect(ctx->conn);
@@ -464,7 +515,7 @@ static xcb_screen_t *get_screen(const xcb_setup_t *setup, int screen_num)
 }
 
 static int pixfmt_from_pixmap_format(AVFormatContext *s, int depth,
-                                     int *pix_fmt)
+                                     int *pix_fmt, int *bpp)
 {
     XCBGrabContext *c        = s->priv_data;
     const xcb_setup_t *setup = xcb_get_setup(c->conn);
@@ -478,21 +529,26 @@ static int pixfmt_from_pixmap_format(AVFormatContext *s, int depth,
             switch (depth) {
             case 32:
                 if (fmt->bits_per_pixel == 32)
-                    *pix_fmt = AV_PIX_FMT_0RGB;
+                    *pix_fmt = setup->image_byte_order == XCB_IMAGE_ORDER_LSB_FIRST ?
+                               AV_PIX_FMT_BGR0 : AV_PIX_FMT_0RGB;
                 break;
             case 24:
                 if (fmt->bits_per_pixel == 32)
-                    *pix_fmt = AV_PIX_FMT_0RGB32;
+                    *pix_fmt = setup->image_byte_order == XCB_IMAGE_ORDER_LSB_FIRST ?
+                               AV_PIX_FMT_BGR0 : AV_PIX_FMT_0RGB;
                 else if (fmt->bits_per_pixel == 24)
-                    *pix_fmt = AV_PIX_FMT_RGB24;
+                    *pix_fmt = setup->image_byte_order == XCB_IMAGE_ORDER_LSB_FIRST ?
+                               AV_PIX_FMT_BGR24 : AV_PIX_FMT_RGB24;
                 break;
             case 16:
                 if (fmt->bits_per_pixel == 16)
-                    *pix_fmt = AV_PIX_FMT_RGB565;
+                    *pix_fmt = setup->image_byte_order == XCB_IMAGE_ORDER_LSB_FIRST ?
+                               AV_PIX_FMT_RGB565LE : AV_PIX_FMT_RGB565BE;
                 break;
             case 15:
                 if (fmt->bits_per_pixel == 16)
-                    *pix_fmt = AV_PIX_FMT_RGB555;
+                    *pix_fmt = setup->image_byte_order == XCB_IMAGE_ORDER_LSB_FIRST ?
+                               AV_PIX_FMT_RGB555LE : AV_PIX_FMT_RGB555BE;
                 break;
             case 8:
                 if (fmt->bits_per_pixel == 8)
@@ -502,8 +558,7 @@ static int pixfmt_from_pixmap_format(AVFormatContext *s, int depth,
         }
 
         if (*pix_fmt) {
-            c->bpp        = fmt->bits_per_pixel;
-            c->frame_size = c->width * c->height * fmt->bits_per_pixel / 8;
+            *bpp        = fmt->bits_per_pixel;
             return 0;
         }
 
@@ -520,23 +575,29 @@ static int create_stream(AVFormatContext *s)
     AVStream *st      = avformat_new_stream(s, NULL);
     xcb_get_geometry_cookie_t gc;
     xcb_get_geometry_reply_t *geo;
+    int64_t frame_size_bits;
     int ret;
 
     if (!st)
         return AVERROR(ENOMEM);
 
-    ret = av_parse_video_size(&c->width, &c->height, c->video_size);
-    if (ret < 0)
-        return ret;
-
     ret = av_parse_video_rate(&st->avg_frame_rate, c->framerate);
     if (ret < 0)
         return ret;
 
     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) {
+        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;
+        c->height = geo->height;
+    }
 
     if (c->x + c->width > geo->width ||
         c->y + c->height > geo->height) {
@@ -546,21 +607,39 @@ static int create_stream(AVFormatContext *s)
                c->width, c->height,
                c->x, c->y,
                geo->width, geo->height);
+        free(geo);
         return AVERROR(EINVAL);
     }
 
     c->time_base  = (AVRational){ st->avg_frame_rate.den,
                                   st->avg_frame_rate.num };
-    c->time_frame = av_gettime();
+    c->frame_duration = av_rescale_q(1, c->time_base, AV_TIME_BASE_Q);
+    c->time_frame = av_gettime_relative();
+
+    ret = pixfmt_from_pixmap_format(s, geo->depth, &st->codecpar->format, &c->bpp);
+    free(geo);
+    if (ret < 0)
+        return ret;
+
+    frame_size_bits = (int64_t)c->width * c->height * c->bpp;
+    if (frame_size_bits / 8 + AV_INPUT_BUFFER_PADDING_SIZE > INT_MAX) {
+        av_log(s, AV_LOG_ERROR, "Captured area is too large\n");
+        return AVERROR_PATCHWELCOME;
+    }
+    c->frame_size = frame_size_bits / 8;
+
+#if CONFIG_LIBXCB_SHM
+    c->shm_pool = av_buffer_pool_init2(c->frame_size + AV_INPUT_BUFFER_PADDING_SIZE,
+                                           c->conn, allocate_shm_buffer, NULL);
+    if (!c->shm_pool)
+        return AVERROR(ENOMEM);
+#endif
 
     st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
     st->codecpar->codec_id   = AV_CODEC_ID_RAWVIDEO;
     st->codecpar->width      = c->width;
     st->codecpar->height     = c->height;
-
-    ret = pixfmt_from_pixmap_format(s, geo->depth, &st->codecpar->format);
-
-    free(geo);
+    st->codecpar->bit_rate   = av_rescale(frame_size_bits, st->avg_frame_rate.num, st->avg_frame_rate.den);
 
     return ret;
 }
@@ -624,6 +703,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;
@@ -658,6 +848,27 @@ 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) {
+            xcbgrab_read_close(s);
+            return ret;
+        }
+    }
+
     ret = create_stream(s);
 
     if (ret < 0) {
@@ -666,8 +877,7 @@ static av_cold int xcbgrab_read_header(AVFormatContext *s)
     }
 
 #if CONFIG_LIBXCB_SHM
-    if ((c->has_shm = check_shm(c->conn)))
-        c->segment = xcb_generate_id(c->conn);
+    c->has_shm = check_shm(c->conn);
 #endif
 
 #if CONFIG_LIBXCB_XFIXES
@@ -690,7 +900,7 @@ static av_cold int xcbgrab_read_header(AVFormatContext *s)
     return 0;
 }
 
-AVInputFormat ff_xcbgrab_demuxer = {
+const AVInputFormat ff_xcbgrab_demuxer = {
     .name           = "x11grab",
     .long_name      = NULL_IF_CONFIG_SMALL("X11 screen capture, using XCB"),
     .priv_data_size = sizeof(XCBGrabContext),