x11grab.c 20.8 KB
Newer Older
1 2
/*
 * X11 video grab interface
3
 *
4
 * This file is part of FFmpeg.
5
 *
6 7 8
 * FFmpeg integration:
 * Copyright (C) 2006 Clemens Fruhwirth <clemens@endorphin.org>
 *                    Edouard Gomez <ed.gomez@free.fr>
9
 *
10
 * This file contains code from grab.c:
11
 * Copyright (c) 2000-2001 Fabrice Bellard
12 13
 *
 * This file contains code from the xvidcap project:
14 15
 * Copyright (C) 1997-1998 Rasca, Berlin
 *               2003-2004 Karl H. Beckers, Frankfurt
16
 *
17
 * FFmpeg is free software; you can redistribute it and/or modify
18 19 20 21
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
22
 * FFmpeg is distributed in the hope that it will be useful,
23 24 25 26
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
27 28 29
 * You should have received a copy of the GNU General Public License
 * along with FFmpeg; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
30
 */
31

32
/**
33
 * @file
34 35 36
 * X11 frame device demuxer
 * @author Clemens Fruhwirth <clemens@endorphin.org>
 * @author Edouard Gomez <ed.gomez@free.fr>
37 38
 */

39
#include "config.h"
40
#include "libavformat/internal.h"
41 42 43
#include "libavutil/log.h"
#include "libavutil/opt.h"
#include "libavutil/parseutils.h"
44
#include "libavutil/time.h"
45
#include <time.h>
46
#include <X11/cursorfont.h>
47 48 49 50
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xlibint.h>
#include <X11/Xproto.h>
51
#include <X11/Xutil.h>
52
#include <sys/shm.h>
53
#include <X11/extensions/shape.h>
54
#include <X11/extensions/XShm.h>
Roxis's avatar
Roxis committed
55
#include <X11/extensions/Xfixes.h>
56
#include "avdevice.h"
57

58 59 60
/**
 * X11 Device Demuxer context
 */
61
struct x11grab {
62
    const AVClass *class;    /**< Class for private options. */
63 64 65 66 67
    int frame_size;          /**< Size in bytes of a grabbed frame */
    AVRational time_base;    /**< Time base */
    int64_t time_frame;      /**< Current time */

    int width;               /**< Width of the grab frame */
68
    int height;              /**< Height of the grab frame */
69 70 71 72 73 74 75
    int x_off;               /**< Horizontal top-left corner coordinate */
    int y_off;               /**< Vertical top-left corner coordinate */

    Display *dpy;            /**< X11 display from which x11grab grabs frames */
    XImage *image;           /**< X11 image holding the grab */
    int use_shm;             /**< !0 when using XShm extension */
    XShmSegmentInfo shminfo; /**< When using XShm, keeps track of XShm infos */
76
    int  draw_mouse;         /**< Set by a private option. */
77
    int  follow_mouse;       /**< Set by a private option. */
78
    int  show_region;        /**< set by a private option. */
79
    char *framerate;         /**< Set by a private option. */
80 81

    Window region_win;       /**< This is used by show_region option. */
82
};
83

84 85 86 87
#define REGION_WIN_BORDER 3
/**
 * Draw grabbing region window
 *
88
 * @param s x11grab context
89 90
 */
static void
91
x11grab_draw_region_win(struct x11grab *s)
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
{
    Display *dpy = s->dpy;
    int screen;
    Window win = s->region_win;
    GC gc;

    screen = DefaultScreen(dpy);
    gc = XCreateGC(dpy, win, 0, 0);
    XSetForeground(dpy, gc, WhitePixel(dpy, screen));
    XSetBackground(dpy, gc, BlackPixel(dpy, screen));
    XSetLineAttributes(dpy, gc, REGION_WIN_BORDER, LineDoubleDash, 0, 0);
    XDrawRectangle(dpy, win, gc,
                   1, 1,
                   (s->width  + REGION_WIN_BORDER * 2) - 1 * 2 - 1,
                   (s->height + REGION_WIN_BORDER * 2) - 1 * 2 - 1);
    XFreeGC(dpy, gc);
}

/**
 * Initialize grabbing region window
 *
113
 * @param s x11grab context
114 115
 */
