blob: fc66c5a9e82013e8c1acdcf893b7d0e2acea4c51 [file] [log] [blame]
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +00001/* Copyright (C) 2002-2003 RealVNC Ltd. All Rights Reserved.
2 * Copyright (C) 2004-2005 Constantin Kaplinsky. All Rights Reserved.
3 *
4 * This is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This software is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this software; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
17 * USA.
18 */
19//
20// Image.cxx
21//
22
23#include <stdio.h>
24#include <sys/types.h>
25
26#ifdef HAVE_MITSHM
27#include <sys/ipc.h>
28#include <sys/shm.h>
29#endif
30
31#include <rfb/LogWriter.h>
32#include <x0vncserver/Image.h>
33
34//
35// ImageCleanup is used to delete Image instances automatically on
36// program shutdown. This is important for shared memory images.
37//
38
39#include <list>
40
41class ImageCleanup {
42public:
43 std::list<Image *> images;
44
45 ~ImageCleanup()
46 {
47 while (!images.empty()) {
48 delete images.front();
49 }
50 }
51};
52
53ImageCleanup imageCleanup;
54
55//
56// Image class implementation.
57//
58
59static rfb::LogWriter vlog("Image");
60
61Image::Image(Display *d)
62 : xim(NULL), dpy(d), trueColor(true)
63{
64 imageCleanup.images.push_back(this);
65}
66
67Image::Image(Display *d, int width, int height)
68 : xim(NULL), dpy(d), trueColor(true)
69{
70 imageCleanup.images.push_back(this);
71 Init(width, height);
72}
73
74void Image::Init(int width, int height)
75{
76 Visual* vis = DefaultVisual(dpy, DefaultScreen(dpy));
77 trueColor = (vis->c_class == TrueColor);
78
79 xim = XCreateImage(dpy, vis, DefaultDepth(dpy, DefaultScreen(dpy)),
80 ZPixmap, 0, 0, width, height, BitmapPad(dpy), 0);
81
82 xim->data = (char *)malloc(xim->bytes_per_line * xim->height);
83 if (xim->data == NULL) {
84 vlog.error("malloc() failed");
85 exit(1);
86 }
87}
88
89Image::~Image()
90{
91 imageCleanup.images.remove(this);
92
93 // XDestroyImage will free xim->data if necessary
94 if (xim != NULL)
95 XDestroyImage(xim);
96}
97
98void Image::get(Window wnd, int x, int y)
99{
100 get(wnd, x, y, xim->width, xim->height);
101}
102
103void Image::get(Window wnd, int x, int y, int w, int h)
104{
105 XGetSubImage(dpy, wnd, x, y, w, h, AllPlanes, ZPixmap, xim, 0, 0);
106}
107
108//
109// Copying pixels from one image to another.
110//
111// FIXME: Use Point and Rect structures?
112// FIXME: Too many similar methods?
113//
114
115inline
116void Image::copyPixels(XImage *src,
117 int dst_x, int dst_y,
118 int src_x, int src_y,
119 int w, int h)
120{
121 const char *srcOffset =
122 src->data + (src_y * src->bytes_per_line +
123 src_x * (src->bits_per_pixel / 8));
124 char *dstOffset =
125 xim->data + (dst_y * xim->bytes_per_line +
126 dst_x * (xim->bits_per_pixel / 8));
127
128 int rowLength = w * (xim->bits_per_pixel / 8);
129
130 for (int i = 0; i < h ; i++) {
131 memcpy(dstOffset, srcOffset, rowLength);
132 srcOffset += src->bytes_per_line;
133 dstOffset += xim->bytes_per_line;
134 }
135}
136
137void Image::updateRect(XImage *src, int dst_x, int dst_y)
138{
139 // Limit width and height at destination image size.
140 int w = src->width;
141 if (dst_x + w > xim->width)
142 w = xim->width - dst_x;
143 int h = src->height;
144 if (dst_y + h > xim->height)
145 h = xim->height - dst_y;
146
147 copyPixels(src, dst_x, dst_y, 0, 0, w, h);
148}
149
150void Image::updateRect(Image *src, int dst_x, int dst_y)
151{
152 updateRect(src->xim, dst_x, dst_y);
153}
154
155void Image::updateRect(XImage *src, int dst_x, int dst_y, int w, int h)
156{
157 // Correct width and height if necessary.
158 if (w > src->width)
159 w = src->width;
160 if (dst_x + w > xim->width)
161 w = xim->width - dst_x;
162 if (h > src->height)
163 h = src->height;
164 if (dst_y + h > xim->height)
165 h = xim->height - dst_y;
166
167 copyPixels(src, dst_x, dst_y, 0, 0, w, h);
168}
169
170void Image::updateRect(Image *src, int dst_x, int dst_y, int w, int h)
171{
172 updateRect(src->xim, dst_x, dst_y, w, h);
173}
174
175void Image::updateRect(XImage *src, int dst_x, int dst_y,
176 int src_x, int src_y, int w, int h)
177{
178 // Correct width and height if necessary.
179 if (src_x + w > src->width)
180 w = src->width - src_x;
181 if (dst_x + w > xim->width)
182 w = xim->width - dst_x;
183 if (src_y + h > src->height)
184 h = src->height - src_y;
185 if (dst_y + h > xim->height)
186 h = xim->height - dst_y;
187
188 copyPixels(src, dst_x, dst_y, src_x, src_y, w, h);
189}
190
191void Image::updateRect(Image *src, int dst_x, int dst_y,
192 int src_x, int src_y, int w, int h)
193{
194 updateRect(src->xim, dst_x, dst_y, src_x, src_y, w, h);
195}
196
197#ifdef HAVE_MITSHM
198
199//
200// ShmImage class implementation.
201//
202
203static bool caughtShmError = false;
204
205static int ShmCreationXErrorHandler(Display *dpy, XErrorEvent *error)
206{
207 caughtShmError = true;
208 return 0;
209}
210
211ShmImage::ShmImage(Display *d)
212 : Image(d), shminfo(NULL)
213{
214}
215
216ShmImage::ShmImage(Display *d, int width, int height)
217 : Image(d), shminfo(NULL)
218{
219 Init(width, height);
220}
221
222// FIXME: Remove duplication of cleanup operations.
223void ShmImage::Init(int width, int height, const XVisualInfo *vinfo)
224{
225 int major, minor;
226 Bool pixmaps;
227
228 if (!XShmQueryVersion(dpy, &major, &minor, &pixmaps)) {
229 vlog.error("XShmQueryVersion() failed");
230 return;
231 }
232
233 Visual *visual;
234 int depth;
235
236 if (vinfo == NULL) {
237 visual = DefaultVisual(dpy, DefaultScreen(dpy));
238 depth = DefaultDepth(dpy, DefaultScreen(dpy));
239 } else {
240 visual = vinfo->visual;
241 depth = vinfo->depth;
242 }
243
244 trueColor = (visual->c_class == TrueColor);
245
246 shminfo = new XShmSegmentInfo;
247
248 xim = XShmCreateImage(dpy, visual, depth, ZPixmap, 0, shminfo,
249 width, height);
250 if (xim == NULL) {
251 vlog.error("XShmCreateImage() failed");
252 delete shminfo;
253 shminfo = NULL;
254 return;
255 }
256
257 shminfo->shmid = shmget(IPC_PRIVATE,
258 xim->bytes_per_line * xim->height,
259 IPC_CREAT|0777);
260 if (shminfo->shmid == -1) {
261 perror("shmget");
262 vlog.error("shmget() failed (%d bytes requested)",
263 int(xim->bytes_per_line * xim->height));
264 XDestroyImage(xim);
265 xim = NULL;
266 delete shminfo;
267 shminfo = NULL;
268 return;
269 }
270
271 shminfo->shmaddr = xim->data = (char *)shmat(shminfo->shmid, 0, 0);
272 if (shminfo->shmaddr == (char *)-1) {
273 perror("shmat");
274 vlog.error("shmat() failed (%d bytes requested)",
275 int(xim->bytes_per_line * xim->height));
276 shmctl(shminfo->shmid, IPC_RMID, 0);
277 XDestroyImage(xim);
278 xim = NULL;
279 delete shminfo;
280 shminfo = NULL;
281 return;
282 }
283
284 shminfo->readOnly = False;
285
286 XErrorHandler oldHdlr = XSetErrorHandler(ShmCreationXErrorHandler);
287 XShmAttach(dpy, shminfo);
288 XSync(dpy, False);
289 XSetErrorHandler(oldHdlr);
290 if (caughtShmError) {
291 vlog.error("XShmAttach() failed");
292 shmdt(shminfo->shmaddr);
293 shmctl(shminfo->shmid, IPC_RMID, 0);
294 XDestroyImage(xim);
295 xim = NULL;
296 delete shminfo;
297 shminfo = NULL;
298 return;
299 }
300}
301
302ShmImage::~ShmImage()
303{
304 // FIXME: Destroy image as described in MIT-SHM documentation.
305 if (shminfo != NULL) {
306 shmdt(shminfo->shmaddr);
307 shmctl(shminfo->shmid, IPC_RMID, 0);
308 delete shminfo;
309 }
310}
311
312void ShmImage::get(Window wnd, int x, int y)
313{
314 XShmGetImage(dpy, wnd, xim, x, y, AllPlanes);
315}
316
317void ShmImage::get(Window wnd, int x, int y, int w, int h)
318{
319 // FIXME: Use SHM for this as well?
320 XGetSubImage(dpy, wnd, x, y, w, h, AllPlanes, ZPixmap, xim, 0, 0);
321}
322
323#ifdef HAVE_READDISPLAY
324
325//
326// IrixOverlayShmImage class implementation.
327//
328
329IrixOverlayShmImage::IrixOverlayShmImage(Display *d)
330 : ShmImage(d), readDisplayBuf(NULL)
331{
332}
333
334IrixOverlayShmImage::IrixOverlayShmImage(Display *d, int width, int height)
335 : ShmImage(d), readDisplayBuf(NULL)
336{
337 Init(width, height);
338}
339
340void IrixOverlayShmImage::Init(int width, int height)
341{
342 // First determine the pixel format used by XReadDisplay.
343 XVisualInfo vinfo;
344 if (!getOverlayVisualInfo(&vinfo))
345 return;
346
347 // Create an SHM image of the same format.
348 ShmImage::Init(width, height, &vinfo);
349 if (xim == NULL)
350 return;
351
352 // FIXME: Check if the extension is available at run time.
353 readDisplayBuf = XShmCreateReadDisplayBuf(dpy, NULL, shminfo, width, height);
354}
355
356bool IrixOverlayShmImage::getOverlayVisualInfo(XVisualInfo *vinfo_ret)
357{
358 // First, get an image in the format returned by XReadDisplay.
359 unsigned long hints = 0, hints_ret;
360 XImage *testImage = XReadDisplay(dpy, DefaultRootWindow(dpy),
361 0, 0, 8, 8, hints, &hints_ret);
362 if (testImage == NULL)
363 return false;
364
365 // Fill in a template for matching visuals.
366 XVisualInfo tmpl;
367 tmpl.c_class = TrueColor;
368 tmpl.depth = 24;
369 tmpl.red_mask = testImage->red_mask;
370 tmpl.green_mask = testImage->green_mask;
371 tmpl.blue_mask = testImage->blue_mask;
372
373 // List fields in template that make sense.
374 long mask = (VisualClassMask |
375 VisualRedMaskMask |
376 VisualGreenMaskMask |
377 VisualBlueMaskMask);
378
379 // We don't need that image any more.
380 XDestroyImage(testImage);
381
382 // Now, get a list of matching visuals available.
383 int nVisuals;
384 XVisualInfo *vinfo = XGetVisualInfo(dpy, mask, &tmpl, &nVisuals);
385 if (vinfo == NULL || nVisuals <= 0) {
386 if (vinfo != NULL) {
387 XFree(vinfo);
388 }
389 return false;
390 }
391
392 // Use first visual from the list.
393 *vinfo_ret = vinfo[0];
394
395 XFree(vinfo);
396
397 return true;
398}
399
400IrixOverlayShmImage::~IrixOverlayShmImage()
401{
402 if (readDisplayBuf != NULL)
403 XShmDestroyReadDisplayBuf(readDisplayBuf);
404}
405
406void IrixOverlayShmImage::get(Window wnd, int x, int y)
407{
408 get(wnd, x, y, xim->width, xim->height);
409}
410
411void IrixOverlayShmImage::get(Window wnd, int x, int y, int w, int h)
412{
413 XRectangle rect;
414 unsigned long hints = XRD_TRANSPARENT | XRD_READ_POINTER;
415
416 rect.x = x;
417 rect.y = y;
418 rect.width = w;
419 rect.height = h;
420
421 XShmReadDisplayRects(dpy, wnd,
422 &rect, 1, readDisplayBuf, -x, -y,
423 hints, &hints);
424}
425
426#endif // HAVE_READDISPLAY
427#endif // HAVE_MITSHM
428
429#ifdef HAVE_SUN_OVL
430
431//
432// SolarisOverlayImage class implementation
433//
434
435SolarisOverlayImage::SolarisOverlayImage(Display *d)
436 : Image(d)
437{
438}
439
440SolarisOverlayImage::SolarisOverlayImage(Display *d, int width, int height)
441 : Image(d)
442{
443 Init(width, height);
444}
445
446void SolarisOverlayImage::Init(int width, int height)
447{
448 // FIXME: Check if the extension is available at run time.
449 // FIXME: Maybe just read a small (e.g. 8x8) screen area then
450 // reallocate xim->data[] and correct width and height?
451 xim = XReadScreen(dpy, DefaultRootWindow(dpy), 0, 0, width, height, True);
452 if (xim == NULL) {
453 vlog.error("XReadScreen() failed");
454 return;
455 }
456}
457
458SolarisOverlayImage::~SolarisOverlayImage()
459{
460}
461
462void SolarisOverlayImage::get(Window wnd, int x, int y)
463{
464 get(wnd, x, y, xim->width, xim->height);
465}
466
467void SolarisOverlayImage::get(Window wnd, int x, int y, int w, int h)
468{
469 XImage *tmp_xim = XReadScreen(dpy, wnd, x, y, w, h, True);
470 if (tmp_xim == NULL)
471 return;
472
473 updateRect(tmp_xim, 0, 0);
474
475 XDestroyImage(tmp_xim);
476}
477
478#endif // HAVE_SUN_OVL
479
480//
481// ImageFactory class implementation
482//
483// FIXME: Make ImageFactory always create images of the same class?
484//
485
486// Prepare useful shortcuts for compile-time options.
487#if defined(HAVE_READDISPLAY) && defined(HAVE_MITSHM)
488#define HAVE_SHM_READDISPLAY
489#endif
490#if defined(HAVE_SHM_READDISPLAY) || defined(HAVE_SUN_OVL)
491#define HAVE_OVERLAY_EXT
492#endif
493
494ImageFactory::ImageFactory(bool allowShm, bool allowOverlay)
495 : mayUseShm(allowShm), mayUseOverlay(allowOverlay)
496{
497}
498
499ImageFactory::~ImageFactory()
500{
501}
502
503Image *ImageFactory::newImage(Display *d, int width, int height)
504{
505 Image *image = NULL;
506
507 // First, try to create an image with overlay support.
508
509#ifdef HAVE_OVERLAY_EXT
510 if (mayUseOverlay) {
511#if defined(HAVE_SHM_READDISPLAY)
512 if (mayUseShm) {
513 image = new IrixOverlayShmImage(d, width, height);
514 if (image->xim != NULL) {
515 return image;
516 }
517 }
518#elif defined(HAVE_SUN_OVL)
519 image = new SolarisOverlayImage(d, width, height);
520 if (image->xim != NULL) {
521 return image;
522 }
523#endif
524 if (image != NULL) {
525 delete image;
526 vlog.error("Failed to create overlay image, trying other options");
527 }
528 }
529#endif // HAVE_OVERLAY_EXT
530
531 // Now, try to use shared memory image.
532
533#ifdef HAVE_MITSHM
534 if (mayUseShm) {
535 image = new ShmImage(d, width, height);
536 if (image->xim != NULL) {
537 return image;
538 }
539
540 delete image;
541 vlog.error("Failed to create SHM image, falling back to Xlib image");
542 }
543#endif // HAVE_MITSHM
544
545 // Fall back to Xlib image.
546
547 image = new Image(d, width, height);
548 return image;
549}