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