1 /*
2  * x11source.c - X11/transcode bridge code, allowing screen capture.
3  * (C) 2006-2010 - Francesco Romani <fromani -at- gmail -dot- com>
4  *
5  * This file is part of transcode, a video stream processing tool.
6  *
7  * transcode is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * transcode is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include "config.h"
22 
23 #include <string.h>
24 
25 #include "transcode.h"
26 
27 #include "magic.h"
28 #include "x11source.h"
29 
30 #include "libtc/ratiocodes.h"
31 #include "libtc/tccodecs.h"
32 #include "libtc/tcframes.h"
33 #include "libtcvideo/tcvideo.h"
34 
35 /*
36  * TODO:
37  * - internal docs
38  * - internla refactoring to properly grab cursor
39  */
40 
41 #ifdef HAVE_X11
42 
43 /*************************************************************************/
44 /* cursor grabbing support. */
45 
46 #ifdef HAVE_X11_FIXES
47 
48 #include <X11/extensions/Xfixes.h>
49 
50 
tc_x11source_acquire_cursor_fixes(TCX11Source * handle,uint8_t * data,int maxdata)51 static void tc_x11source_acquire_cursor_fixes(TCX11Source *handle,
52                                               uint8_t *data, int maxdata)
53 {
54     XFixesCursorImage *cursor = XFixesGetCursorImage(handle->dpy);
55 
56     if (cursor == NULL) {
57         /* this MUST be noisy! */
58         tc_log_warn(__FILE__, "failed to get cursor image");
59     } else {
60         /* FIXME: this has to be rewritten and need significant
61          * internal refactoring :( */
62     }
63 }
64 
65 #endif /* HAVE_X11_FIXES */
66 
67 /* FIXME: explain why don't use funcpointers in here */
tc_x11source_acquire_cursor_plain(TCX11Source * handle,uint8_t * data,int maxdata)68 static void tc_x11source_acquire_cursor_plain(TCX11Source *handle,
69                                               uint8_t *data, int maxdata)
70 {
71     static int warn = 0;
72 
73     if (!warn) {
74         tc_log_warn(__FILE__, "cursor grabbing not supported!");
75         warn = 1;
76     }
77 }
78 
tc_x11source_init_cursor(TCX11Source * handle)79 static void tc_x11source_init_cursor(TCX11Source *handle)
80 {
81     /* sane default if we don't have any better */
82     handle->acquire_cursor = tc_x11source_acquire_cursor_plain;
83 #ifdef HAVE_X11_FIXES
84     handle->acquire_cursor = tc_x11source_acquire_cursor_fixes;
85 #endif
86 }
87 
88 /*************************************************************************/
89 
tc_x11source_is_display_name(const char * name)90 int tc_x11source_is_display_name(const char *name)
91 {
92     if (name != NULL && strlen(name) != 0) {
93         uint32_t disp, screen;
94         int ret = sscanf(name, ":%u.%u", &disp, &screen);
95         if (ret == 2) {
96             /* looks like a display specifier */
97             return TC_TRUE;
98         }
99     }
100     return TC_FALSE;
101 }
102 
tc_x11source_probe(TCX11Source * handle,ProbeInfo * info)103 int tc_x11source_probe(TCX11Source *handle, ProbeInfo *info)
104 {
105     if (handle != NULL && info != NULL) {
106         info->width = handle->width;
107         info->height = handle->height;
108         info->codec = handle->out_fmt;
109         info->magic = TC_MAGIC_X11; /* enforce */
110         info->asr = 1; /* force 1:1 ASR (XXX) */
111         /* FPS/FRC MUST BE choosed by user; that's only a kind suggestion */
112         info->fps = 10.0;
113         tc_frc_code_from_value(&info->frc, info->fps);
114 
115         info->num_tracks = 0; /* no audio, here */
116         return 0;
117     }
118 
119     return 1;
120 }
121 
122 /*************************************************************************/
123 
tc_x11source_acquire_image_plain(TCX11Source * handle,uint8_t * data,int maxdata)124 static int tc_x11source_acquire_image_plain(TCX11Source *handle,
125                                            uint8_t *data, int maxdata)
126 {
127     int size = -1;
128 
129     /* but draw such areas if windows are opaque */
130     /* FIXME: switch to XCreateImage? */
131     handle->image = XGetImage(handle->dpy, handle->pix, 0, 0,
132                               handle->width, handle->height,
133                               AllPlanes, ZPixmap);
134 
135     if (handle->image == NULL || handle->image->data == NULL) {
136         tc_log_error(__FILE__, "cannot get X image");
137     } else {
138         size = (int)tc_video_frame_size(handle->image->width,
139                                         handle->image->height,
140                                         handle->out_fmt);
141         if (size <= maxdata) {
142             tcv_convert(handle->tcvhandle, handle->image->data, data,
143                         handle->image->width, handle->image->height,
144                         IMG_BGRA32, handle->conv_fmt);
145         } else {
146             size = 0;
147         }
148         XDestroyImage(handle->image);
149     }
150     return size;
151 }
152 
tc_x11source_fini_plain(TCX11Source * handle)153 static int tc_x11source_fini_plain(TCX11Source *handle)
154 {
155     return 0;
156 }
157 
tc_x11source_init_plain(TCX11Source * handle)158 static int tc_x11source_init_plain(TCX11Source *handle)
159 {
160     handle->acquire_image = tc_x11source_acquire_image_plain;
161     handle->fini = tc_x11source_fini_plain;
162     return 0;
163 }
164 
165 
166 /*************************************************************************/
167 
168 #ifdef HAVE_X11_SHM
169 
tc_x11source_acquire_image_shm(TCX11Source * handle,uint8_t * data,int maxdata)170 static int tc_x11source_acquire_image_shm(TCX11Source *handle,
171                                           uint8_t *data, int maxdata)
172 {
173     int size = -1;
174     Status ret;
175 
176     /* but draw such areas if windows are opaque */
177     ret = XShmGetImage(handle->dpy, handle->pix, handle->image,
178                        0, 0, AllPlanes);
179 
180     if (!ret || handle->image == NULL || handle->image->data == NULL) {
181         tc_log_error(__FILE__, "cannot get X image (using SHM)");
182     } else {
183         size = (int)tc_video_frame_size(handle->image->width,
184                                         handle->image->height,
185                                         handle->out_fmt);
186         if (size <= maxdata) {
187             tcv_convert(handle->tcvhandle, handle->image->data, data,
188                         handle->image->width, handle->image->height,
189                         IMG_BGRA32, handle->conv_fmt);
190         } else {
191             size = 0;
192         }
193     }
194     return size;
195 }
196 
tc_x11source_fini_shm(TCX11Source * handle)197 static int tc_x11source_fini_shm(TCX11Source *handle)
198 {
199     Status ret = XShmDetach(handle->dpy, &handle->shm_info);
200     if (!ret) { /* XXX */
201         tc_log_error(__FILE__, "failed to attach SHM from Xserver");
202         return -1;
203     }
204     XDestroyImage(handle->image);
205     handle->image = NULL;
206 
207     XSync(handle->dpy, False); /* XXX */
208     if (shmdt(handle->shm_info.shmaddr) != 0) {
209         tc_log_error(__FILE__, "failed to destroy shared memory segment");
210         return -1;
211     }
212     return 0;
213 }
214 
tc_x11source_init_shm(TCX11Source * handle)215 static int tc_x11source_init_shm(TCX11Source *handle)
216 {
217     Status ret;
218 
219     ret = XMatchVisualInfo(handle->dpy, handle->screen, handle->depth,
220                            DirectColor, &handle->vis_info);
221     if (!ret) {
222         tc_log_error(__FILE__, "Can't match visual information");
223         goto xshm_failed;
224     }
225     handle->image = XShmCreateImage(handle->dpy, handle->vis_info.visual,
226                                     handle->depth, ZPixmap,
227                                     NULL, &handle->shm_info,
228                                     handle->width, handle->height);
229     if (handle->image == NULL) {
230         tc_log_error(__FILE__, "XShmCreateImage failed.");
231         goto xshm_failed_image;
232     }
233     handle->shm_info.shmid = shmget(IPC_PRIVATE,
234                                     handle->image->bytes_per_line * handle->image->height,
235                                     IPC_CREAT | 0777);
236     if (handle->shm_info.shmid < 0) {
237         tc_log_error(__FILE__, "failed to create shared memory segment");
238         goto xshm_failed_image;
239     }
240     handle->shm_info.shmaddr = shmat(handle->shm_info.shmid, NULL, 0);
241     if (handle->shm_info.shmaddr == (void*)-1) {
242         tc_log_error(__FILE__, "failed to attach shared memory segment");
243         goto xshm_failed_image;
244     }
245 
246     shmctl(handle->shm_info.shmid, IPC_RMID, 0); /* XXX */
247 
248     handle->image->data = handle->shm_info.shmaddr;
249     handle->shm_info.readOnly = False;
250 
251     ret = XShmAttach(handle->dpy, &handle->shm_info);
252     if (!ret) {
253         tc_log_error(__FILE__, "failed to attach SHM to Xserver");
254         goto xshm_failed_image;
255     }
256 
257     XSync(handle->dpy, False);
258     handle->mode = TC_X11_MODE_SHM;
259     handle->acquire_image = tc_x11source_acquire_image_shm;
260     handle->fini = tc_x11source_fini_shm;
261 
262     return 0;
263 
264 xshm_failed_image:
265     XDestroyImage(handle->image);
266     handle->image = NULL;
267 xshm_failed:
268     return -1;
269 }
270 
271 /*************************************************************************/
272 
273 #endif /* X11_SHM */
274 
tc_x11source_map_format(TCX11Source * handle,uint32_t format)275 static int tc_x11source_map_format(TCX11Source *handle, uint32_t format)
276 {
277     int ret = -1;
278 
279     if (handle != NULL) {
280         ret = 0;
281         switch (format) {
282           case CODEC_RGB: /* compatibility */
283           case TC_CODEC_RGB:
284             handle->out_fmt = TC_CODEC_RGB;
285             handle->conv_fmt = IMG_RGB24;
286             if (verbose >= TC_DEBUG) {
287                 tc_log_info(__FILE__, "output colorspace: RGB24");
288             }
289             break;
290           case CODEC_YUV: /* compatibility */
291           case TC_CODEC_YUV420P:
292             handle->out_fmt = TC_CODEC_YUV420P;
293             handle->conv_fmt = IMG_YUV420P;
294             if (verbose >= TC_DEBUG) {
295                 tc_log_info(__FILE__, "output colorspace: YUV420P");
296             }
297             break;
298           case CODEC_YUV422: /* compatibility */
299           case TC_CODEC_YUV422P:
300             handle->out_fmt = TC_CODEC_YUV422P;
301             handle->conv_fmt = IMG_YUV422P;
302             if (verbose >= TC_DEBUG) {
303                 tc_log_info(__FILE__, "output colorspace: YUV4222");
304             }
305             break;
306           default:
307             tc_log_error(__FILE__, "unknown colorspace requested: 0x%x",
308                          format);
309             ret = -1;
310         }
311     }
312     return ret;
313 }
314 
tc_x11source_acquire(TCX11Source * handle,uint8_t * data,int maxdata)315 int tc_x11source_acquire(TCX11Source *handle, uint8_t *data, int maxdata)
316 {
317     int size = -1;
318 
319     if (handle == NULL || data == NULL || maxdata <= 0) {
320         tc_log_error(__FILE__, "x11source_acquire: wrong (NULL) parameters");
321         return size;
322     }
323 
324     XLockDisplay(handle->dpy);
325     /* OK, let's hack a bit our GraphicContext */
326     XSetSubwindowMode(handle->dpy, handle->gc, IncludeInferiors);
327     /* don't catch areas of windows covered by children windows */
328     XCopyArea(handle->dpy, handle->root, handle->pix, handle->gc,
329               0, 0, handle->width, handle->height, 0, 0);
330 
331     XSetSubwindowMode(handle->dpy, handle->gc, ClipByChildren);
332     /* but draw such areas if windows are opaque */
333 
334     size = handle->acquire_image(handle, data, maxdata);
335     if (size > 0) {
336         handle->acquire_cursor(handle, data, maxdata); /* cannot fail */
337     }
338     XUnlockDisplay(handle->dpy);
339     return size;
340 }
341 
tc_x11source_close(TCX11Source * handle)342 int tc_x11source_close(TCX11Source *handle)
343 {
344     if (handle != NULL) {
345         if (handle->dpy != NULL) {
346             int ret = handle->fini(handle);
347             if (ret != 0) {
348                 return ret;
349             }
350 
351             tcv_free(handle->tcvhandle);
352 
353             XFreePixmap(handle->dpy, handle->pix); /* XXX */
354             XFreeGC(handle->dpy, handle->gc); /* XXX */
355 
356             ret = XCloseDisplay(handle->dpy);
357             if (ret == 0) {
358                 handle->dpy = NULL;
359             } else {
360                 tc_log_error(__FILE__, "XCloseDisplay() failed: %i", ret);
361                 return -1;
362             }
363         }
364     }
365     return 0;
366 }
367 
tc_x11source_open(TCX11Source * handle,const char * display,int mode,uint32_t format)368 int tc_x11source_open(TCX11Source *handle, const char *display,
369                       int mode, uint32_t format)
370 {
371     XWindowAttributes winfo;
372     Status ret;
373     int err;
374 
375     if (handle == NULL) {
376         return 1;
377     }
378     err = tc_x11source_map_format(handle, format);
379     if (err != 0) {
380         return err;
381     }
382 
383     handle->mode = mode;
384     handle->dpy = XOpenDisplay(display);
385     if (handle->dpy == NULL) {
386         tc_log_error(__FILE__, "failed to open display %s",
387                      (display != NULL) ?display :"default");
388         goto open_failed;
389     }
390 
391     handle->screen = DefaultScreen(handle->dpy);
392     handle->root = RootWindow(handle->dpy, handle->screen);
393     /* Get the parameters of the root winfow */
394     ret = XGetWindowAttributes(handle->dpy, handle->root, &winfo);
395     if (!ret) {
396         tc_log_error(__FILE__, "failed to get root window attributes");
397         goto link_failed;
398     }
399 
400     handle->width = winfo.width;
401     handle->height = winfo.height;
402     handle->depth = winfo.depth;
403 
404     if (handle->depth != 24) { /* XXX */
405         tc_log_error(__FILE__, "Non-truecolor display depth"
406                                " not supported. Yet.");
407         goto link_failed;
408     }
409 
410     if (verbose >= TC_STATS) {
411         tc_log_info(__FILE__, "display properties: %ix%i@%i",
412                     handle->width, handle->height, handle->depth);
413     }
414 
415     handle->pix = XCreatePixmap(handle->dpy, handle->root,
416                                 handle->width, handle->height,
417                                 handle->depth); /* XXX */
418     if (!handle->pix) {
419         tc_log_error(__FILE__, "Can't allocate Pixmap");
420         goto pix_failed;
421     }
422 
423     handle->gc = XCreateGC(handle->dpy, handle->root, 0, 0);
424     /* FIXME: what about failures? */
425 
426     handle->tcvhandle = tcv_init();
427     if (!handle->tcvhandle)
428         goto tcv_failed;
429 
430     tc_x11source_init_cursor(handle); /* cannot fail */
431 
432 #ifdef HAVE_X11_SHM
433     if (XShmQueryExtension(handle->dpy) != 0
434       && (mode & TC_X11_MODE_SHM)) {
435         if (tc_x11source_init_shm(handle) < 0)
436             goto init_failed;
437     } else
438 #endif /* X11_SHM */
439     if (tc_x11source_init_plain(handle) < 0)
440         goto init_failed;
441     return 0;
442 
443   init_failed:
444     tcv_free(handle->tcvhandle);
445   tcv_failed:
446     XFreeGC(handle->dpy, handle->gc);
447     XFreePixmap(handle->dpy, handle->pix);
448   pix_failed:
449   link_failed:
450     XCloseDisplay(handle->dpy);
451   open_failed:
452     return -1;
453 }
454 
455 
456 #else /* HAVE_X11 */
457 
458 
tc_x11source_open(TCX11Source * handle,const char * display,int mode,uint32_t format)459 int tc_x11source_open(TCX11Source *handle, const char *display,
460                       int mode, uint32_t format)
461 {
462     tc_log_error(__FILE__, "X11 support unavalaible");
463     return -1;
464 }
465 
tc_x11source_close(TCX11Source * handle)466 int tc_x11source_close(TCX11Source *handle)
467 {
468     tc_log_error(__FILE__, "X11 support unavalaible");
469     return 0;
470 }
471 
tc_x11source_probe(TCX11Source * handle,ProbeInfo * info)472 int tc_x11source_probe(TCX11Source *handle, ProbeInfo *info)
473 {
474     tc_log_error(__FILE__, "X11 support unavalaible");
475     return -1;
476 }
477 
tc_x11source_acquire(TCX11Source * handle,uint8_t * data,int maxdata)478 int tc_x11source_acquire(TCX11Source *handle, uint8_t *data, int maxdata)
479 {
480     tc_log_error(__FILE__, "X11 support unavalaible");
481     return -1;
482 }
483 
tc_x11source_is_display_name(const char * name)484 int tc_x11source_is_display_name(const char *name)
485 {
486     return TC_FALSE;
487 }
488 
489 #endif /* HAVE_X11 */
490 
491 /*************************************************************************/
492 
493 /*
494  * Local variables:
495  *   c-file-style: "stroustrup"
496  *   c-file-offsets: ((case-label . *) (statement-case-intro . *))
497  *   indent-tabs-mode: nil
498  * End:
499  *
500  * vim: expandtab shiftwidth=4:
501  */
502