Wayland/screen: remove useless callbacks
[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 program is free software; you can redistribute it and/or modify it
9  * under the terms of the GNU Lesser General Public License as published by
10  * the Free Software Foundation; either version 2.1 of the License, or
11  * (at your option) any later version.
12  *
13  * This program 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 Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with this program; if not, write to the Free Software Foundation,
20  * Inc., 51 Franklin Street, 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 <assert.h>
28 #include <errno.h>
29 #include <xcb/xcb.h>
30 #include <xcb/composite.h>
31 #ifdef HAVE_SYS_SHM_H
32 # include <sys/shm.h>
33 # include <xcb/shm.h>
34 #endif
35 #include <vlc_common.h>
36 #include <vlc_demux.h>
37 #include <vlc_plugin.h>
38
39 #define FPS_TEXT N_("Frame rate")
40 #define FPS_LONGTEXT N_( \
41     "How many times the screen content should be refreshed per second.")
42
43 #define LEFT_TEXT N_("Region left column")
44 #define LEFT_LONGTEXT N_( \
45     "Abscissa of the capture region in pixels.")
46
47 #define TOP_TEXT N_("Region top row")
48 #define TOP_LONGTEXT N_( \
49     "Ordinate of the capture region in pixels.")
50
51 #define WIDTH_TEXT N_("Capture region width")
52 #define WIDTH_LONGTEXT N_( \
53     "Pixel width of the capture region, or 0 for full width")
54
55 #define HEIGHT_TEXT N_("Capture region height")
56 #define HEIGHT_LONGTEXT N_( \
57     "Pixel height of the capture region, or 0 for full height")
58
59 #define FOLLOW_MOUSE_TEXT N_( "Follow the mouse" )
60 #define FOLLOW_MOUSE_LONGTEXT N_( \
61     "Follow the mouse when capturing a subscreen." )
62
63 static int  Open (vlc_object_t *);
64 static void Close (vlc_object_t *);
65
66 /*
67  * Module descriptor
68  */
69 vlc_module_begin ()
70     set_shortname (N_("Screen"))
71     set_description (N_("Screen capture (with X11/XCB)"))
72     set_category (CAT_INPUT)
73     set_subcategory (SUBCAT_INPUT_ACCESS)
74     set_capability ("access_demux", 0)
75     set_callbacks (Open, Close)
76
77     add_float ("screen-fps", 2.0, FPS_TEXT, FPS_LONGTEXT, true)
78     add_integer ("screen-left", 0, LEFT_TEXT, LEFT_LONGTEXT, true)
79         change_integer_range (-32768, 32767)
80         change_safe ()
81     add_integer ("screen-top", 0, TOP_TEXT, TOP_LONGTEXT, true)
82         change_integer_range (-32768, 32767)
83         change_safe ()
84     add_integer ("screen-width", 0, WIDTH_TEXT, WIDTH_LONGTEXT, true)
85         change_integer_range (0, 65535)
86         change_safe ()
87     add_integer ("screen-height", 0, HEIGHT_TEXT, HEIGHT_LONGTEXT, true)
88         change_integer_range (0, 65535)
89         change_safe ()
90     add_bool ("screen-follow-mouse", false, FOLLOW_MOUSE_TEXT,
91               FOLLOW_MOUSE_LONGTEXT, true)
92
93     add_shortcut ("screen", "window")
94 vlc_module_end ()
95
96 /*
97  * Local prototypes
98  */
99 static void Demux (void *);
100 static int Control (demux_t *, int, va_list);
101 static es_out_id_t *InitES (demux_t *, uint_fast16_t, uint_fast16_t,
102                             uint_fast8_t, uint8_t *);
103
104 struct demux_sys_t
105 {
106     /* All owned by timer thread while timer is armed: */
107     xcb_connection_t *conn; /**< XCB connection */
108     es_out_id_t      *es; /**< Window capture stream */
109     float             rate; /**< Frame rate */
110     xcb_window_t      window; /**< Captured window XID  */
111     xcb_pixmap_t      pixmap; /**< Pixmap for composited capture */
112     xcb_shm_seg_t     segment; /**< SHM segment XID */
113     int16_t           x, y; /**< Requested capture top-left coordinates */
114     uint16_t          w, h; /**< Requested capture pixel dimensions */
115     uint8_t           bpp; /**< Actual bytes per pixel *es */
116     bool              shm; /**< Whether to use MIT-SHM */
117     bool              follow_mouse;
118     uint16_t          cur_w, cur_h; /**< Actual capture pixel dimensions */
119     /* Timer does not use this, only input thread: */
120     vlc_timer_t       timer;
121 };
122
123 /** Checks MIT-SHM shared memory support */
124 static bool CheckSHM (xcb_connection_t *conn)
125 {
126 #ifdef HAVE_SYS_SHM_H
127     xcb_shm_query_version_cookie_t ck = xcb_shm_query_version (conn);
128     xcb_shm_query_version_reply_t *r;
129
130     r = xcb_shm_query_version_reply (conn, ck, NULL);
131     free (r);
132     return r != NULL;
133 #else
134     (void) conn;
135     return false;
136 #endif
137 }
138
139 /**
140  * Probes and initializes.
141  */
142 static int Open (vlc_object_t *obj)
143 {
144     demux_t *demux = (demux_t *)obj;
145     demux_sys_t *p_sys = malloc (sizeof (*p_sys));
146
147     if (p_sys == NULL)
148         return VLC_ENOMEM;
149     demux->p_sys = p_sys;
150
151     /* Connect to X server */
152     char *display = var_InheritString (obj, "x11-display");
153     int snum;
154     xcb_connection_t *conn = xcb_connect (display, &snum);
155     free (display);
156     if (xcb_connection_has_error (conn))
157     {
158         free (p_sys);
159         return VLC_EGENERIC;
160     }
161     p_sys->conn = conn;
162
163    /* Find configured screen */
164     if (!strcmp (demux->psz_access, "screen"))
165     {
166         const xcb_setup_t *setup = xcb_get_setup (conn);
167         const xcb_screen_t *scr = NULL;
168         for (xcb_screen_iterator_t i = xcb_setup_roots_iterator (setup);
169              i.rem > 0; xcb_screen_next (&i))
170         {
171             if (snum == 0)
172             {
173                 scr = i.data;
174                 break;
175             }
176             snum--;
177         }
178         if (scr == NULL)
179         {
180             msg_Err (obj, "bad X11 screen number");
181             goto error;
182         }
183         p_sys->window = scr->root;
184     }
185     else
186     /* Determine capture window */
187     if (!strcmp (demux->psz_access, "window"))
188     {
189         char *end;
190         unsigned long ul = strtoul (demux->psz_location, &end, 0);
191         if (*end || ul > 0xffffffff)
192         {
193             msg_Err (obj, "bad X11 drawable %s", demux->psz_location);
194             goto error;
195         }
196         p_sys->window = ul;
197
198         xcb_composite_query_version_reply_t *r =
199             xcb_composite_query_version_reply (conn,
200                 xcb_composite_query_version (conn, 0, 4), NULL);
201         if (r == NULL || r->minor_version < 2)
202         {
203             msg_Err (obj, "X Composite extension not available");
204             free (r);
205             goto error;
206         }
207         msg_Dbg (obj, "using Composite extension v%"PRIu32".%"PRIu32,
208                  r->major_version, r->minor_version);
209         free (r);
210
211         xcb_composite_redirect_window (conn, p_sys->window,
212                                        XCB_COMPOSITE_REDIRECT_AUTOMATIC);
213     }
214     else
215         goto error;
216
217     /* Window properties */
218     p_sys->pixmap = xcb_generate_id (conn);
219     p_sys->segment = xcb_generate_id (conn);
220     p_sys->shm = CheckSHM (conn);
221     p_sys->w = var_InheritInteger (obj, "screen-width");
222     p_sys->h = var_InheritInteger (obj, "screen-height");
223     if (p_sys->w != 0 || p_sys->h != 0)
224         p_sys->follow_mouse = var_InheritBool (obj, "screen-follow-mouse");
225     else /* Following mouse is meaningless if width&height are dynamic. */
226         p_sys->follow_mouse = false;
227     if (!p_sys->follow_mouse) /* X and Y are meaningless if following mouse */
228     {
229         p_sys->x = var_InheritInteger (obj, "screen-left");
230         p_sys->y = var_InheritInteger (obj, "screen-top");
231     }
232
233     /* Initializes format */
234     p_sys->rate = var_InheritFloat (obj, "screen-fps");
235     if (!p_sys->rate)
236         goto error;
237
238     mtime_t interval = (float)CLOCK_FREQ / p_sys->rate;
239     if (!interval)
240         goto error;
241
242     p_sys->cur_w = 0;
243     p_sys->cur_h = 0;
244     p_sys->bpp = 0;
245     p_sys->es = NULL;
246     if (vlc_timer_create (&p_sys->timer, Demux, demux))
247         goto error;
248     vlc_timer_schedule (p_sys->timer, false, 1, 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     switch (query)
282     {
283         case DEMUX_GET_POSITION:
284         {
285             float *v = va_arg (args, float *);
286             *v = 0.;
287             return VLC_SUCCESS;
288         }
289
290         case DEMUX_GET_LENGTH:
291         case DEMUX_GET_TIME:
292         {
293             int64_t *v = va_arg (args, int64_t *);
294             *v = 0;
295             return VLC_SUCCESS;
296         }
297
298         /* TODO: get title info -> crawl visible windows */
299
300         case DEMUX_GET_PTS_DELAY:
301         {
302             int64_t *v = va_arg (args, int64_t *);
303             *v = INT64_C(1000) * var_InheritInteger (demux, "live-caching");
304             return VLC_SUCCESS;
305         }
306
307         case DEMUX_CAN_PAUSE:
308         case DEMUX_CAN_CONTROL_PACE:
309         case DEMUX_CAN_CONTROL_RATE:
310         case DEMUX_CAN_SEEK:
311         {
312             bool *v = va_arg (args, bool *);
313             *v = false;
314             return VLC_SUCCESS;
315         }
316
317         case DEMUX_SET_PAUSE_STATE:
318             return VLC_SUCCESS; /* should not happen */
319     }
320
321     return VLC_EGENERIC;
322 }
323
324
325 /**
326  * Processing callback
327  */
328 static void Demux (void *opaque)
329 {
330     demux_t *demux = opaque;
331     demux_sys_t *sys = demux->p_sys;
332     xcb_connection_t *conn = sys->conn;
333
334     /* Determine capture region */
335     xcb_get_geometry_cookie_t gc;
336     xcb_query_pointer_cookie_t qc;
337
338     gc = xcb_get_geometry (conn, sys->window);
339     if (sys->follow_mouse)
340         qc = xcb_query_pointer (conn, sys->window);
341
342     xcb_get_geometry_reply_t *geo = xcb_get_geometry_reply (conn, gc, NULL);
343     if (geo == NULL)
344     {
345         msg_Err (demux, "bad X11 drawable 0x%08"PRIx32, sys->window);
346 discard:
347         if (sys->follow_mouse)
348             xcb_discard_reply (conn, gc.sequence);
349         return;
350     }
351
352     unsigned w = sys->w;
353     unsigned h = sys->h;
354     int x, y;
355
356     if (sys->follow_mouse)
357     {
358         xcb_query_pointer_reply_t *ptr =
359             xcb_query_pointer_reply (conn, qc, NULL);
360         if (ptr == NULL)
361         {
362             free (geo);
363             return;
364         }
365
366         if (w == 0 || w > geo->width)
367             w = geo->width;
368         x = ptr->win_x;
369         if (x < (int)(w / 2))
370             x = 0;
371         else if (x >= (int)(geo->width - (w / 2)))
372             x = geo->width - w;
373         else
374             x -= w / 2;
375
376         if (h == 0 || h > geo->height)
377             h = geo->height;
378         y = ptr->win_y;
379         if (y < (int)(h / 2))
380             y = 0;
381         else if (y >= (int)(geo->height - (h / 2)))
382             y = geo->height - h;
383         else
384             y -= h / 2;
385     }
386     else
387     {
388         int max;
389
390         x = sys->x;
391         max = (int)geo->width - x;
392         if (max <= 0)
393             goto discard;
394         if (w == 0 || w > (unsigned)max)
395             w = max;
396
397         y = sys->y;
398         max = (int)geo->height - y;
399         if (max <= 0)
400             goto discard;
401         if (h == 0 || h > (unsigned)max)
402             h = max;
403     }
404
405     /* Update elementary stream format (if needed) */
406     if (w != sys->cur_w || h != sys->cur_h)
407     {
408         if (sys->es != NULL)
409             es_out_Del (demux->out, sys->es);
410
411         /* Update composite pixmap */
412         if (sys->window != geo->root)
413         {
414             xcb_free_pixmap (conn, sys->pixmap); /* no-op first time */
415             xcb_composite_name_window_pixmap (conn, sys->window, sys->pixmap);
416             xcb_create_pixmap (conn, geo->depth, sys->pixmap,
417                                geo->root, geo->width, geo->height);
418         }
419
420         sys->es = InitES (demux, w, h, geo->depth, &sys->bpp);
421         if (sys->es != NULL)
422         {
423             sys->cur_w = w;
424             sys->cur_h = h;
425             sys->bpp /= 8; /* bits -> bytes */
426         }
427     }
428
429     /* Capture screen */
430     xcb_drawable_t drawable =
431         (sys->window != geo->root) ? sys->pixmap : sys->window;
432     free (geo);
433
434     block_t *block = NULL;
435 #if HAVE_SYS_SHM_H
436     if (sys->shm)
437     {   /* Capture screen through shared memory */
438         size_t size = w * h * sys->bpp;
439         int id = shmget (IPC_PRIVATE, size, IPC_CREAT | 0777);
440         if (id == -1) /* XXX: fallback */
441         {
442             msg_Err (demux, "shared memory allocation error: %s",
443                      vlc_strerror_c(errno));
444             goto noshm;
445         }
446
447         /* Attach the segment to X and capture */
448         xcb_shm_get_image_reply_t *img;
449         xcb_shm_get_image_cookie_t ck;
450
451         xcb_shm_attach (conn, sys->segment, id, 0 /* read/write */);
452         ck = xcb_shm_get_image (conn, drawable, x, y, w, h, ~0,
453                                 XCB_IMAGE_FORMAT_Z_PIXMAP, sys->segment, 0);
454         xcb_shm_detach (conn, sys->segment);
455         img = xcb_shm_get_image_reply (conn, ck, NULL);
456         xcb_flush (conn); /* ensure eventual detach */
457
458         if (img == NULL)
459         {
460             shmctl (id, IPC_RMID, 0);
461             goto noshm;
462         }
463         free (img);
464
465         /* Attach the segment to VLC */
466         void *shm = shmat (id, NULL, 0 /* read/write */);
467         shmctl (id, IPC_RMID, 0);
468         if (-1 == (intptr_t)shm)
469         {
470             msg_Err (demux, "shared memory attachment error: %s",
471                      vlc_strerror_c(errno));
472             return;
473         }
474
475         block = block_shm_Alloc (shm, size);
476         if (unlikely(block == NULL))
477             shmdt (shm);
478     }
479 noshm:
480 #endif
481     if (block == NULL)
482     {   /* Capture screen through socket (fallback) */
483         xcb_get_image_reply_t *img;
484
485         img = xcb_get_image_reply (conn,
486             xcb_get_image (conn, XCB_IMAGE_FORMAT_Z_PIXMAP, drawable,
487                            x, y, w, h, ~0), NULL);
488         if (img == NULL)
489             return;
490
491         uint8_t *data = xcb_get_image_data (img);
492         size_t datalen = xcb_get_image_data_length (img);
493         block = block_heap_Alloc (img, data + datalen - (uint8_t *)img);
494         if (block == NULL)
495             return;
496         block->p_buffer = data;
497         block->i_buffer = datalen;
498     }
499
500     /* Send block - zero copy */
501     if (sys->es != NULL)
502     {
503         block->i_pts = block->i_dts = mdate ();
504
505         es_out_Control (demux->out, ES_OUT_SET_PCR, block->i_pts);
506         es_out_Send (demux->out, sys->es, block);
507     }
508     else
509         block_Release (block);
510 }
511
512 static es_out_id_t *InitES (demux_t *demux, uint_fast16_t width,
513                             uint_fast16_t height, uint_fast8_t depth,
514                             uint8_t *bpp)
515 {
516     demux_sys_t *p_sys = demux->p_sys;
517     const xcb_setup_t *setup = xcb_get_setup (p_sys->conn);
518     uint32_t chroma = 0;
519
520     for (const xcb_format_t *fmt = xcb_setup_pixmap_formats (setup),
521              *end = fmt + xcb_setup_pixmap_formats_length (setup);
522          fmt < end; fmt++)
523     {
524         if (fmt->depth != depth)
525             continue;
526         switch (fmt->depth)
527         {
528             case 32:
529                 if (fmt->bits_per_pixel == 32)
530                     chroma = VLC_CODEC_ARGB;
531                 break;
532             case 24:
533                 if (fmt->bits_per_pixel == 32)
534                     chroma = VLC_CODEC_RGB32;
535                 else if (fmt->bits_per_pixel == 24)
536                     chroma = VLC_CODEC_RGB24;
537                 break;
538             case 16:
539                 if (fmt->bits_per_pixel == 16)
540                     chroma = VLC_CODEC_RGB16;
541                 break;
542             case 15:
543                 if (fmt->bits_per_pixel == 16)
544                     chroma = VLC_CODEC_RGB15;
545                 break;
546             case 8: /* XXX: screw grey scale! */
547                 if (fmt->bits_per_pixel == 8)
548                     chroma = VLC_CODEC_RGB8;
549                 break;
550         }
551         if (chroma != 0)
552         {
553             *bpp = fmt->bits_per_pixel;
554             break;
555         }
556     }
557
558     if (!chroma)
559     {
560         msg_Err (demux, "unsupported pixmap formats");
561         return NULL;
562     }
563
564     es_format_t fmt;
565
566     es_format_Init (&fmt, VIDEO_ES, chroma);
567     fmt.video.i_chroma = chroma;
568     fmt.video.i_bits_per_pixel = *bpp;
569     fmt.video.i_sar_num = fmt.video.i_sar_den = 1;
570     fmt.video.i_frame_rate = 1000 * p_sys->rate;
571     fmt.video.i_frame_rate_base = 1000;
572     fmt.video.i_visible_width = fmt.video.i_width = width;
573     fmt.video.i_visible_height = fmt.video.i_height = height;
574
575     return es_out_Add (demux->out, &fmt);
576 }