http://www.jwz.org/xscreensaver/xscreensaver-5.13.tar.gz
[xscreensaver] / OSX / osxgrabscreen.m
1 /* xscreensaver, Copyright (c) 1992-2011 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 /* This is the OSX implementation of desktop-grabbing and image-loading.
13    This code is invoked by "utils/grabclient.c", which is linked directly
14    in to each screen saver bundle.
15
16    X11-based builds of the savers do not use this code (even on MacOS).
17    This is used only by the Cocoa build of the savers.
18  */
19
20 #import <stdlib.h>
21 #import <stdint.h>
22 #import <Cocoa/Cocoa.h>
23 #import "jwxyz.h"
24 #import "grabscreen.h"
25 #import "colorbars.h"
26 #import "resources.h"
27 #import "usleep.h"
28
29
30 #ifndef  MAC_OS_X_VERSION_10_6
31 # define MAC_OS_X_VERSION_10_6 1060  /* undefined in 10.4 SDK, grr */
32 #endif
33
34 #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5
35
36      /* 10.4 code.
37
38         This version of the code works on 10.4, but is flaky.  There is
39         a better way to do it on 10.5 and newer, but taking this path,
40         then we are being compiled against the 10.4 SDK instead of the
41         10.5 SDK, and the newer API is not available to us.
42       */
43
44 static void
45 copy_framebuffer_to_ximage (CGDirectDisplayID cgdpy, XImage *xim,
46                             int window_x, int window_y)
47 {
48   unsigned char *data = (unsigned char *) 
49     CGDisplayAddressForPosition (cgdpy, window_x, window_y);
50   int bpp = CGDisplayBitsPerPixel (cgdpy);
51   int spp = CGDisplaySamplesPerPixel (cgdpy);
52   int bps = CGDisplayBitsPerSample (cgdpy);
53   int bpr = CGDisplayBytesPerRow (cgdpy);
54
55   int y;
56   int ximw = xim->width;
57   int ximh = xim->height;
58
59   uint32_t *odata = (uint32_t *) xim->data;
60
61   switch (bpp) {
62   case 32:
63     if (spp != 3) abort();
64     if (bps != 8) abort();
65     int xwpl = xim->bytes_per_line/4;
66     for (y = 0; y < ximh; y++) {
67       // We can do this because the frame buffer and XImage are both ARGB 32.
68       // Both PPC and Intel use ARGB, viewed in word order (not byte-order).
69       memcpy (odata, data, ximw * 4);
70       odata += xwpl;
71       data += bpr;
72     }
73     break;
74
75   case 16:
76     if (spp != 3) abort();
77     if (bps != 5) abort();
78     for (y = 0; y < ximh; y++) {
79       uint16_t *ip = (uint16_t *) data;
80       int x;
81       for (x = 0; x < ximw; x++) {
82         uint16_t p = *ip++;
83         // This should be ok on both PPC and Intel (ARGB, word order)
84         unsigned char r = (p >> 10) & 0x1F;
85         unsigned char g = (p >>  5) & 0x1F;
86         unsigned char b = (p      ) & 0x1F;
87         r = (r << 3) | (r >> 2);
88         g = (g << 3) | (g >> 2);
89         b = (b << 3) | (b >> 2);
90         uint32_t pixel = 0xFF000000 | (r << 16) | (g << 8) | b;
91         // XPutPixel (xim, x, y, pixel);
92         *odata++ = pixel;
93       }
94       data += bpr;
95     }
96     break;
97
98   case 8:
99     {
100       /* Get the current palette of the display. */
101       CGDirectPaletteRef pal = CGPaletteCreateWithDisplay (cgdpy);
102
103       /* Map it to 32bpp pixels */
104       uint32_t map[256];
105       for (y = 0; y < 256; y++) {
106         CGDeviceColor c = CGPaletteGetColorAtIndex (pal, y);
107         unsigned char r = c.red   * 255.0;
108         unsigned char g = c.green * 255.0;
109         unsigned char b = c.blue  * 255.0;
110         uint32_t pixel = 0xFF000000 | (r << 16) | (g << 8) | b;
111         map[y] = pixel;
112       }
113
114       for (y = 0; y < ximh; y++) {
115         unsigned char *ip = data;
116         int x;
117         for (x = 0; x < ximw; x++) {
118           *odata++ = map[*ip++];
119         }
120         data += bpr;
121       }
122       CGPaletteRelease (pal);
123     }
124     break;
125
126   default:
127     abort();
128     break;
129   }
130 }
131
132
133 /* Loads an image into the Drawable, returning once the image is loaded.
134  */
135 void
136 osx_grab_desktop_image (Screen *screen, Window xwindow, Drawable drawable)
137 {
138   Display *dpy = DisplayOfScreen (screen);
139   NSView *nsview = jwxyz_window_view (xwindow);
140   NSWindow *nswindow = [nsview window];
141   XWindowAttributes xgwa;
142   int window_x, window_y;
143   Window unused;
144
145   // Figure out where this window is on the screen.
146   //
147   XGetWindowAttributes (dpy, xwindow, &xgwa);
148   XTranslateCoordinates (dpy, xwindow, RootWindowOfScreen (screen), 0, 0, 
149                          &window_x, &window_y, &unused);
150
151   // Use the size of the Drawable, not the Window.
152   {
153     Window r;
154     int x, y;
155     unsigned int w, h, bbw, d;
156     XGetGeometry (dpy, drawable, &r, &x, &y, &w, &h, &bbw, &d);
157     xgwa.width = w;
158     xgwa.height = h;
159   }
160
161   // Create a tmp ximage to hold the screen data.
162   //
163   XImage *xim = XCreateImage (dpy, xgwa.visual, 32, ZPixmap, 0, 0,
164                               xgwa.width, xgwa.height, 8, 0);
165   xim->data = (char *) malloc (xim->height * xim->bytes_per_line);
166
167
168   // Find the address in the frame buffer of the top left of this window.
169   //
170   CGDirectDisplayID cgdpy = 0;
171   {
172     CGPoint p;
173     // #### this isn't quite right for screen 2: it's offset slightly.
174     p.x = window_x;
175     p.y = window_y;
176     CGDisplayCount n;
177     CGGetDisplaysWithPoint (p, 1, &cgdpy, &n);
178     if (!cgdpy) abort();
179   }
180
181   // Paint a transparent "hole" in this window.
182   //
183   BOOL oopaque = [nswindow isOpaque];
184   [nswindow setOpaque:NO];
185
186   [[NSColor clearColor] set];
187   NSRectFill ([nsview frame]);
188   [[nswindow graphicsContext] flushGraphics];
189
190
191   // Without this, we get a dozen black scanlines at the top.
192   // #### But with this, the screen saver loops, because calling this
193   //      seems to implicitly mark the display as non-idle!
194   // CGDisplayCaptureWithOptions (cgdpy, kCGCaptureNoFill);
195
196   // #### So let's try waiting for the vertical blank instead...
197   //      Nope, that doesn't work.
198   //
199   // CGDisplayWaitForBeamPositionOutsideLines (cgdpy, 0,
200   //   window_y + [nswindow frame].size.height);
201
202   // #### Ok, try a busy-wait?
203   //      Nope.
204   //
205
206   // #### Ok, just fuckin' sleep!
207   //
208   usleep (100000);
209
210
211   // Pull the bits out of the frame buffer.
212   //
213   copy_framebuffer_to_ximage (cgdpy, xim, window_x, window_y);
214
215   // CGDisplayRelease (cgdpy);
216
217   // Make the window visible again.
218   //
219   [nswindow setOpaque:oopaque];
220
221   // Splat the XImage onto the target drawable (probably the window)
222   // and free the bits.
223   //
224   XGCValues gcv;
225   GC gc = XCreateGC (dpy, drawable, 0, &gcv);
226   XPutImage (dpy, drawable, gc, xim, 0, 0, 0, 0, xim->width, xim->height);
227   XFreeGC (dpy, gc);
228   XDestroyImage (xim);
229 }
230
231
232 #else /* MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
233
234          10.5+ code.
235
236          This version of the code is simpler and more reliable, but
237          uses an API that only exist on 10.5 and newer, so we can only
238          use it if when being compiled against the 10.5 SDK or later.
239        */
240
241 /* Loads an image into the Drawable, returning once the image is loaded.
242  */
243 void
244 osx_grab_desktop_image (Screen *screen, Window xwindow, Drawable drawable)
245 {
246   Display *dpy = DisplayOfScreen (screen);
247   NSView *nsview = jwxyz_window_view (xwindow);
248   XWindowAttributes xgwa;
249   int window_x, window_y;
250   Window unused;
251
252   // Figure out where this window is on the screen.
253   //
254   XGetWindowAttributes (dpy, xwindow, &xgwa);
255   XTranslateCoordinates (dpy, xwindow, RootWindowOfScreen (screen), 0, 0, 
256                          &window_x, &window_y, &unused);
257
258   // Grab only the rectangle of the screen underlying this window.
259   //
260   CGRect cgrect;
261   cgrect.origin.x    = window_x;
262   cgrect.origin.y    = window_y;
263   cgrect.size.width  = xgwa.width;
264   cgrect.size.height = xgwa.height;
265
266   /* If a password is required to unlock the screen, a large black
267      window will be on top of all of the desktop windows by the time
268      we reach here, making the screen-grab rather uninteresting.  If
269      we move ourselves temporarily below the login-window windows
270      before capturing the image, we capture the real desktop as
271      intended.
272    */
273
274   // save our current level so we can restore it later
275   int oldLevel = [[nsview window] level]; 
276
277   [[nsview window] setLevel:CGWindowLevelForKey(kCGPopUpMenuWindowLevelKey)];
278
279   // Grab a screen shot of those windows below this one
280   // (hey, X11 can't do that!)
281   //
282   CGImageRef img = 
283     CGWindowListCreateImage (cgrect,
284                              kCGWindowListOptionOnScreenBelowWindow,
285                              [[nsview window] windowNumber],
286                              kCGWindowImageDefault);
287
288   // put us back above the login windows so the screensaver is visible.
289   [[nsview window] setLevel:oldLevel];
290
291   // Render the grabbed CGImage into the Drawable.
292   if (img) {
293     jwxyz_draw_NSImage_or_CGImage (DisplayOfScreen (screen), drawable, 
294                                    False, img, NULL, 0);
295     CGImageRelease (img);
296   }
297 }
298
299 #endif /* 10.5+ code */
300
301
302 /* Returns the EXIF rotation property of the image, if any.
303  */
304 static int
305 exif_rotation (const char *filename)
306 {
307   /* As of 10.6, NSImage rotates according to EXIF by default:
308      http://developer.apple.com/mac/library/releasenotes/cocoa/appkit.html
309      So this function should return -1 when *running* on 10.6 systems.
310      But when running against older systems, we need to examine the image
311      to figure out its rotation.
312    */
313
314 # if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6  /* 10.6 SDK */
315
316   /* When we have compiled against the 10.6 SDK, we know that we are 
317      running on a 10.6 or later system.
318    */
319   return -1;
320
321 # else /* Compiled against 10.5 SDK or earlier */
322
323   /* If this selector exists, then we are running against a 10.6 runtime
324      that does automatic EXIF rotation (despite the fact that we were
325      compiled against the 10.5 or earlier SDK).  So in that case, this
326      function should no-op.
327    */
328   if ([NSImage instancesRespondToSelector:
329                  @selector(initWithDataIgnoringOrientation:)])
330     return -1;
331
332   /* Otherwise, go ahead and figure out what the rotational characteristics
333      of this image are. */
334
335
336
337   /* This is a ridiculous amount of rigamarole to go through, but for some
338      reason the "Orientation" tag does not exist in the "NSImageEXIFData"
339      dictionary inside the NSBitmapImageRep of the NSImage.  Several other
340      EXIF tags are there (e.g., shutter speed) but not orientation.  WTF?
341    */
342   CFStringRef s = CFStringCreateWithCString (NULL, filename, 
343                                              kCFStringEncodingUTF8);
344   CFURLRef url = CFURLCreateWithFileSystemPath (NULL, s, 
345                                                 kCFURLPOSIXPathStyle, 0);
346   CGImageSourceRef cgimg = CGImageSourceCreateWithURL (url, NULL);
347   if (! cgimg) return -1;
348
349   NSDictionary *props = (NSDictionary *)
350     CGImageSourceCopyPropertiesAtIndex (cgimg, 0, NULL);
351   int rot = [[props objectForKey:@"Orientation"] intValue];
352   CFRelease (cgimg);
353   CFRelease (url);
354   CFRelease (s);
355   return rot;
356
357 # endif /* 10.5 */
358 }
359
360
361 /* Loads an image file and splats it onto the drawable.
362    The image is drawn as large as possible while preserving its aspect ratio.
363    If geom_ret is provided, the actual rectangle the rendered image takes
364    up will be returned there.
365  */
366 Bool
367 osx_load_image_file (Screen *screen, Window xwindow, Drawable drawable,
368                      const char *filename, XRectangle *geom_ret)
369 {
370   NSImage *img = [[NSImage alloc] initWithContentsOfFile:
371                                     [NSString stringWithCString:filename
372                                               encoding:NSUTF8StringEncoding]];
373   if (!img)
374     return False;
375
376   jwxyz_draw_NSImage_or_CGImage (DisplayOfScreen (screen), drawable, 
377                                  True, img, geom_ret, 
378                                  exif_rotation (filename));
379   [img release];
380   return True;
381 }
382