static void
116
x11grab_region_win_init(struct x11grab *s)
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
{
    Display *dpy = s->dpy;
    int screen;
    XSetWindowAttributes attribs;
    XRectangle rect;

    screen = DefaultScreen(dpy);
    attribs.override_redirect = True;
    s->region_win = XCreateWindow(dpy, RootWindow(dpy, screen),
                                  s->x_off  - REGION_WIN_BORDER,
                                  s->y_off  - REGION_WIN_BORDER,
                                  s->width  + REGION_WIN_BORDER * 2,
                                  s->height + REGION_WIN_BORDER * 2,
                                  0, CopyFromParent,
                                  InputOutput, CopyFromParent,
                                  CWOverrideRedirect, &attribs);
    rect.x = 0;
    rect.y = 0;
    rect.width  = s->width;
    rect.height = s->height;
    XShapeCombineRectangles(dpy, s->region_win,
                            ShapeBounding, REGION_WIN_BORDER, REGION_WIN_BORDER,
                            &rect, 1, ShapeSubtract, 0);
    XMapWindow(dpy, s->region_win);
    XSelectInput(dpy, s->region_win, ExposureMask | StructureNotifyMask);
    x11grab_draw_region_win(s);
}

145
/**
146
 * Initialize the x11 grab device demuxer (public device demuxer API).
147 148 149
 *
 * @param s1 Context from avformat core
 * @return <ul>
150
 *          <li>AVERROR(ENOMEM) no memory left</li>
151
 *          <li>AVERROR(EIO) other failure case</li>
152 153 154
 *          <li>0 success</li>
 *         </ul>
 */
