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