XCB screen: timer thread safety
[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     xcb_connection_t *conn;
100     es_out_id_t      *es;
101     es_format_t       fmt;
102     mtime_t           pts, interval;
103     xcb_window_t      root, window;
104     int16_t           x, y;
105     uint16_t          w, h;
106     /* fmt, es and pts are protected by the lock. The rest is read-only. */
107     vlc_mutex_t       lock;
108     /* Timer does not use this, only input thread: */
109     vlc_timer_t       timer;
110 };
111
112 /**
113  * Probes and initializes.
114  */
115 static int Open (vlc_object_t *obj)
116 {
117     demux_t *demux = (demux_t *)obj;
118     demux_sys_t *p_sys = malloc (sizeof (*p_sys));
119
120     if (p_sys == NULL)
121         return VLC_ENOMEM;
122     demux->p_sys = p_sys;
123
124     /* Connect to X server */
125     char *display = var_CreateGetNonEmptyString (obj, "x11-display");
126     int snum;
127     xcb_connection_t *conn = xcb_connect (display, &snum);
128     free (display);
129     if (xcb_connection_has_error (conn))
130     {
131         free (p_sys);
132         return VLC_EGENERIC;
133     }
134     p_sys->conn = conn;
135
136     /* Find configured screen */
137     const xcb_setup_t *setup = xcb_get_setup (conn);
138     const xcb_screen_t *scr = NULL;
139     for (xcb_screen_iterator_t i = xcb_setup_roots_iterator (setup);
140          i.rem > 0; xcb_screen_next (&i))
141     {
142         if (snum == 0)
143         {
144             scr = i.data;
145             break;
146         }
147         snum--;
148     }
149     if (scr == NULL)
150     {
151         msg_Err (obj, "bad X11 screen number");
152         goto error;
153     }
154
155     /* Determine capture window */
156     p_sys->root = scr->root;
157     if (!strcmp (demux->psz_access, "screen"))
158         p_sys->window = p_sys->root;
159     else
160     if (!strcmp (demux->psz_access, "window"))
161     {
162         char *end;
163         unsigned long ul = strtoul (demux->psz_path, &end, 0);
164         if (*end || ul > 0xffffffff)
165         {
166             msg_Err (obj, "bad X11 drawable %s", demux->psz_path);
167             goto error;
168         }
169         p_sys->window = ul;
170     }
171     else
172         goto error;
173
174     /* Window properties */
175     p_sys->x = var_CreateGetInteger (obj, "screen-left");
176     p_sys->y = var_CreateGetInteger (obj, "screen-top");
177     p_sys->w = var_CreateGetInteger (obj, "screen-width");
178     p_sys->h = var_CreateGetInteger (obj, "screen-height");
179
180     uint32_t chroma = 0;
181     uint8_t bpp;
182     for (const xcb_format_t *fmt = xcb_setup_pixmap_formats (setup),
183              *end = fmt + xcb_setup_pixmap_formats_length (setup);
184          fmt < end; fmt++)
185     {
186         if (fmt->depth != scr->root_depth)
187             continue;
188         bpp = fmt->depth;
189         switch (fmt->depth)
190         {
191             case 32:
192                 if (fmt->bits_per_pixel == 32)
193                     chroma = VLC_CODEC_RGBA;
194                 break;
195             case 24:
196                 if (fmt->bits_per_pixel == 32)
197                 {
198                     chroma = VLC_CODEC_RGB32;
199                     bpp = 32;
200                 }
201                 else if (fmt->bits_per_pixel == 24)
202                     chroma = VLC_CODEC_RGB24;
203                 break;
204             case 16:
205                 if (fmt->bits_per_pixel == 16)
206                     chroma = VLC_CODEC_RGB16;
207                 break;
208             case 15:
209                 if (fmt->bits_per_pixel == 16)
210                     chroma = VLC_CODEC_RGB15;
211                 break;
212             case 8: /* XXX: screw grey scale! */
213                 if (fmt->bits_per_pixel == 8)
214                     chroma = VLC_CODEC_RGB8;
215                 break;
216         }
217         if (chroma != 0)
218             break;
219     }
220
221     if (!chroma)
222     {
223         msg_Err (obj, "unsupported pixmap formats");
224         goto error;
225     }
226
227     /* Initializes format */
228     float rate = var_CreateGetFloat (obj, "screen-fps");
229     if (!rate)
230         goto error;
231     p_sys->interval = (float)CLOCK_FREQ / rate;
232     if (!p_sys->interval)
233         goto error;
234     var_Create (obj, "screen-caching", VLC_VAR_INTEGER|VLC_VAR_DOINHERIT);
235
236     es_format_Init (&p_sys->fmt, VIDEO_ES, chroma);
237     p_sys->fmt.video.i_chroma = chroma;
238     p_sys->fmt.video.i_bits_per_pixel = bpp;
239     p_sys->fmt.video.i_sar_num = p_sys->fmt.video.i_sar_den = 1;
240     p_sys->fmt.video.i_frame_rate = 1000 * rate;
241     p_sys->fmt.video.i_frame_rate_base = 1000;
242     p_sys->es = NULL;
243     p_sys->pts = VLC_TS_INVALID;
244     vlc_mutex_init (&p_sys->lock);
245     if (vlc_timer_create (&p_sys->timer, Demux, demux))
246         goto error;
247     vlc_timer_schedule (&p_sys->timer, false, 1, p_sys->interval);
248
249     /* Initializes demux */
250     demux->pf_demux   = NULL;
251     demux->pf_control = Control;
252     return VLC_SUCCESS;
253
254 error:
255     xcb_disconnect (p_sys->conn);
256     free (p_sys);
257     return VLC_EGENERIC;
258 }
259
260
261 /**
262  * Releases resources
263  */
264 static void Close (vlc_object_t *obj)
265 {
266     demux_t *demux = (demux_t *)obj;
267     demux_sys_t *p_sys = demux->p_sys;
268
269     vlc_timer_destroy (&p_sys->timer);
270     vlc_mutex_destroy (&p_sys->lock);
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                 vlc_mutex_lock (&p_sys->lock);
323                 p_sys->pts = VLC_TS_INVALID;
324                 es_out_Control (demux->out, ES_OUT_RESET_PCR);
325                 vlc_mutex_unlock (&p_sys->lock);
326             }
327             vlc_timer_schedule (&p_sys->timer, false,
328                                 pausing ? 0 : 1, p_sys->interval);
329             return VLC_SUCCESS;
330         }
331
332         case DEMUX_CAN_CONTROL_PACE:
333         case DEMUX_CAN_CONTROL_RATE:
334         case DEMUX_CAN_SEEK:
335         {
336             bool *v = (bool*)va_arg( args, bool * );
337             *v = false;
338             return VLC_SUCCESS;
339         }
340     }
341
342     return VLC_EGENERIC;
343 }
344
345
346 /**
347  * Processing callback
348  */
349 static void Demux (void *data)
350 {
351     demux_t *demux = data;
352     demux_sys_t *p_sys = demux->p_sys;
353     xcb_connection_t *conn = p_sys->conn;
354
355     /* Update capture region (if needed) */
356     xcb_get_geometry_cookie_t gc = xcb_get_geometry (conn, p_sys->window);
357     int16_t x = p_sys->x, y = p_sys->y;
358     xcb_translate_coordinates_cookie_t tc;
359
360     if (p_sys->window != p_sys->root)
361         tc = xcb_translate_coordinates (conn, p_sys->window, p_sys->root,
362                                         x, y);
363
364     xcb_get_geometry_reply_t *geo = xcb_get_geometry_reply (conn, gc, NULL);
365     if (geo == NULL)
366     {
367         msg_Err (demux, "bad X11 drawable 0x%08"PRIx32, p_sys->window);
368         return;
369     }
370
371     uint16_t w = geo->width - x;
372     uint16_t h = geo->height - y;
373     free (geo);
374     if (p_sys->w > 0 && p_sys->w < w)
375         w = p_sys->w;
376     if (p_sys->h > 0 && p_sys->h < h)
377         h = p_sys->h;
378
379     if (p_sys->window != p_sys->root)
380     {
381         xcb_translate_coordinates_reply_t *coords =
382              xcb_translate_coordinates_reply (conn, tc, NULL);
383         if (coords == NULL)
384             return;
385         x = coords->dst_x;
386         y = coords->dst_y;
387         free (coords);
388     }
389
390     xcb_get_image_reply_t *img;
391     img = xcb_get_image_reply (conn,
392         xcb_get_image (conn, XCB_IMAGE_FORMAT_Z_PIXMAP, p_sys->root,
393                        x, y, w, h, ~0), NULL);
394     if (img == NULL)
395         return;
396
397     /* Send block - zero copy */
398     block_t *block = block_heap_Alloc (img, xcb_get_image_data (img),
399                                        xcb_get_image_data_length (img));
400     if (block == NULL)
401         return;
402
403     vlc_mutex_lock (&p_sys->lock);
404     if (w != p_sys->fmt.video.i_visible_width
405      || h != p_sys->fmt.video.i_visible_height)
406     {
407         if (p_sys->es != NULL)
408             es_out_Del (demux->out, p_sys->es);
409         p_sys->fmt.video.i_visible_width = p_sys->fmt.video.i_width = w;
410         p_sys->fmt.video.i_visible_height = p_sys->fmt.video.i_height = h;
411         p_sys->es = es_out_Add (demux->out, &p_sys->fmt);
412     }
413
414     /* Capture screen */
415     if (p_sys->es != NULL)
416     {
417         if (p_sys->pts == VLC_TS_INVALID)
418             p_sys->pts = mdate ();
419         block->i_pts = block->i_dts = p_sys->pts;
420
421         es_out_Control (demux->out, ES_OUT_SET_PCR, p_sys->pts);
422         es_out_Send (demux->out, p_sys->es, block);
423         p_sys->pts += p_sys->interval;
424     }
425     vlc_mutex_unlock (&p_sys->lock);
426 }