155
static int
156
x11grab_read_header(AVFormatContext *s1)
157
{
158
    struct x11grab *x11grab = s1->priv_data;
159 160
    Display *dpy;
    AVStream *st = NULL;
161
    enum AVPixelFormat input_pixfmt;
162
    XImage *image;
163 164
    int x_off = 0;
    int y_off = 0;
165
    int screen;
166
    int use_shm;
167
    char *dpyname, *offset;
168
    int ret = 0;
169
    AVRational framerate;
170

171
    dpyname = av_strdup(s1->filename);
172
    if (!dpyname)
173 174
        goto out;

175
    offset = strchr(dpyname, '+');
176 177
    if (offset) {
        sscanf(offset, "%d,%d", &x_off, &y_off);
178 179 180 181 182 183
        if (strstr(offset, "nomouse")) {
            av_log(s1, AV_LOG_WARNING,
                   "'nomouse' specification in argument is deprecated: "
                   "use 'draw_mouse' option with value 0 instead\n");
            x11grab->draw_mouse = 0;
        }
184 185 186
        *offset= 0;
    }

187 188 189 190
    if ((ret = av_parse_video_rate(&framerate, x11grab->framerate)) < 0) {
        av_log(s1, AV_LOG_ERROR, "Could not parse framerate: %s.\n", x11grab->framerate);
        goto out;
    }
191
    av_log(s1, AV_LOG_INFO, "device: %s -> display: %s x: %d y: %d width: %d height: %d\n",
192
           s1->filename, dpyname, x_off, y_off, x11grab->width, x11grab->height);
193

194 195
    dpy = XOpenDisplay(dpyname);
    av_freep(&dpyname);
196 197
    if(!dpy) {
        av_log(s1, AV_LOG_ERROR, "Could not open X display.\n");
198 199
        ret = AVERROR(EIO);
        goto out;
200
    }
201

202
    st = avformat_new_stream(s1, NULL);
203
    if (!st) {
204 205
        ret = AVERROR(ENOMEM);
        goto out;
206
    }
207
    avpriv_set_pts_info(st, 64, 1, 1000000); /* 64 bits pts in us */
208

209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
    screen = DefaultScreen(dpy);

    if (x11grab->follow_mouse) {
        int screen_w, screen_h;
        Window w;

        screen_w = DisplayWidth(dpy, screen);
        screen_h = DisplayHeight(dpy, screen);
        XQueryPointer(dpy, RootWindow(dpy, screen), &w, &w, &x_off, &y_off, &ret, &ret, &ret);
        x_off -= x11grab->width / 2;
        y_off -= x11grab->height / 2;
        x_off = FFMIN(FFMAX(x_off, 0), screen_w - x11grab->width);
        y_off = FFMIN(FFMAX(y_off, 0), screen_h - x11grab->height);
        av_log(s1, AV_LOG_INFO, "followmouse is enabled, resetting grabbing region to x: %d y: %d\n", x_off, y_off);
    }

225
    use_shm = XShmQueryExtension(dpy);
226
    av_log(s1, AV_LOG_INFO, "shared memory extension%s found\n", use_shm ? "" : " not");
227 228 229 230 231 232 233 234 235

    if(use_shm) {
        int scr = XDefaultScreen(dpy);
        image = XShmCreateImage(dpy,
                                DefaultVisual(dpy, scr),
                                DefaultDepth(dpy, scr),
                                ZPixmap,
                                NULL,
                                &x11grab->shminfo,
236
                                x11grab->width, x11grab->height);
237 238 239 240 241
        x11grab->shminfo.shmid = shmget(IPC_PRIVATE,
                                        image->bytes_per_line * image->height,
                                        IPC_CREAT|0777);
        if (x11grab->shminfo.shmid == -1) {
            av_log(s1, AV_LOG_ERROR, "Fatal: Can't get shared memory!\n");
242 243
            ret = AVERROR(ENOMEM);
            goto out;
244 245 246 247 248 249 250
        }
        x11grab->shminfo.shmaddr = image->data = shmat(x11grab->shminfo.shmid, 0, 0);
        x11grab->shminfo.readOnly = False;

        if (!XShmAttach(dpy, &x11grab->shminfo)) {
            av_log(s1, AV_LOG_ERROR, "Fatal: Failed to attach shared memory!\n");
            /* needs some better error subroutine :) */
251 252
            ret = AVERROR(EIO);
            goto out;
253 254
        }
    } else {
255
        image = XGetImage(dpy, RootWindow(dpy, screen),
256
                          x_off,y_off,
257
                          x11grab->width, x11grab->height,
258 259 260 261 262
                          AllPlanes, ZPixmap);
    }

    switch (image->bits_per_pixel) {
    case 8:
263
        av_log (s1, AV_LOG_DEBUG, "8 bit palette\n");
264
        input_pixfmt = AV_PIX_FMT_PAL8;
265 266
        break;
    case 16:
267 268 269
        if (       image->red_mask   == 0xf800 &&
                   image->green_mask == 0x07e0 &&
                   image->blue_mask  == 0x001f ) {
270
            av_log (s1, AV_LOG_DEBUG, "16 bit RGB565\n");
271
            input_pixfmt = AV_PIX_FMT_RGB565;
272 273 274
        } else if (image->red_mask   == 0x7c00 &&
                   image->green_mask == 0x03e0 &&
                   image->blue_mask  == 0x001f ) {
275
            av_log(s1, AV_LOG_DEBUG, "16 bit RGB555\n");
276
            input_pixfmt = AV_PIX_FMT_RGB555;
277 278 279
        } else {
            av_log(s1, AV_LOG_ERROR, "RGB ordering at image depth %i not supported ... aborting\n", image->bits_per_pixel);
            av_log(s1, AV_LOG_ERROR, "color masks: r 0x%.6lx g 0x%.6lx b 0x%.6lx\n", image->red_mask, image->green_mask, image->blue_mask);
280 281
            ret = AVERROR(EIO);
            goto out;
282 283 284
        }
        break;
    case 24:
285 286 287
        if (        image->red_mask   == 0xff0000 &&
                    image->green_mask == 0x00ff00 &&
                    image->blue_mask  == 0x0000ff ) {
288
            input_pixfmt = AV_PIX_FMT_BGR24;
289 290 291
        } else if ( image->red_mask   == 0x0000ff &&
                    image->green_mask == 0x00ff00 &&
                    image->blue_mask  == 0xff0000 ) {
292
            input_pixfmt = AV_PIX_FMT_RGB24;
293 294 295
        } else {
            av_log(s1, AV_LOG_ERROR,"rgb ordering at image depth %i not supported ... aborting\n", image->bits_per_pixel);
            av_log(s1, AV_LOG_ERROR, "color masks: r 0x%.6lx g 0x%.6lx b 0x%.6lx\n", image->red_mask, image->green_mask, image->blue_mask);
296 297
            ret = AVERROR(EIO);
            goto out;
298 299 300
        }
        break;
    case 32:
301
        input_pixfmt = AV_PIX_FMT_0RGB32;
302 303 304
        break;
    default:
        av_log(s1, AV_LOG_ERROR, "image depth %i not supported ... aborting\n", image->bits_per_pixel);
305 306
        ret = AVERROR(EINVAL);
        goto out;
307 308
    }

309
    x11grab->frame_size = x11grab->width * x11grab->height * image->bits_per_pixel/8;
310
    x11grab->dpy = dpy;
311
    x11grab->time_base  = av_inv_q(framerate);
312
    x11grab->time_frame = av_gettime() / av_q2d(x11grab->time_base);
313 314 315 316 317
    x11grab->x_off = x_off;
    x11grab->y_off = y_off;
    x11grab->image = image;
    x11grab->use_shm = use_shm;

318
    st->codec->codec_type = AVMEDIA_TYPE_VIDEO;
319
    st->codec->codec_id = AV_CODEC_ID_RAWVIDEO;
320 321
    st->codec->width  = x11grab->width;
    st->codec->height = x11grab->height;
322
    st->codec->pix_fmt = input_pixfmt;
323 324
    st->codec->time_base = x11grab->time_base;
    st->codec->bit_rate = x11grab->frame_size * 1/av_q2d(x11grab->time_base) * 8;
325

326
out:
327
    av_free(dpyname);
328
    return ret;
329 330
}

