blob: 12da5495b725c6d7157c542bdf3dc4b538135466 [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
Constantin Kaplinskycffb6912007-09-03 10:17:19 +0000103void Image::get(Window wnd, int x, int y, int w, int h,
104 int dst_x, int dst_y)
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000105{
Constantin Kaplinskycffb6912007-09-03 10:17:19 +0000106 XGetSubImage(dpy, wnd, x, y, w, h, AllPlanes, ZPixmap, xim, dst_x, dst_y);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000107}
108
109//
110// Copying pixels from one image to another.
111//
112// FIXME: Use Point and Rect structures?
113// FIXME: Too many similar methods?
114//
115
116inline
117void Image::copyPixels(XImage *src,
118 int dst_x, int dst_y,
119 int src_x, int src_y,
120 int w, int h)
121{
122 const char *srcOffset =
123 src->data + (src_y * src->bytes_per_line +
124 src_x * (src->bits_per_pixel / 8));
125 char *dstOffset =
126 xim->data + (dst_y * xim->bytes_per_line +
127 dst_x * (xim->bits_per_pixel / 8));
128
129 int rowLength = w * (xim->bits_per_pixel / 8);
130
131 for (int i = 0; i < h ; i++) {
132 memcpy(dstOffset, srcOffset, rowLength);
133 srcOffset += src->bytes_per_line;
134 dstOffset += xim->bytes_per_line;
135 }
136}
137
138void Image::updateRect(XImage *src, int dst_x, int dst_y)
139{
140 // Limit width and height at destination image size.
141 int w = src->width;
142 if (dst_x + w > xim->width)
143 w = xim->width - dst_x;
144 int h = src->height;
145 if (dst_y + h > xim->height)
146 h = xim->height - dst_y;
147
148 copyPixels(src, dst_x, dst_y, 0, 0, w, h);
149}
150
151void Image::updateRect(Image *src, int dst_x, int dst_y)
152{
153 updateRect(src->xim, dst_x, dst_y);
154}
155
156void Image::updateRect(XImage *src, int dst_x, int dst_y, int w, int h)
157{
158 // Correct width and height if necessary.
159 if (w > src->width)
160 w = src->width;
161 if (dst_x + w > xim->width)
162 w = xim->width - dst_x;
163 if (h > src->height)
164 h = src->height;
165 if (dst_y + h > xim->height)
166 h = xim->height - dst_y;
167
168 copyPixels(src, dst_x, dst_y, 0, 0, w, h);
169}
170
171void Image::updateRect(Image *src, int dst_x, int dst_y, int w, int h)
172{
173 updateRect(src->xim, dst_x, dst_y, w, h);
174}
175
176void Image::updateRect(XImage *src, int dst_x, int dst_y,
177 int src_x, int src_y, int w, int h)
178{
179 // Correct width and height if necessary.
180 if (src_x + w > src->width)
181 w = src->width - src_x;
182 if (dst_x + w > xim->width)
183 w = xim->width - dst_x;
184 if (src_y + h > src->height)
185 h = src->height - src_y;
186 if (dst_y + h > xim->height)
187 h = xim->height - dst_y;
188
189 copyPixels(src, dst_x, dst_y, src_x, src_y, w, h);
190}
191
192void Image::updateRect(Image *src, int dst_x, int dst_y,
193 int src_x, int src_y, int w, int h)
194{
195 updateRect(src->xim, dst_x, dst_y, src_x, src_y, w, h);
196}
197
198#ifdef HAVE_MITSHM
199
200//
201// ShmImage class implementation.
202//
203
204static bool caughtShmError = false;
205
206static int ShmCreationXErrorHandler(Display *dpy, XErrorEvent *error)
207{
208 caughtShmError = true;
209 return 0;
210}
211
212ShmImage::ShmImage(Display *d)
213 : Image(d), shminfo(NULL)
214{
215}
216
217ShmImage::ShmImage(Display *d, int width, int height)
218 : Image(d), shminfo(NULL)
219{
220 Init(width, height);
221}
222
223// FIXME: Remove duplication of cleanup operations.
224void ShmImage::Init(int width, int height, const XVisualInfo *vinfo)
225{
226 int major, minor;
227 Bool pixmaps;
228
229 if (!XShmQueryVersion(dpy, &major, &minor, &pixmaps)) {
230 vlog.error("XShmQueryVersion() failed");
231 return;
232 }
233
234 Visual *visual;
235 int depth;
236
237 if (vinfo == NULL) {
238 visual = DefaultVisual(dpy, DefaultScreen(dpy));
239 depth = DefaultDepth(dpy, DefaultScreen(dpy));
240 } else {
241 visual = vinfo->visual;
242 depth = vinfo->depth;
243 }
244
245 trueColor = (visual->c_class == TrueColor);
246
247 shminfo = new XShmSegmentInfo;
248
249 xim = XShmCreateImage(dpy, visual, depth, ZPixmap, 0, shminfo,
250 width, height);
251 if (xim == NULL) {
252 vlog.error("XShmCreateImage() failed");
253 delete shminfo;
254 shminfo = NULL;
255 return;
256 }
257
258 shminfo->shmid = shmget(IPC_PRIVATE,
259 xim->bytes_per_line * xim->height,
260 IPC_CREAT|0777);
261 if (shminfo->shmid == -1) {
262 perror("shmget");
263 vlog.error("shmget() failed (%d bytes requested)",
264 int(xim->bytes_per_line * xim->height));
265 XDestroyImage(xim);
266 xim = NULL;
267 delete shminfo;
268 shminfo = NULL;
269 return;
270 }
271
272 shminfo->shmaddr = xim->data = (char *)shmat(shminfo->shmid, 0, 0);
273 if (shminfo->shmaddr == (char *)-1) {
274 perror("shmat");
275 vlog.error("shmat() failed (%d bytes requested)",
276 int(xim->bytes_per_line * xim->height));
277 shmctl(shminfo->shmid, IPC_RMID, 0);
278 XDestroyImage(xim);
279 xim = NULL;
280 delete shminfo;
281 shminfo = NULL;
282 return;
283 }
284
285 shminfo->readOnly = False;
286
287 XErrorHandler oldHdlr = XSetErrorHandler(ShmCreationXErrorHandler);
288 XShmAttach(dpy, shminfo);
289 XSync(dpy, False);
290 XSetErrorHandler(oldHdlr);
291 if (caughtShmError) {
292 vlog.error("XShmAttach() failed");
293 shmdt(shminfo->shmaddr);
294 shmctl(shminfo->shmid, IPC_RMID, 0);
295 XDestroyImage(xim);
296 xim = NULL;
297 delete shminfo;
298 shminfo = NULL;
299 return;
300 }
301}
302
303ShmImage::~ShmImage()
304{
305 // FIXME: Destroy image as described in MIT-SHM documentation.
306 if (shminfo != NULL) {
307 shmdt(shminfo->shmaddr);
308 shmctl(shminfo->shmid, IPC_RMID, 0);
309 delete shminfo;
310 }
311}
312
313void ShmImage::get(Window wnd, int x, int y)
314{
315 XShmGetImage(dpy, wnd, xim, x, y, AllPlanes);
316}
317
Constantin Kaplinskycffb6912007-09-03 10:17:19 +0000318void ShmImage::get(Window wnd, int x, int y, int w, int h,
319 int dst_x, int dst_y)
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000320{
321 // FIXME: Use SHM for this as well?
Constantin Kaplinskycffb6912007-09-03 10:17:19 +0000322 XGetSubImage(dpy, wnd, x, y, w, h, AllPlanes, ZPixmap, xim, dst_x, dst_y);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000323}
324
325#ifdef HAVE_READDISPLAY
326
327//
328// IrixOverlayShmImage class implementation.
329//
330
331IrixOverlayShmImage::IrixOverlayShmImage(Display *d)
332 : ShmImage(d), readDisplayBuf(NULL)
333{
334}
335
336IrixOverlayShmImage::IrixOverlayShmImage(Display *d, int width, int height)
337 : ShmImage(d), readDisplayBuf(NULL)
338{
339 Init(width, height);
340}
341
342void IrixOverlayShmImage::Init(int width, int height)
343{
344 // First determine the pixel format used by XReadDisplay.
345 XVisualInfo vinfo;
346 if (!getOverlayVisualInfo(&vinfo))
347 return;
348
349 // Create an SHM image of the same format.
350 ShmImage::Init(width, height, &vinfo);
351 if (xim == NULL)
352 return;
353
354 // FIXME: Check if the extension is available at run time.
355 readDisplayBuf = XShmCreateReadDisplayBuf(dpy, NULL, shminfo, width, height);
356}
357
358bool IrixOverlayShmImage::getOverlayVisualInfo(XVisualInfo *vinfo_ret)
359{
360 // First, get an image in the format returned by XReadDisplay.
361 unsigned long hints = 0, hints_ret;
362 XImage *testImage = XReadDisplay(dpy, DefaultRootWindow(dpy),
363 0, 0, 8, 8, hints, &hints_ret);
364 if (testImage == NULL)
365 return false;
366
367 // Fill in a template for matching visuals.
368 XVisualInfo tmpl;
369 tmpl.c_class = TrueColor;
370 tmpl.depth = 24;
371 tmpl.red_mask = testImage->red_mask;
372 tmpl.green_mask = testImage->green_mask;
373 tmpl.blue_mask = testImage->blue_mask;
374
375 // List fields in template that make sense.
376 long mask = (VisualClassMask |
377 VisualRedMaskMask |
378 VisualGreenMaskMask |
379 VisualBlueMaskMask);
380
381 // We don't need that image any more.
382 XDestroyImage(testImage);
383
384 // Now, get a list of matching visuals available.
385 int nVisuals;
386 XVisualInfo *vinfo = XGetVisualInfo(dpy, mask, &tmpl, &nVisuals);
387 if (vinfo == NULL || nVisuals <= 0) {
388 if (vinfo != NULL) {
389 XFree(vinfo);
390 }
391 return false;
392 }
393
394 // Use first visual from the list.
395 *vinfo_ret = vinfo[0];
396
397 XFree(vinfo);
398
399 return true;
400}
401
402IrixOverlayShmImage::~IrixOverlayShmImage()
403{
404 if (readDisplayBuf != NULL)
405 XShmDestroyReadDisplayBuf(readDisplayBuf);
406}
407
408void IrixOverlayShmImage::get(Window wnd, int x, int y)
409{
410 get(wnd, x, y, xim->width, xim->height);
411}
412
Constantin Kaplinskycffb6912007-09-03 10:17:19 +0000413void IrixOverlayShmImage::get(Window wnd, int x, int y, int w, int h,
414 int dst_x, int dst_y)
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000415{
416 XRectangle rect;
417 unsigned long hints = XRD_TRANSPARENT | XRD_READ_POINTER;
418
419 rect.x = x;
420 rect.y = y;
421 rect.width = w;
422 rect.height = h;
423
424 XShmReadDisplayRects(dpy, wnd,
Constantin Kaplinskycffb6912007-09-03 10:17:19 +0000425 &rect, 1, readDisplayBuf,
426 dst_x - x, dst_y - y,
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000427 hints, &hints);
428}
429
430#endif // HAVE_READDISPLAY
431#endif // HAVE_MITSHM
432
433#ifdef HAVE_SUN_OVL
434
435//
436// SolarisOverlayImage class implementation
437//
438
439SolarisOverlayImage::SolarisOverlayImage(Display *d)
440 : Image(d)
441{
442}
443
444SolarisOverlayImage::SolarisOverlayImage(Display *d, int width, int height)
445 : Image(d)
446{
447 Init(width, height);
448}
449
450void SolarisOverlayImage::Init(int width, int height)
451{
452 // FIXME: Check if the extension is available at run time.
453 // FIXME: Maybe just read a small (e.g. 8x8) screen area then
454 // reallocate xim->data[] and correct width and height?
455 xim = XReadScreen(dpy, DefaultRootWindow(dpy), 0, 0, width, height, True);
456 if (xim == NULL) {
457 vlog.error("XReadScreen() failed");
458 return;
459 }
460}
461
462SolarisOverlayImage::~SolarisOverlayImage()
463{
464}
465
466void SolarisOverlayImage::get(Window wnd, int x, int y)
467{
468 get(wnd, x, y, xim->width, xim->height);
469}
470
Constantin Kaplinskycffb6912007-09-03 10:17:19 +0000471void SolarisOverlayImage::get(Window wnd, int x, int y, int w, int h,
472 int dst_x, int dst_y)
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000473{
474 XImage *tmp_xim = XReadScreen(dpy, wnd, x, y, w, h, True);
475 if (tmp_xim == NULL)
476 return;
477
Constantin Kaplinskycffb6912007-09-03 10:17:19 +0000478 updateRect(tmp_xim, dst_x, dst_y);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000479
480 XDestroyImage(tmp_xim);
481}
482
483#endif // HAVE_SUN_OVL
484
485//
486// ImageFactory class implementation
487//
488// FIXME: Make ImageFactory always create images of the same class?
489//
490
491// Prepare useful shortcuts for compile-time options.
492#if defined(HAVE_READDISPLAY) && defined(HAVE_MITSHM)
493#define HAVE_SHM_READDISPLAY
494#endif
495#if defined(HAVE_SHM_READDISPLAY) || defined(HAVE_SUN_OVL)
496#define HAVE_OVERLAY_EXT
497#endif
498
499ImageFactory::ImageFactory(bool allowShm, bool allowOverlay)
500 : mayUseShm(allowShm), mayUseOverlay(allowOverlay)
501{
502}
503
504ImageFactory::~ImageFactory()
505{
506}
507
508Image *ImageFactory::newImage(Display *d, int width, int height)
509{
510 Image *image = NULL;
511
512 // First, try to create an image with overlay support.
513
514#ifdef HAVE_OVERLAY_EXT
515 if (mayUseOverlay) {
516#if defined(HAVE_SHM_READDISPLAY)
517 if (mayUseShm) {
518 image = new IrixOverlayShmImage(d, width, height);
519 if (image->xim != NULL) {
520 return image;
521 }
522 }
523#elif defined(HAVE_SUN_OVL)
524 image = new SolarisOverlayImage(d, width, height);
525 if (image->xim != NULL) {
526 return image;
527 }
528#endif
529 if (image != NULL) {
530 delete image;
531 vlog.error("Failed to create overlay image, trying other options");
532 }
533 }
534#endif // HAVE_OVERLAY_EXT
535
536 // Now, try to use shared memory image.
537
538#ifdef HAVE_MITSHM
539 if (mayUseShm) {
540 image = new ShmImage(d, width, height);
541 if (image->xim != NULL) {
542 return image;
543 }
544
545 delete image;
546 vlog.error("Failed to create SHM image, falling back to Xlib image");
547 }
548#endif // HAVE_MITSHM
549
550 // Fall back to Xlib image.
551
552 image = new Image(d, width, height);
553 return image;
554}