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