From http://www.jwz.org/xscreensaver/xscreensaver-5.37.tar.gz
[xscreensaver] / utils / xshm.c
1 /* xscreensaver, Copyright (c) 1993-2017 Jamie Zawinski <jwz@jwz.org>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10  */
11
12 /* The MIT-SHM (Shared Memory) extension is pretty tricky to use.
13    This file contains the common boiler-plate for creating a shared
14    XImage structure, and for making sure that the shared memory segments
15    get allocated and shut down cleanly.
16
17    This code currently deals only with shared XImages, not with shared Pixmaps.
18    It also doesn't use "completion events", but so far that doesn't seem to
19    be a problem (and I'm not entirely clear on when they would actually be
20    needed, anyway.)
21
22    If you don't have man pages for this extension, see
23    https://www.x.org/releases/current/doc/xextproto/shm.html
24
25    (This document seems not to ever remain available on the web in one place
26    for very long; you can search for it by the title, "MIT-SHM -- The MIT
27    Shared Memory Extension".)
28
29    To monitor the system's shared memory segments, run "ipcs -m".
30   */
31
32 #include "utils.h"
33
34 /* #define DEBUG */
35
36 #include <errno.h>              /* for perror() */
37
38 #ifdef HAVE_JWXYZ
39 # include "jwxyz.h"
40 #else
41 # include <X11/Xutil.h>         /* for XDestroyImage() */
42 #endif
43
44 #include "xshm.h"
45 #include "resources.h"          /* for get_string_resource() */
46 #include "thread_util.h"        /* for thread_malloc() */
47
48 #ifdef DEBUG
49 # include <X11/Xmu/Error.h>
50 #endif
51
52 extern char *progname;
53
54
55 /* The documentation for the XSHM extension implies that if the server
56    supports XSHM but is not the local machine, the XShm calls will return
57    False; but this turns out not to be the case.  Instead, the server
58    throws a BadAccess error.  So, we need to catch X errors around all
59    of our XSHM calls, sigh.
60  */
61
62 #ifdef HAVE_XSHM_EXTENSION
63
64 static Bool shm_got_x_error = False;
65 XErrorHandler old_handler = 0;
66 static int
67 shm_ehandler (Display *dpy, XErrorEvent *error)
68 {
69   shm_got_x_error = True;
70
71 #ifdef DEBUG
72   fprintf (stderr, "\n%s: ignoring X error from XSHM:\n", progname);
73   XmuPrintDefaultErrorMessage (dpy, error, stderr);
74   fprintf (stderr, "\n");
75 #endif
76
77   return 0;
78 }
79
80
81 #define CATCH_X_ERROR(DPY) do {                         \
82   XSync((DPY), False);                                  \
83   shm_got_x_error = False;                              \
84   if (old_handler != shm_ehandler)                      \
85     old_handler = XSetErrorHandler (shm_ehandler);      \
86 } while(0)
87
88 #define UNCATCH_X_ERROR(DPY) do {                       \
89   XSync((DPY), False);                                  \
90   if (old_handler)                                      \
91     XSetErrorHandler (old_handler);                     \
92     old_handler = 0;                                    \
93 } while(0)
94
95 #endif /* HAVE_XSHM_EXTENSION */
96
97
98 static void
99 print_error (int err)
100 {
101   fprintf(stderr, "%s: %s\n", progname, strerror(err));
102 }
103
104 static XImage *
105 create_fallback (Display *dpy, Visual *visual,
106                  unsigned int depth,
107                  int format, XShmSegmentInfo *shm_info,
108                  unsigned int width, unsigned int height)
109 {
110   XImage *image = XCreateImage (dpy, visual, depth, format, 0, NULL,
111                                 width, height, BitmapPad(dpy), 0);
112   shm_info->shmid = -1;
113
114   if (!image) {
115     print_error (ENOMEM);
116   } else {
117     /* Sometimes the XImage data needs to be aligned, such as for SIMD (SSE2
118        in Fireworkx), or multithreading (AnalogTV).
119      */
120     int error = thread_malloc ((void **)&image->data, dpy,
121                                image->height * image->bytes_per_line);
122     if (error) {
123       print_error (error);
124       XDestroyImage (image);
125       image = NULL;
126     } else {
127       memset (image->data, 0, image->height * image->bytes_per_line);
128     }
129   }
130
131   return image;
132 }
133
134
135 XImage *
136 create_xshm_image (Display *dpy, Visual *visual,
137                    unsigned int depth,
138                    int format, XShmSegmentInfo *shm_info,
139                    unsigned int width, unsigned int height)
140 {
141 #ifndef HAVE_XSHM_EXTENSION
142
143   return create_fallback (dpy, visual, depth, format, shm_info, width, height);
144
145 #else /* HAVE_XSHM_EXTENSION */
146
147   Status status;
148   XImage *image = 0;
149   if (!get_boolean_resource(dpy, "useSHM", "Boolean") ||
150       !XShmQueryExtension (dpy)) {
151     return create_fallback (dpy, visual, depth, format, shm_info,
152                             width, height);
153   }
154
155   CATCH_X_ERROR(dpy);
156   image = XShmCreateImage(dpy, visual, depth,
157                           format, NULL, shm_info, width, height);
158   UNCATCH_X_ERROR(dpy);
159   if (shm_got_x_error)
160     return create_fallback (dpy, visual, depth, format, shm_info,
161                             width, height);
162
163 #ifdef DEBUG
164   fprintf(stderr, "\n%s: XShmCreateImage(... %d, %d)\n", progname,
165           width, height);
166 #endif
167
168   shm_info->shmid = shmget(IPC_PRIVATE,
169                            image->bytes_per_line * image->height,
170                            IPC_CREAT | 0777);
171 #ifdef DEBUG
172   fprintf(stderr, "%s: shmget(IPC_PRIVATE, %d, IPC_CREAT | 0777) ==> %d\n",
173           progname, image->bytes_per_line * image->height, shm_info->shmid);
174 #endif
175
176   if (shm_info->shmid == -1)
177     {
178       char buf[1024];
179       sprintf (buf, "%s: shmget failed", progname);
180       perror(buf);
181       XDestroyImage (image);
182       image = 0;
183       XSync(dpy, False);
184     }
185   else
186     {
187       shm_info->readOnly = False;
188       image->data = shm_info->shmaddr = shmat(shm_info->shmid, 0, 0);
189
190 #ifdef DEBUG
191       fprintf(stderr, "%s: shmat(%d, 0, 0) ==> %d\n", progname,
192               shm_info->shmid, (int) image->data);
193 #endif
194
195       CATCH_X_ERROR(dpy);
196       status = XShmAttach(dpy, shm_info);
197       UNCATCH_X_ERROR(dpy);
198       if (shm_got_x_error)
199         status = False;
200
201       if (!status)
202         {
203           fprintf (stderr, "%s: XShmAttach failed!\n", progname);
204           XDestroyImage (image);
205           XSync(dpy, False);
206           shmdt (shm_info->shmaddr);
207           image = 0;
208         }
209 #ifdef DEBUG
210       else
211         fprintf(stderr, "%s: XShmAttach(dpy, shm_info) ==> True\n", progname);
212 #endif
213
214       XSync(dpy, False);
215
216       /* Delete the shared segment right now; the segment won't actually
217          go away until both the client and server have deleted it.  The
218          server will delete it as soon as the client disconnects, so we
219          should delete our side early in case of abnormal termination.
220          (And note that, in the context of xscreensaver, abnormal
221          termination is the rule rather than the exception, so this would
222          leak like a sieve if we didn't do this...)
223
224          #### Are we leaking anyway?  Perhaps because of the window of
225          opportunity between here and the XShmAttach call above, during
226          which we might be killed?  Do we need to establish a signal
227          handler for this case?
228        */
229       shmctl (shm_info->shmid, IPC_RMID, 0);
230
231 #ifdef DEBUG
232       fprintf(stderr, "%s: shmctl(%d, IPC_RMID, 0)\n\n", progname,
233               shm_info->shmid);
234 #endif
235     }
236
237   if (!image) {
238     return create_fallback (dpy, visual, depth, format, shm_info,
239                             width, height);
240   }
241
242   return image;
243
244 #endif /* HAVE_XSHM_EXTENSION */
245 }
246
247
248 Bool
249 put_xshm_image (Display *dpy, Drawable d, GC gc, XImage *image,
250                 int src_x, int src_y, int dest_x, int dest_y,
251                 unsigned int width, unsigned int height,
252                 XShmSegmentInfo *shm_info)
253 {
254 #ifdef HAVE_XSHM_EXTENSION
255   assert (shm_info); /* Don't just s/XShmPutImage/put_xshm_image/. */
256   if (shm_info->shmid != -1) {
257     /* XShmPutImage is asynchronous; the contents of the XImage must not be
258        modified until the server has placed the pixels on the screen and the
259        client has received an XShmCompletionEvent. Breaking this rule can cause
260        tearing. That said, put_xshm_image doesn't provide a send_event
261        parameter, so we're always breaking this rule. Not that it seems to
262        matter; everything (so far) looks fine without it.
263
264        ####: Add a send_event parameter. And fake it for XPutImage.
265      */
266     return XShmPutImage (dpy, d, gc, image, src_x, src_y, dest_x, dest_y,
267                          width, height, False);
268   }
269 #endif /* HAVE_XSHM_EXTENSION */
270
271   return XPutImage (dpy, d, gc, image, src_x, src_y, dest_x, dest_y,
272                     width, height);
273 }
274
275
276 Bool
277 get_xshm_image (Display *dpy, Drawable d, XImage *image, int x, int y,
278                 unsigned long plane_mask, XShmSegmentInfo *shm_info)
279 {
280 #ifdef HAVE_XSHM_EXTENSION
281   if (shm_info->shmid != -1) {
282     return XShmGetImage (dpy, d, image, x, y, plane_mask);
283   }
284 #endif /* HAVE_XSHM_EXTENSION */
285   return XGetSubImage (dpy, d, x, y, image->width, image->height, plane_mask,
286                        image->format, image, 0, 0) != NULL;
287 }
288
289
290 void
291 destroy_xshm_image (Display *dpy, XImage *image, XShmSegmentInfo *shm_info)
292 {
293 #ifdef HAVE_XSHM_EXTENSION
294   if (shm_info->shmid == -1) {
295 #endif /* HAVE_XSHM_EXTENSION */
296
297     /* Don't let XDestroyImage free image->data. */
298     thread_free (image->data);
299     image->data = NULL;
300     XDestroyImage (image);
301     return;
302
303 #ifdef HAVE_XSHM_EXTENSION
304   }
305
306   Status status;
307
308   CATCH_X_ERROR(dpy);
309   status = XShmDetach (dpy, shm_info);
310   UNCATCH_X_ERROR(dpy);
311   if (shm_got_x_error)
312     status = False;
313   if (!status)
314     fprintf (stderr, "%s: XShmDetach failed!\n", progname);
315 #ifdef DEBUG
316   else
317     fprintf (stderr, "%s: XShmDetach(dpy, shm_info) ==> True\n", progname);
318 #endif
319
320   XDestroyImage (image);
321   XSync(dpy, False);
322
323   status = shmdt (shm_info->shmaddr);
324
325   if (status != 0)
326     {
327       char buf[1024];
328       sprintf (buf, "%s: shmdt(0x%lx) failed", progname,
329                (unsigned long) shm_info->shmaddr);
330       perror(buf);
331     }
332 #ifdef DEBUG
333   else
334     fprintf (stderr, "%s: shmdt(shm_info->shmaddr) ==> 0\n", progname);
335 #endif
336
337   XSync(dpy, False);
338
339 #endif /* HAVE_XSHM_EXTENSION */
340 }