f090a6ff19e019064cc61ac1e0164c1e3591ddeb
[vlc.git] / modules / access / screen / xcb.c
1 /**
2  * @file xcb.c
3  * @brief X11 C Bindings screen capture demux module for VLC media player
4  */
5 /*****************************************************************************
6  * Copyright © 2009 Rémi Denis-Courmont
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License
10  * as published by the Free Software Foundation; either version 2.0
11  * of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
21  ****************************************************************************/
22
23 #ifdef HAVE_CONFIG_H
24 # include <config.h>
25 #endif
26 #include <stdarg.h>
27 #include <xcb/xcb.h>
28 #include <vlc_common.h>
29 #include <vlc_demux.h>
30 #include <vlc_plugin.h>
31
32 #define CACHING_TEXT N_("Caching value in ms")
33 #define CACHING_LONGTEXT N_( \
34     "Caching value for screen capture. " \
35     "This value should be set in milliseconds.")
36
37 #define FPS_TEXT N_("Frame rate")
38 #define FPS_LONGTEXT N_( \
39     "How many times the screen content should be refreshed per second.")
40
41 #define LEFT_TEXT N_("Region left column")
42 #define LEFT_LONGTEXT N_( \
43     "Abscissa of the capture reion in pixels.")
44
45 #define TOP_TEXT N_("Region top row")
46 #define TOP_LONGTEXT N_( \
47     "Ordinate of the capture region in pixels.")
48
49 #define WIDTH_TEXT N_("Capture region width")
50 #define WIDTH_LONGTEXT N_( \
51     "Pixel width of the capture region, or 0 for full width")
52
53 #define HEIGHT_TEXT N_("Capture region height")
54 #define HEIGHT_LONGTEXT N_( \
55     "Pixel height of the capture region, or 0 for full height")
56
57 static int  Open (vlc_object_t *);
58 static void Close (vlc_object_t *);
59
60 /*
61  * Module descriptor
62  */
63 vlc_module_begin ()
64     set_shortname (N_("Screen"))
65     set_description (N_("Screen capture (with X11/XCB)"))
66     set_category (CAT_INPUT)
67     set_subcategory (SUBCAT_INPUT_ACCESS)
68     set_capability ("access_demux", 0)
69     set_callbacks (Open, Close)
70
71     add_integer ("screen-caching", DEFAULT_PTS_DELAY * 1000 / CLOCK_FREQ,
72                  NULL, CACHING_TEXT, CACHING_LONGTEXT, true)
73     add_float ("screen-fps", 2.0, NULL, FPS_TEXT, FPS_LONGTEXT, true)
74     add_integer ("screen-left", 0, NULL, LEFT_TEXT, LEFT_LONGTEXT, true)
75         change_integer_range (-32768, 32767)
76         change_safe ()
77     add_integer ("screen-top", 0, NULL, LEFT_TEXT, LEFT_LONGTEXT, true)
78         change_integer_range (-32768, 32767)
79         change_safe ()
80     add_integer ("screen-width", 0, NULL, LEFT_TEXT, LEFT_LONGTEXT, true)
81         change_integer_range (0, 65535)
82         change_safe ()
83     add_integer ("screen-height", 0, NULL, LEFT_TEXT, LEFT_LONGTEXT, true)
84         change_integer_range (0, 65535)
85         change_safe ()
86
87     add_shortcut ("screen")
88     add_shortcut ("window")
89 vlc_module_end ()
90
91 /*
92  * Local prototypes
93  */
94 static void Demux (void *);
95 static int Control (demux_t *, int, va_list);
96
97 struct demux_sys_t
98 {
99     /* WARNING: Control() is concurrent with Thread()/Demux().
100      * Currently, the thread owns everything in read/write, if it exists.
101      * We destroy it on pause, and re-create it on resume. */
102     xcb_connection_t *conn;
103     es_out_id_t      *es;
104     es_format_t       fmt;
105     mtime_t           pts, interval;
106     xcb_window_t      root, window;
107     int16_t           x, y;
108     uint16_t          w, h;
109
110     /* Timer does not use this, only input threa: */
111     vlc_timer_t       timer;
112 };
113
114 /**
115  * Probes and initializes.
116  */
117 static int Open (vlc_object_t *obj)
118 {
119     demux_t *demux = (demux_t *)obj;
120     demux_sys_t *p_sys = malloc (sizeof (*p_sys));
121
122     if (p_sys == NULL)
123         return VLC_ENOMEM;
124     demux->p_sys = p_sys;
125
126     /* Connect to X server */
127     char *display = var_CreateGetNonEmptyString (obj, "x11-display");
128     int snum;
129     xcb_connection_t *conn = xcb_connect (display, &snum);
130     free (display);
131     if (xcb_connection_has_error (conn))
132     {
133         free (p_sys);
134         return VLC_EGENERIC;
135     }
136     p_sys->conn = conn;
137
138     /* Find configured screen */
139     const xcb_setup_t *setup = xcb_get_setup (conn);
140     const xcb_screen_t *scr = NULL;
141     for (xcb_screen_iterator_t i = xcb_setup_roots_iterator (setup);
142          i.rem > 0; xcb_screen_next (&i))
143     {
144         if (snum == 0)
145         {
146             scr = i.data;
147             break;
148         }
149         snum--;
150     }
151     if (scr == NULL)
152     {
153         msg_Err (obj, "bad X11 screen number");
154         goto error;
155     }
156
157     /* Determine capture window */
158     p_sys->root = scr->root;
159     if (!strcmp (demux->psz_access, "screen"))
160         p_sys->window = p_sys->root;
161     else
162     if (!strcmp (demux->psz_access, "window"))
163     {
164         char *end;
165         unsigned long ul = strtoul (demux->psz_path, &end, 0);
166         if (*end || ul > 0xffffffff)
167         {
168             msg_Err (obj, "bad X11 drawable %s", demux->psz_path);
169             goto error;
170         }
171         p_sys->window = ul;
172     }
173     else
174         goto error;
175
176     /* Window properties */
177     p_sys->x = var_CreateGetInteger (obj, "screen-left");
178     p_sys->y = var_CreateGetInteger (obj, "screen-top");
179     p_sys->w = var_CreateGetInteger (obj, "screen-width");
180     p_sys->h = var_CreateGetInteger (obj, "screen-height");
181
182     uint32_t chroma = 0;
183     uint8_t bpp;
184     for (const xcb_format_t *fmt = xcb_setup_pixmap_formats (setup),
185              *end = fmt + xcb_setup_pixmap_formats_length (setup);
186          fmt < end; fmt++)
187     {
188         if (fmt->depth != scr->root_depth)
189             continue;
190         bpp = fmt->depth;
191         switch (fmt->depth)
192         {
193             case 32:
194                 if (fmt->bits_per_pixel == 32)
195                     chroma = VLC_CODEC_RGBA;
196                 break;
197             case 24:
198                 if (fmt->bits_per_pixel == 32)
199                 {
200                     chroma = VLC_CODEC_RGB32;
201                     bpp = 32;
202                 }
203                 else if (fmt->bits_per_pixel == 24)
204                     chroma = VLC_CODEC_RGB24;
205                 break;
206             case 16:
207                 if (fmt->bits_per_pixel == 16)
208                     chroma = VLC_CODEC_RGB16;
209                 break;
210             case 15:
211                 if (fmt->bits_per_pixel == 16)
212                     chroma = VLC_CODEC_RGB15;
213                 break;
214             case 8: /* XXX: screw grey scale! */
215                 if (fmt->bits_per_pixel == 8)
216                     chroma = VLC_CODEC_RGB8;
217                 break;
218         }
219         if (chroma != 0)
220             break;
221     }
222
223     if (!chroma)
224     {
225         msg_Err (obj, "unsupported pixmap formats");
226         goto error;
227     }
228
229     /* Initializes format */
230     float rate = var_CreateGetFloat (obj, "screen-fps");
231     if (!rate)
232         goto error;
233     p_sys->interval = (float)CLOCK_FREQ / rate;
234     if (!p_sys->interval)
235         goto error;
236     var_Create (obj, "screen-caching", VLC_VAR_INTEGER|VLC_VAR_DOINHERIT);
237
238     es_format_Init (&p_sys->fmt, VIDEO_ES, chroma);
239     p_sys->fmt.video.i_chroma = chroma;
240     p_sys->fmt.video.i_bits_per_pixel = bpp;
241     p_sys->fmt.video.i_sar_num = p_sys->fmt.video.i_sar_den = 1;
242     p_sys->fmt.video.i_frame_rate = 1000 * rate;
243     p_sys->fmt.video.i_frame_rate_base = 1000;
244     p_sys->es = NULL;
245     p_sys->pts = VLC_TS_INVALID;
246     if (vlc_timer_create (&p_sys->timer, Demux, demux))
247         goto error;
248     vlc_timer_schedule (&p_sys->timer, false, 1, p_sys->interval);
249
250     /* Initializes demux */
251     demux->pf_demux   = NULL;
252     demux->pf_control = Control;
253     return VLC_SUCCESS;
254
255 error:
256     xcb_disconnect (p_sys->conn);
257     free (p_sys);
258     return VLC_EGENERIC;
259 }
260
261
262 /**
263  * Releases resources
264  */
265 static void Close (vlc_object_t *obj)
266 {
267     demux_t *demux = (demux_t *)obj;
268     demux_sys_t *p_sys = demux->p_sys;
269
270     vlc_timer_destroy (&p_sys->timer);
271     xcb_disconnect (p_sys->conn);
272     free (p_sys);
273 }
274
275
276 /**
277  * Control callback
278  */
279 static int Control (demux_t *demux, int query, va_list args)
280 {
281     demux_sys_t *p_sys = demux->p_sys;
282
283     switch (query)
284     {
285         case DEMUX_GET_POSITION:
286         {
287             float *v = va_arg (args, float *);
288             *v = 0.;
289             return VLC_SUCCESS;
290         }
291
292         case DEMUX_GET_LENGTH:
293         case DEMUX_GET_TIME:
294         {
295             int64_t *v = va_arg (args, int64_t *);
296             *v = 0;
297             return VLC_SUCCESS;
298         }
299
300         /* TODO: get title info -> crawl visible windows */
301
302         case DEMUX_GET_PTS_DELAY:
303         {
304             int64_t *v = va_arg (args, int64_t *);
305             *v = var_GetInteger (demux, "screen-caching") * UINT64_C(1000);
306             return VLC_SUCCESS;
307         }
308
309         case DEMUX_CAN_PAUSE:
310         {
311             bool *v = (bool*)va_arg( args, bool * );
312             *v = true;
313             return VLC_SUCCESS;
314         }
315
316         case DEMUX_SET_PAUSE_STATE:
317         {
318             bool pausing = va_arg (args, int);
319
320             if (!pausing)
321             {
322                 p_sys->pts = VLC_TS_INVALID;
323                 es_out_Control (demux->out, ES_OUT_RESET_PCR);
324             }
325             vlc_timer_schedule (&p_sys->timer, false,
326                                 pausing ? 0 : 1, p_sys->interval);
327             return VLC_SUCCESS;
328         }
329
330         case DEMUX_CAN_CONTROL_PACE:
331         case DEMUX_CAN_CONTROL_RATE:
332         case DEMUX_CAN_SEEK:
333         {
334             bool *v = (bool*)va_arg( args, bool * );
335             *v = false;
336             return VLC_SUCCESS;
337         }
338     }
339
340     return VLC_EGENERIC;
341 }
342
343
344 /**
345  * Processing callback
346  */
347 static void Demux (void *data)
348 {
349     demux_t *demux = data;
350     demux_sys_t *p_sys = demux->p_sys;
351     xcb_connection_t *conn = p_sys->conn;
352
353     /* Update capture region (if needed) */
354     xcb_get_geometry_cookie_t gc = xcb_get_geometry (conn, p_sys->window);
355     int16_t x = p_sys->x, y = p_sys->y;
356     xcb_translate_coordinates_cookie_t tc;
357
358     if (p_sys->window != p_sys->root)
359         tc = xcb_translate_coordinates (conn, p_sys->window, p_sys->root,
360                                         x, y);
361
362     xcb_get_geometry_reply_t *geo = xcb_get_geometry_reply (conn, gc, NULL);
363     if (geo == NULL)
364     {
365         msg_Err (demux, "bad X11 drawable 0x%08"PRIx32, p_sys->window);
366         return;
367     }
368
369     uint16_t w = geo->width - x;
370     uint16_t h = geo->height - y;
371     if (p_sys->w > 0 && p_sys->w < w)
372         w = p_sys->w;
373     if (p_sys->h > 0 && p_sys->h < h)
374         h = p_sys->h;
375
376     if (w != p_sys->fmt.video.i_visible_width
377      || h != p_sys->fmt.video.i_visible_height)
378     {
379         if (p_sys->es != NULL)
380             es_out_Del (demux->out, p_sys->es);
381         p_sys->fmt.video.i_visible_width = p_sys->fmt.video.i_width = w;
382         p_sys->fmt.video.i_visible_height = p_sys->fmt.video.i_height = h;
383         p_sys->es = es_out_Add (demux->out, &p_sys->fmt);
384     }
385     free (geo);
386
387     if (p_sys->window != p_sys->root)
388     {
389         xcb_translate_coordinates_reply_t *coords =
390              xcb_translate_coordinates_reply (conn, tc, NULL);
391         if (coords == NULL)
392             return;
393         x = coords->dst_x;
394         y = coords->dst_y;
395         free (coords);
396     }
397
398     /* Capture screen */
399     if (p_sys->es == NULL)
400         return;
401
402     xcb_get_image_reply_t *img;
403     img = xcb_get_image_reply (conn,
404         xcb_get_image (conn, XCB_IMAGE_FORMAT_Z_PIXMAP, p_sys->root,
405                        x, y, w, h, ~0), NULL);
406     if (img == NULL)
407         return;
408
409     /* Send block - zero copy */
410     block_t *block = block_heap_Alloc (img, xcb_get_image_data (img),
411                                        xcb_get_image_data_length (img));
412     if (block == NULL)
413         return;
414
415     if (p_sys->pts == VLC_TS_INVALID)
416         p_sys->pts = mdate ();
417     block->i_pts = block->i_dts = p_sys->pts;
418
419     es_out_Control (demux->out, ES_OUT_SET_PCR, p_sys->pts);
420     es_out_Send (demux->out, p_sys->es, block);
421     p_sys->pts += p_sys->interval;
422 }