1 /* xscreensaver, Copyright (c) 1992-2010 Jamie Zawinski <jwz@jwz.org>
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
12 /* This is the OSX implementation of desktop-grabbing and image-loading.
17 #import <Cocoa/Cocoa.h>
19 #import "grabscreen.h"
25 #ifndef MAC_OS_X_VERSION_10_6
26 # define MAC_OS_X_VERSION_10_6 1060 /* undefined in 10.4 SDK, grr */
29 #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5
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.
40 copy_framebuffer_to_ximage (CGDirectDisplayID cgdpy, XImage *xim,
41 int window_x, int window_y)
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);
51 int ximw = xim->width;
52 int ximh = xim->height;
54 uint32_t *odata = (uint32_t *) xim->data;
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);
71 if (spp != 3) abort();
72 if (bps != 5) abort();
73 for (y = 0; y < ximh; y++) {
74 uint16_t *ip = (uint16_t *) data;
76 for (x = 0; x < ximw; x++) {
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);
95 /* Get the current palette of the display. */
96 CGDirectPaletteRef pal = CGPaletteCreateWithDisplay (cgdpy);
98 /* Map it to 32bpp pixels */
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;
109 for (y = 0; y < ximh; y++) {
110 unsigned char *ip = data;
112 for (x = 0; x < ximw; x++) {
113 *odata++ = map[*ip++];
117 CGPaletteRelease (pal);
128 /* Loads an image into the Drawable, returning once the image is loaded.
131 osx_grab_desktop_image (Screen *screen, Window xwindow, Drawable drawable)
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;
140 // Figure out where this window is on the screen.
142 XGetWindowAttributes (dpy, xwindow, &xgwa);
143 XTranslateCoordinates (dpy, xwindow, RootWindowOfScreen (screen), 0, 0,
144 &window_x, &window_y, &unused);
146 // Use the size of the Drawable, not the Window.
150 unsigned int w, h, bbw, d;
151 XGetGeometry (dpy, drawable, &r, &x, &y, &w, &h, &bbw, &d);
156 // Create a tmp ximage to hold the screen data.
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);
163 // Find the address in the frame buffer of the top left of this window.
165 CGDirectDisplayID cgdpy = 0;
168 // #### this isn't quite right for screen 2: it's offset slightly.
172 CGGetDisplaysWithPoint (p, 1, &cgdpy, &n);
176 // Paint a transparent "hole" in this window.
178 BOOL oopaque = [nswindow isOpaque];
179 [nswindow setOpaque:NO];
181 [[NSColor clearColor] set];
182 NSRectFill ([nsview frame]);
183 [[nswindow graphicsContext] flushGraphics];
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);
191 // #### So let's try waiting for the vertical blank instead...
192 // Nope, that doesn't work.
194 // CGDisplayWaitForBeamPositionOutsideLines (cgdpy, 0,
195 // window_y + [nswindow frame].size.height);
197 // #### Ok, try a busy-wait?
201 // #### Ok, just fuckin' sleep!
206 // Pull the bits out of the frame buffer.
208 copy_framebuffer_to_ximage (cgdpy, xim, window_x, window_y);
210 // CGDisplayRelease (cgdpy);
212 // Make the window visible again.
214 [nswindow setOpaque:oopaque];
216 // Splat the XImage onto the target drawable (probably the window)
217 // and free the bits.
220 XPutImage (dpy, drawable, gc, xim, 0, 0, 0, 0, xim->width, xim->height);
225 #else /* MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
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.
234 /* Loads an image into the Drawable, returning once the image is loaded.
237 osx_grab_desktop_image (Screen *screen, Window xwindow, Drawable drawable)
239 Display *dpy = DisplayOfScreen (screen);
240 NSView *nsview = jwxyz_window_view (xwindow);
241 XWindowAttributes xgwa;
242 int window_x, window_y;
245 // Figure out where this window is on the screen.
247 XGetWindowAttributes (dpy, xwindow, &xgwa);
248 XTranslateCoordinates (dpy, xwindow, RootWindowOfScreen (screen), 0, 0,
249 &window_x, &window_y, &unused);
251 // Grab only the rectangle of the screen underlying this window.
254 cgrect.origin.x = window_x;
255 cgrect.origin.y = window_y;
256 cgrect.size.width = xgwa.width;
257 cgrect.size.height = xgwa.height;
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
267 // save our current level so we can restore it later
268 int oldLevel = [[nsview window] level];
270 [[nsview window] setLevel:CGWindowLevelForKey(kCGPopUpMenuWindowLevelKey)];
272 // Grab a screen shot of those windows below this one
273 // (hey, X11 can't do that!)
276 CGWindowListCreateImage (cgrect,
277 kCGWindowListOptionOnScreenBelowWindow,
278 [[nsview window] windowNumber],
279 kCGWindowImageDefault);
281 // put us back above the login windows so the screensaver is visible.
282 [[nsview window] setLevel:oldLevel];
284 // Render the grabbed CGImage into the Drawable.
286 jwxyz_draw_NSImage_or_CGImage (DisplayOfScreen (screen), drawable,
287 False, img, NULL, 0);
288 CGImageRelease (img);
292 #endif /* 10.5+ code */
295 /* Returns the EXIF rotation property of the image, if any.
298 exif_rotation (const char *filename)
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.
307 # if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 /* 10.6 SDK */
309 /* When we have compiled against the 10.6 SDK, we know that we are
310 running on a 10.6 or later system.
314 # else /* Compiled against 10.5 SDK or earlier */
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.
321 if ([NSImage instancesRespondToSelector:
322 @selector(initWithDataIgnoringOrientation:)])
325 /* Otherwise, go ahead and figure out what the rotational characteristics
326 of this image are. */
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?
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;
342 NSDictionary *props = (NSDictionary *)
343 CGImageSourceCopyPropertiesAtIndex (cgimg, 0, NULL);
344 int rot = [[props objectForKey:@"Orientation"] intValue];
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.
360 osx_load_image_file (Screen *screen, Window xwindow, Drawable drawable,
361 const char *filename, XRectangle *geom_ret)
363 NSImage *img = [[NSImage alloc] initWithContentsOfFile:
364 [NSString stringWithCString:filename
365 encoding:NSUTF8StringEncoding]];
369 jwxyz_draw_NSImage_or_CGImage (DisplayOfScreen (screen), drawable,
371 exif_rotation (filename));