331
/**
332
 * Paint a mouse pointer in an X11 image.
333
 *
Diego Biurrun's avatar
Diego Biurrun committed
334
 * @param image image to paint the mouse pointer to
335 336 337
 * @param s context used to retrieve original grabbing rectangle
 *          coordinates
 */
338
static void
339
paint_mouse_pointer(XImage *image, struct x11grab *s)
340
{
341 342 343 344
    int x_off = s->x_off;
    int y_off = s->y_off;
    int width = s->width;
    int height = s->height;
Roxis's avatar
Roxis committed
345 346 347 348 349
    Display *dpy = s->dpy;
    XFixesCursorImage *xcim;
    int x, y;
    int line, column;
    int to_line, to_column;
350 351 352 353 354 355
    int pixstride = image->bits_per_pixel >> 3;
    /* Warning: in its insanity, xlib provides unsigned image data through a
     * char* pointer, so we have to make it uint8_t to make things not break.
     * Anyone who performs further investigation of the xlib API likely risks
     * permanent brain damage. */
    uint8_t *pix = image->data;
356 357 358
    Cursor c;
    Window w;
    XSetWindowAttributes attr;
359 360 361 362

    /* Code doesn't currently support 16-bit or PAL8 */
    if (image->bits_per_pixel != 24 && image->bits_per_pixel != 32)
        return;
363

364 365
    c = XCreateFontCursor(dpy, XC_left_ptr);
    w = DefaultRootWindow(dpy);
366 367 368
    attr.cursor = c;
    XChangeWindowAttributes(dpy, w, CWCursor, &attr);

369
    xcim = XFixesGetCursorImage(dpy);
370

Roxis's avatar
Roxis committed
371 372 373 374 375 376 377 378
    x = xcim->x - xcim->xhot;
    y = xcim->y - xcim->yhot;

    to_line = FFMIN((y + xcim->height), (height + y_off));
    to_column = FFMIN((x + xcim->width), (width + x_off));

    for (line = FFMAX(y, y_off); line < to_line; line++) {
        for (column = FFMAX(x, x_off); column < to_column; column++) {
379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394
            int  xcim_addr = (line - y) * xcim->width + column - x;
            int image_addr = ((line - y_off) * width + column - x_off) * pixstride;
            int r = (uint8_t)(xcim->pixels[xcim_addr] >>  0);
            int g = (uint8_t)(xcim->pixels[xcim_addr] >>  8);
            int b = (uint8_t)(xcim->pixels[xcim_addr] >> 16);
            int a = (uint8_t)(xcim->pixels[xcim_addr] >> 24);

            if (a == 255) {
                pix[image_addr+0] = r;
                pix[image_addr+1] = g;
                pix[image_addr+2] = b;
            } else if (a) {
                /* pixel values from XFixesGetCursorImage come premultiplied by alpha */
                pix[image_addr+0] = r + (pix[image_addr+0]*(255-a) + 255/2) / 255;
                pix[image_addr+1] = g + (pix[image_addr+1]*(255-a) + 255/2) / 255;
                pix[image_addr+2] = b + (pix[image_addr+2]*(255-a) + 255/2) / 255;
395
            }
396 397
        }
    }
Roxis's avatar
Roxis committed
398 399 400

    XFree(xcim);
    xcim = NULL;
401 402 403
}


404
/**
405
 * Read new data in the image structure.
406 407
 *
 * @param dpy X11 display to grab from
408
 * @param d
409 410 411 412
 * @param image Image where the grab will be put
 * @param x Top-Left grabbing rectangle horizontal coordinate
 * @param y Top-Left grabbing rectangle vertical coordinate
 * @return 0 if error, !0 if successful
413 414
 */
