1 /* xscreensaver, Copyright (c) 1992-2017 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.
13 This code is invoked by "utils/grabclient.c", which is linked directly
14 in to each screen saver bundle.
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.
23 # import <Cocoa/Cocoa.h>
25 # import "SaverRunner.h"
27 #import "jwxyz-cocoa.h"
28 #import "grabscreen.h"
35 # define NSImage UIImage
39 #ifndef MAC_OS_X_VERSION_10_6
40 # define MAC_OS_X_VERSION_10_6 1060 /* undefined in 10.4 SDK, grr */
43 #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5
47 This version of the code works on 10.4, but is flaky. There is
48 a better way to do it on 10.5 and newer, but taking this path,
49 then we are being compiled against the 10.4 SDK instead of the
50 10.5 SDK, and the newer API is not available to us.
54 copy_framebuffer_to_ximage (CGDirectDisplayID cgdpy, XImage *xim,
55 int window_x, int window_y)
57 unsigned char *data = (unsigned char *)
58 CGDisplayAddressForPosition (cgdpy, window_x, window_y);
59 int bpp = CGDisplayBitsPerPixel (cgdpy);
60 int spp = CGDisplaySamplesPerPixel (cgdpy);
61 int bps = CGDisplayBitsPerSample (cgdpy);
62 int bpr = CGDisplayBytesPerRow (cgdpy);
65 int ximw = xim->width;
66 int ximh = xim->height;
68 uint32_t *odata = (uint32_t *) xim->data;
72 if (spp != 3) abort();
73 if (bps != 8) abort();
74 int xwpl = xim->bytes_per_line/4;
75 for (y = 0; y < ximh; y++) {
76 // We can do this because the frame buffer and XImage are both ARGB 32.
77 // Both PPC and Intel use ARGB, viewed in word order (not byte-order).
78 memcpy (odata, data, ximw * 4);
85 if (spp != 3) abort();
86 if (bps != 5) abort();
87 for (y = 0; y < ximh; y++) {
88 uint16_t *ip = (uint16_t *) data;
90 for (x = 0; x < ximw; x++) {
92 // This should be ok on both PPC and Intel (ARGB, word order)
93 unsigned char r = (p >> 10) & 0x1F;
94 unsigned char g = (p >> 5) & 0x1F;
95 unsigned char b = (p ) & 0x1F;
96 r = (r << 3) | (r >> 2);
97 g = (g << 3) | (g >> 2);
98 b = (b << 3) | (b >> 2);
99 uint32_t pixel = 0xFF000000 | (r << 16) | (g << 8) | b;
100 // XPutPixel (xim, x, y, pixel);
109 /* Get the current palette of the display. */
110 CGDirectPaletteRef pal = CGPaletteCreateWithDisplay (cgdpy);
112 /* Map it to 32bpp pixels */
114 for (y = 0; y < 256; y++) {
115 CGDeviceColor c = CGPaletteGetColorAtIndex (pal, y);
116 unsigned char r = c.red * 255.0;
117 unsigned char g = c.green * 255.0;
118 unsigned char b = c.blue * 255.0;
119 uint32_t pixel = 0xFF000000 | (r << 16) | (g << 8) | b;
123 for (y = 0; y < ximh; y++) {
124 unsigned char *ip = data;
126 for (x = 0; x < ximw; x++) {
127 *odata++ = map[*ip++];
131 CGPaletteRelease (pal);
142 /* Loads an image into the Drawable, returning once the image is loaded.
145 osx_grab_desktop_image (Screen *screen, Window xwindow, Drawable drawable,
146 XRectangle *geom_ret)
148 Display *dpy = DisplayOfScreen (screen);
149 NSView *nsview = jwxyz_window_view (xwindow);
150 NSWindow *nswindow = [nsview window];
151 XWindowAttributes xgwa;
152 int window_x, window_y;
155 // Figure out where this window is on the screen.
157 XGetWindowAttributes (dpy, xwindow, &xgwa);
158 XTranslateCoordinates (dpy, xwindow, RootWindowOfScreen (screen), 0, 0,
159 &window_x, &window_y, &unused);
161 // Use the size of the Drawable, not the Window.
165 unsigned int w, h, bbw, d;
166 XGetGeometry (dpy, drawable, &r, &x, &y, &w, &h, &bbw, &d);
171 // Create a tmp ximage to hold the screen data.
173 XImage *xim = XCreateImage (dpy, xgwa.visual, 32, ZPixmap, 0, 0,
174 xgwa.width, xgwa.height, 8, 0);
175 xim->data = (char *) malloc (xim->height * xim->bytes_per_line);
178 // Find the address in the frame buffer of the top left of this window.
180 CGDirectDisplayID cgdpy = 0;
183 // #### this isn't quite right for screen 2: it's offset slightly.
187 CGGetDisplaysWithPoint (p, 1, &cgdpy, &n);
191 // Paint a transparent "hole" in this window.
193 BOOL oopaque = [nswindow isOpaque];
194 [nswindow setOpaque:NO];
196 [[NSColor clearColor] set];
197 NSRectFill ([nsview frame]);
198 [[nswindow graphicsContext] flushGraphics];
201 // Without this, we get a dozen black scanlines at the top.
202 // #### But with this, the screen saver loops, because calling this
203 // seems to implicitly mark the display as non-idle!
204 // CGDisplayCaptureWithOptions (cgdpy, kCGCaptureNoFill);
206 // #### So let's try waiting for the vertical blank instead...
207 // Nope, that doesn't work.
209 // CGDisplayWaitForBeamPositionOutsideLines (cgdpy, 0,
210 // window_y + [nswindow frame].size.height);
212 // #### Ok, try a busy-wait?
216 // #### Ok, just fuckin' sleep!
221 // Pull the bits out of the frame buffer.
223 copy_framebuffer_to_ximage (cgdpy, xim, window_x, window_y);
225 // CGDisplayRelease (cgdpy);
227 // Make the window visible again.
229 [nswindow setOpaque:oopaque];
231 // Splat the XImage onto the target drawable (probably the window)
232 // and free the bits.
235 GC gc = XCreateGC (dpy, drawable, 0, &gcv);
236 XPutImage (dpy, drawable, gc, xim, 0, 0, 0, 0, xim->width, xim->height);
242 geom_ret->width = xim->width;
243 geom_ret->height = xim->height;
251 #elif defined(USE_IPHONE)
255 On iOS, our application delegate, SaverRunner, grabs an image
256 of itself as a UIImage before mapping the XScreenSaverView.
257 In this code, we ask SaverRunner for that UIImage, then copy
258 it to the root window.
262 osx_grab_desktop_image (Screen *screen, Window xwindow, Drawable drawable,
263 XRectangle *geom_ret)
266 (SaverRunner *) [[UIApplication sharedApplication] delegate];
269 if (! [s isKindOfClass:[SaverRunner class]])
271 UIImage *img = [s screenshot];
274 jwxyz_draw_NSImage_or_CGImage (DisplayOfScreen (screen), drawable,
275 True, img, geom_ret, 0);
280 #else /* MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
284 This version of the code is simpler and more reliable, but
285 uses an API that only exist on 10.5 and newer, so we can only
286 use it if when being compiled against the 10.5 SDK or later.
289 extern float jwxyz_scale (Window); /* jwxyzI.h */
291 /* Loads an image into the Drawable, returning once the image is loaded.
294 osx_grab_desktop_image (Screen *screen, Window xwindow, Drawable drawable,
295 XRectangle *geom_ret)
297 Display *dpy = DisplayOfScreen (screen);
298 NSView *nsview = jwxyz_window_view (xwindow);
299 XWindowAttributes xgwa;
300 int window_x, window_y;
303 // Figure out where this window is on the screen.
305 XGetWindowAttributes (dpy, xwindow, &xgwa);
306 XTranslateCoordinates (dpy, xwindow, RootWindowOfScreen (screen), 0, 0,
307 &window_x, &window_y, &unused);
309 // Grab only the rectangle of the screen underlying this window.
312 double s = jwxyz_scale (xwindow);
313 cgrect.origin.x = window_x;
314 cgrect.origin.y = window_y;
315 cgrect.size.width = xgwa.width / s;
316 cgrect.size.height = xgwa.height / s;
318 /* If a password is required to unlock the screen, a large black
319 window will be on top of all of the desktop windows by the time
320 we reach here, making the screen-grab rather uninteresting. If
321 we move ourselves temporarily below the login-window windows
322 before capturing the image, we capture the real desktop as
325 Oct 2016: Surprise, this trick no longer works on MacOS 10.12. Sigh.
328 CGWindowID windowNumber = (CGWindowID) nsview.window.windowNumber;
331 CFArrayRef L = CGWindowListCopyWindowInfo (kCGWindowListOptionOnScreenOnly,
334 CFIndex n = CFArrayGetCount(L);
335 for (int i = 0; i < n; i++) {
336 NSDictionary *dict = (NSDictionary *) CFArrayGetValueAtIndex(L, i);
338 // loginwindow creates multiple toplevel windows. Grab the lowest one.
339 if(![([dict objectForKey:(NSString *)kCGWindowOwnerName])
340 compare:@"loginwindow"]) {
341 windowNumber = ((NSNumber *)[dict objectForKey:
342 (NSString *)kCGWindowNumber]).intValue;
347 // Grab a screen shot of those windows below this one
348 // (hey, X11 can't do that!)
351 CGWindowListCreateImage (cgrect,
352 kCGWindowListOptionOnScreenBelowWindow,
354 kCGWindowImageDefault);
356 if (! img) return False;
358 // Render the grabbed CGImage into the Drawable.
359 jwxyz_draw_NSImage_or_CGImage (DisplayOfScreen (screen), drawable,
360 False, img, geom_ret, 0);
361 CGImageRelease (img);
365 #endif /* 10.5+ code */
370 /* Returns the EXIF rotation property of the image, if any.
373 exif_rotation (const char *filename)
375 /* As of 10.6, NSImage rotates according to EXIF by default:
376 http://developer.apple.com/mac/library/releasenotes/cocoa/appkit.html
377 So this function should return -1 when *running* on 10.6 systems.
378 But when running against older systems, we need to examine the image
379 to figure out its rotation.
382 # if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 /* 10.6 SDK */
384 /* When we have compiled against the 10.6 SDK, we know that we are
385 running on a 10.6 or later system.
389 # else /* Compiled against 10.5 SDK or earlier */
391 /* If this selector exists, then we are running against a 10.6 runtime
392 that does automatic EXIF rotation (despite the fact that we were
393 compiled against the 10.5 or earlier SDK). So in that case, this
394 function should no-op.
396 if ([NSImage instancesRespondToSelector:
397 @selector(initWithDataIgnoringOrientation:)])
400 /* Otherwise, go ahead and figure out what the rotational characteristics
401 of this image are. */
405 /* This is a ridiculous amount of rigamarole to go through, but for some
406 reason the "Orientation" tag does not exist in the "NSImageEXIFData"
407 dictionary inside the NSBitmapImageRep of the NSImage. Several other
408 EXIF tags are there (e.g., shutter speed) but not orientation. WTF?
410 CFStringRef s = CFStringCreateWithCString (NULL, filename,
411 kCFStringEncodingUTF8);
412 CFURLRef url = CFURLCreateWithFileSystemPath (NULL, s,
413 kCFURLPOSIXPathStyle, 0);
414 CGImageSourceRef cgimg = CGImageSourceCreateWithURL (url, NULL);
415 if (! cgimg) return -1;
417 NSDictionary *props = (NSDictionary *)
418 CGImageSourceCopyPropertiesAtIndex (cgimg, 0, NULL);
419 int rot = [[props objectForKey:@"Orientation"] intValue];
428 # endif /* USE_IPHONE */
432 /* Loads an image file and splats it onto the drawable.
433 The image is drawn as large as possible while preserving its aspect ratio.
434 If geom_ret is provided, the actual rectangle the rendered image takes
435 up will be returned there.
438 osx_load_image_file (Screen *screen, Window xwindow, Drawable drawable,
439 const char *filename, XRectangle *geom_ret)
443 if (!filename || !*filename) return False;
445 NSImage *img = [[NSImage alloc] initWithContentsOfFile:
446 [NSString stringWithCString:filename
447 encoding:NSUTF8StringEncoding]];
451 jwxyz_draw_NSImage_or_CGImage (DisplayOfScreen (screen), drawable,
453 exif_rotation (filename));
457 # else /* USE_IPHONE */
459 /* This is handled differently: see grabclient.c and grabclient-ios.m. */
462 # endif /* USE_IPHONE */