static int
415
xget_zpixmap(Display *dpy, Drawable d, XImage *image, int x, int y)
416
{
417 418 419 420 421
    xGetImageReply rep;
    xGetImageReq *req;
    long nbytes;

    if (!image) {
422
        return 0;
423 424 425 426 427 428 429 430 431 432 433 434 435 436
    }

    LockDisplay(dpy);
    GetReq(GetImage, req);

    /* First set up the standard stuff in the request */
    req->drawable = d;
    req->x = x;
    req->y = y;
    req->width = image->width;
    req->height = image->height;
    req->planeMask = (unsigned int)AllPlanes;
    req->format = ZPixmap;

437
    if (!_XReply(dpy, (xReply *)&rep, 0, xFalse) || !rep.length) {
438 439
        UnlockDisplay(dpy);
        SyncHandle();
440
        return 0;
441 442 443 444 445 446 447
    }

    nbytes = (long)rep.length << 2;
    _XReadPad(dpy, image->data, nbytes);

    UnlockDisplay(dpy);
    SyncHandle();
448
    return 1;
449 450
}

451
/**
452
 * Grab a frame from x11 (public device demuxer API).
453 454 455 456 457
 *
 * @param s1 Context from avformat core
 * @param pkt Packet holding the brabbed frame
 * @return frame size in bytes
 */
458 459
static int
x11grab_read_packet(AVFormatContext *s1, AVPacket *pkt)
460
{
461
    struct x11grab *s = s1->priv_data;
462 463 464 465 466
    Display *dpy = s->dpy;
    XImage *image = s->image;
    int x_off = s->x_off;
    int y_off = s->y_off;

467 468 469 470
    int screen;
    Window root;
    int follow_mouse = s->follow_mouse;

471 472 473 474
    int64_t curtime, delay;
    struct timespec ts;

    /* Calculate the time of the next frame */
475
    s->time_frame += INT64_C(1000000);
476 477 478 479

    /* wait based on the frame rate */
    for(;;) {
        curtime = av_gettime();
480
        delay = s->time_frame * av_q2d(s->time_base) - curtime;
481
        if (delay <= 0) {
482 483
            if (delay < INT64_C(-1000000) * av_q2d(s->time_base)) {
                s->time_frame += INT64_C(1000000);
484 485 486 487 488 489 490 491
            }
            break;
        }
        ts.tv_sec = delay / 1000000;
        ts.tv_nsec = (delay % 1000000) * 1000;
        nanosleep(&ts, NULL);
    }

492 493 494
    av_init_packet(pkt);
    pkt->data = image->data;
    pkt->size = s->frame_size;
495
    pkt->pts = curtime;
496

497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525
    screen = DefaultScreen(dpy);
    root = RootWindow(dpy, screen);
    if (follow_mouse) {
        int screen_w, screen_h;
        int pointer_x, pointer_y, _;
        Window w;

        screen_w = DisplayWidth(dpy, screen);
        screen_h = DisplayHeight(dpy, screen);
        XQueryPointer(dpy, root, &w, &w, &pointer_x, &pointer_y, &_, &_, &_);
        if (follow_mouse == -1) {
            // follow the mouse, put it at center of grabbing region
            x_off += pointer_x - s->width  / 2 - x_off;
            y_off += pointer_y - s->height / 2 - y_off;
        } else {
            // follow the mouse, but only move the grabbing region when mouse
            // reaches within certain pixels to the edge.
            if (pointer_x > x_off + s->width - follow_mouse) {
                x_off += pointer_x - (x_off + s->width - follow_mouse);
            } else if (pointer_x < x_off + follow_mouse)
                x_off -= (x_off + follow_mouse) - pointer_x;
            if (pointer_y > y_off + s->height - follow_mouse) {
                y_off += pointer_y - (y_off + s->height - follow_mouse);
            } else if (pointer_y < y_off + follow_mouse)
                y_off -= (y_off + follow_mouse) - pointer_y;
        }
        // adjust grabbing region position if it goes out of screen.
        s->x_off = x_off = FFMIN(FFMAX(x_off, 0), screen_w - s->width);
        s->y_off = y_off = FFMIN(FFMAX(y_off, 0), screen_h - s->height);
526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542

        if (s->show_region && s->region_win)
            XMoveWindow(dpy, s->region_win,
                        s->x_off - REGION_WIN_BORDER,
                        s->y_off - REGION_WIN_BORDER);
    }

    if (s->show_region) {
        if (s->region_win) {
            XEvent evt;
            // clean up the events, and do the initinal draw or redraw.
            for (evt.type = NoEventMask; XCheckMaskEvent(dpy, ExposureMask | StructureNotifyMask, &evt); );
            if (evt.type)
                x11grab_draw_region_win(s);
        } else {
            x11grab_region_win_init(s);
        }
543 544
    }

545
    if(s->use_shm) {
546
        if (!XShmGetImage(dpy, root, image, x_off, y_off, AllPlanes)) {
547 548 549
            av_log (s1, AV_LOG_INFO, "XShmGetImage() failed\n");
        }
    } else {
550
        if (!xget_zpixmap(dpy, root, image, x_off, y_off)) {
551 552 553 554
            av_log (s1, AV_LOG_INFO, "XGetZPixmap() failed\n");
        }
    }

555
    if (s->draw_mouse) {
Roxis's avatar
Roxis committed
556
        paint_mouse_pointer(image, s);
557 558 559
    }

    return s->frame_size;
560 561
}

562
/**
563
 * Close x11 frame grabber (public device demuxer API).
564 565 566 567
 *
 * @param s1 Context from avformat core
 * @return 0 success, !0 failure
 */
568 569
static int
x11grab_read_close(AVFormatContext *s1)
570
{
571
    struct x11grab *x11grab = s1->priv_data;
572 573 574 575 576 577 578 579 580 581 582 583 584 585

    /* Detach cleanly from shared mem */
    if (x11grab->use_shm) {
        XShmDetach(x11grab->dpy, &x11grab->shminfo);
        shmdt(x11grab->shminfo.shmaddr);
        shmctl(x11grab->shminfo.shmid, IPC_RMID, NULL);
    }

    /* Destroy X11 image */
    if (x11grab->image) {
        XDestroyImage(x11grab->image);
        x11grab->image = NULL;
    }

586 587 588 589
    if (x11grab->region_win) {
        XDestroyWindow(x11grab->dpy, x11grab->region_win);
    }

590 591 592
    /* Free X11 display */
    XCloseDisplay(x11grab->dpy);
    return 0;
593 594
}

595
#define OFFSET(x) offsetof(struct x11grab, x)
596 597
#define DEC AV_OPT_FLAG_DECODING_PARAM
static const AVOption options[] = {
598
    { "draw_mouse", "draw the mouse pointer", OFFSET(draw_mouse), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 1, DEC },
599 600

    { "follow_mouse", "move the grabbing region when the mouse pointer reaches within specified amount of pixels to the edge of region",
601
      OFFSET(follow_mouse), AV_OPT_TYPE_INT, {.i64 = 0}, -1, INT_MAX, DEC, "follow_mouse" },
602
    { "centered",     "keep the mouse pointer at the center of grabbing region when following",
603
      0, AV_OPT_TYPE_CONST, {.i64 = -1}, INT_MIN, INT_MAX, DEC, "follow_mouse" },
604 605

    { "framerate",  "set video frame rate",      OFFSET(framerate),   AV_OPT_TYPE_STRING,     {.str = "ntsc"}, 0, 0, DEC },
606
    { "show_region", "show the grabbing region", OFFSET(show_region), AV_OPT_TYPE_INT,        {.i64 = 0}, 0, 1, DEC },
607
    { "video_size",  "set video frame size",     OFFSET(width),       AV_OPT_TYPE_IMAGE_SIZE, {.str = "vga"}, 0, 0, DEC },
608 609 610 611 612 613 614 615 616 617
    { NULL },
};

static const AVClass x11_class = {
    .class_name = "X11grab indev",
    .item_name  = av_default_item_name,
    .option     = options,
    .version    = LIBAVUTIL_VERSION_INT,
};

618
/** x11 grabber device demuxer declaration */
619
AVInputFormat ff_x11grab_demuxer = {
620 621
    .name           = "x11grab",
    .long_name      = NULL_IF_CONFIG_SMALL("X11grab"),
622
    .priv_data_size = sizeof(struct x11grab),
623 624 625 626 627
    .read_header    = x11grab_read_header,
    .read_packet    = x11grab_read_packet,
    .read_close     = x11grab_read_close,
    .flags          = AVFMT_NOFILE,
    .priv_class     = &x11_class,
628
};