From http://www.jwz.org/xscreensaver/xscreensaver-5.24.tar.gz
[xscreensaver] / OSX / XScreenSaverView.m
index a0dc3813200b738f86d24d2c2aea71251d283c9a..a02a5067d3bf9da8515a5b24bcc457abb3263953 100644 (file)
@@ -1,13 +1,13 @@
-/* xscreensaver, Copyright (c) 2006-2012 Jamie Zawinski <jwz@jwz.org>
-*
-* Permission to use, copy, modify, distribute, and sell this software and its
-* documentation for any purpose is hereby granted without fee, provided that
-* the above copyright notice appear in all copies and that both that
-* copyright notice and this permission notice appear in supporting
-* documentation.  No representations are made about the suitability of this
-* software for any purpose.  It is provided "as is" without express or 
-* implied warranty.
-*/
+/* xscreensaver, Copyright (c) 2006-2013 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
 
 /* This is a subclass of Apple's ScreenSaverView that knows how to run
    xscreensaver programs without X11 via the dark magic of the "jwxyz"
  */
 
 #import <QuartzCore/QuartzCore.h>
+#import <zlib.h>
 #import "XScreenSaverView.h"
 #import "XScreenSaverConfigSheet.h"
+#import "Updater.h"
 #import "screenhackI.h"
 #import "xlockmoreI.h"
 #import "jwxyz-timers.h"
 
-#ifdef USE_IPHONE
-# include "ios_function_tables.h"
-static NSDictionary *function_tables = 0;
-#endif
-
 
 /* Garbage collection only exists if we are being compiled against the 
    10.6 SDK or newer, not if we are building against the 10.4 SDK.
@@ -50,6 +47,8 @@ int mono_p = 0;
 
 # ifdef USE_IPHONE
 
+extern NSDictionary *make_function_table_dict(void);  // ios-function-table.m
+
 /* Stub definition of the superclass, for iPhone.
  */
 @implementation ScreenSaverView
@@ -98,7 +97,6 @@ int mono_p = 0;
 
 
 @interface XScreenSaverView (Private)
-- (void) stopAndClose;
 - (void) stopAndClose:(Bool)relaunch;
 @end
 
@@ -120,6 +118,7 @@ int mono_p = 0;
   CFBundleRef cfb = CFBundleCreate (kCFAllocatorDefault, url);
   CFRelease (url);
   NSAssert1 (cfb, @"no CFBundle for \"%@\"", path);
+  // #### Analyze says "Potential leak of an object stored into cfb"
   
   if (! name)
     name = [[path lastPathComponent] stringByDeletingPathExtension];
@@ -141,9 +140,9 @@ int mono_p = 0;
 
 # else  // USE_IPHONE
   // Remember: any time you add a new saver to the iOS app,
-  // manually run "make ios_function_tables.h"!
+  // manually run "make ios-function-table.m"!
   if (! function_tables)
-    function_tables = [make_function_tables_dict() retain];
+    function_tables = [make_function_table_dict() retain];
   NSValue *v = [function_tables objectForKey: name];
   void *addr = v ? [v pointerValue] : 0;
 # endif // USE_IPHONE
@@ -223,6 +222,29 @@ add_default_options (const XrmOptionDescRec *opts,
     { "-image-directory",        ".imageDirectory",    XrmoptionSepArg, 0 },
     { "-fps",                    ".doFPS",             XrmoptionNoArg, "True" },
     { "-no-fps",                 ".doFPS",             XrmoptionNoArg, "False"},
+    { "-foreground",             ".foreground",        XrmoptionSepArg, 0 },
+    { "-fg",                     ".foreground",        XrmoptionSepArg, 0 },
+    { "-background",             ".background",        XrmoptionSepArg, 0 },
+    { "-bg",                     ".background",        XrmoptionSepArg, 0 },
+
+# ifndef USE_IPHONE
+    // <xscreensaver-updater />
+    {    "-" SUSUEnableAutomaticChecksKey,
+         "." SUSUEnableAutomaticChecksKey, XrmoptionNoArg, "True"  },
+    { "-no-" SUSUEnableAutomaticChecksKey,
+         "." SUSUEnableAutomaticChecksKey, XrmoptionNoArg, "False" },
+    {    "-" SUAutomaticallyUpdateKey,
+         "." SUAutomaticallyUpdateKey, XrmoptionNoArg, "True"  },
+    { "-no-" SUAutomaticallyUpdateKey,
+         "." SUAutomaticallyUpdateKey, XrmoptionNoArg, "False" },
+    {    "-" SUSendProfileInfoKey,
+         "." SUSendProfileInfoKey, XrmoptionNoArg,"True" },
+    { "-no-" SUSendProfileInfoKey,
+         "." SUSendProfileInfoKey, XrmoptionNoArg,"False"},
+    {    "-" SUScheduledCheckIntervalKey,
+         "." SUScheduledCheckIntervalKey, XrmoptionSepArg, 0 },
+# endif // !USE_IPHONE
+
     { 0, 0, 0, 0 }
   };
   static const char *default_defaults [] = {
@@ -236,7 +258,7 @@ add_default_options (const XrmOptionDescRec *opts,
 # endif
  // ".textLiteral:        ",
  // ".textFile:           ",
-    ".textURL:            http://twitter.com/statuses/public_timeline.atom",
+    ".textURL:            http://en.wikipedia.org/w/index.php?title=Special:NewPages&feed=rss",
  // ".textProgram:        ",
     ".grabDesktopImages:  yes",
 # ifndef USE_IPHONE
@@ -246,6 +268,21 @@ add_default_options (const XrmOptionDescRec *opts,
 # endif
     ".imageDirectory:     ~/Pictures",
     ".relaunchDelay:      2",
+
+# ifndef USE_IPHONE
+#  define STR1(S) #S
+#  define STR(S) STR1(S)
+#  define __objc_yes Yes
+#  define __objc_no  No
+    "." SUSUEnableAutomaticChecksKey ": " STR(SUSUEnableAutomaticChecksDef),
+    "." SUAutomaticallyUpdateKey ":  "    STR(SUAutomaticallyUpdateDef),
+    "." SUSendProfileInfoKey ": "         STR(SUSendProfileInfoDef),
+    "." SUScheduledCheckIntervalKey ": "  STR(SUScheduledCheckIntervalDef),
+#  undef __objc_yes
+#  undef __objc_no
+#  undef STR1
+#  undef STR
+# endif // USE_IPHONE
     0
   };
 
@@ -323,6 +360,7 @@ double_time (void)
            isPreview:(BOOL)isPreview
 {
 # ifdef USE_IPHONE
+  initial_bounds = frame.size;
   rot_current_size = frame.size;       // needs to be early, because
   rot_from = rot_current_size;         // [self setFrame] is called by
   rot_to = rot_current_size;           // [super initWithFrame].
@@ -388,10 +426,12 @@ double_time (void)
 
 - (void) initLayer
 {
-# ifndef USE_IPHONE
+# if !defined(USE_IPHONE) && defined(USE_CALAYER)
   [self setLayer: [CALayer layer]];
+  self.layer.delegate = self;
+  self.layer.opaque = YES;
   [self setWantsLayer: YES];
-# endif
+# endif  // !USE_IPHONE && USE_CALAYER
 }
 
 
@@ -411,7 +451,16 @@ double_time (void)
 # ifdef USE_BACKBUFFER
   if (backbuffer)
     CGContextRelease (backbuffer);
-# endif
+
+  if (colorspace)
+    CGColorSpaceRelease (colorspace);
+
+#  ifndef USE_CALAYER
+  if (window_ctx)
+    CGContextRelease (window_ctx);
+#  endif // !USE_CALAYER
+
+# endif // USE_BACKBUFFER
 
   [prefsReader release];
 
@@ -474,6 +523,7 @@ double_time (void)
                          selector:@selector(allSystemsGo:)
                          userInfo:nil
                          repeats:NO];
+
 # endif // USE_IPHONE
 
   // Never automatically turn the screen off if we are docked,
@@ -482,6 +532,8 @@ double_time (void)
 # ifdef USE_IPHONE
   [UIApplication sharedApplication].idleTimerDisabled =
     ([UIDevice currentDevice].batteryState != UIDeviceBatteryStateUnplugged);
+  [[UIApplication sharedApplication]
+    setStatusBarHidden:YES withAnimation:UIStatusBarAnimationNone];
 # endif
 }
 
@@ -525,6 +577,8 @@ double_time (void)
   //
 # ifdef USE_IPHONE
   [UIApplication sharedApplication].idleTimerDisabled = NO;
+  [[UIApplication sharedApplication]
+    setStatusBarHidden:NO withAnimation:UIStatusBarAnimationNone];
 # endif
 }
 
@@ -562,13 +616,16 @@ screenhack_do_fps (Display *dpy, Window w, fps_state *fpst, void *closure)
    anyway, since that's a higher resolution than most desktop monitors
    have even today.  (This is only true for X11 programs, not GL 
    programs.  Those are fine at full rez.)
+
+   This method is overridden in XScreenSaverGLView, since this kludge
+   isn't necessary for GL programs, being resolution independent by
+   nature.
  */
 - (CGFloat) hackedContentScaleFactor
 {
   GLfloat s = [self contentScaleFactor];
-  CGRect frame = [self bounds];
-  if (frame.size.width  >= 1024 ||
-      frame.size.height >= 1024)
+  if (initial_bounds.width  >= 1024 ||
+      initial_bounds.height >= 1024)
     s = 1;
   return s;
 }
@@ -629,9 +686,10 @@ double current_device_rotation (void)
 #   undef CLAMP180
 
   double s = [self hackedContentScaleFactor];
-  if (((int) backbuffer_size.width  != (int) (s * rot_current_size.width) ||
-       (int) backbuffer_size.height != (int) (s * rot_current_size.height))
-/*      && rotation_ratio == -1*/)
+  if (!ignore_rotation_p &&
+      /* rotation_ratio && */
+      ((int) backbuffer_size.width  != (int) (s * rot_current_size.width) ||
+       (int) backbuffer_size.height != (int) (s * rot_current_size.height)))
     [self resize_x11];
 }
 
@@ -639,7 +697,7 @@ double current_device_rotation (void)
 - (void)alertView:(UIAlertView *)av clickedButtonAtIndex:(NSInteger)i
 {
   if (i == 0) exit (-1);       // Cancel
-  [self stopAndClose];         // Keep going
+  [self stopAndClose:NO];      // Keep going
 }
 
 - (void) handleException: (NSException *)e
@@ -674,12 +732,81 @@ double current_device_rotation (void)
 {
 # ifdef USE_IPHONE
   double s = [self hackedContentScaleFactor];
-  int new_w = s * rot_current_size.width;
-  int new_h = s * rot_current_size.height;
+  CGSize rotsize = ignore_rotation_p ? initial_bounds : rot_current_size;
+  int new_w = s * rotsize.width;
+  int new_h = s * rotsize.height;
 # else
   int new_w = [self bounds].size.width;
   int new_h = [self bounds].size.height;
 # endif
+       
+  // Colorspaces and CGContexts only happen with non-GL hacks.
+  if (colorspace)
+    CGColorSpaceRelease (colorspace);
+# ifndef USE_CALAYER
+  if (window_ctx)
+    CGContextRelease (window_ctx);
+# endif
+       
+  NSWindow *window = [self window];
+
+  if (window && xdpy) {
+    [self lockFocus];
+
+# ifndef USE_CALAYER
+    // TODO: This was borrowed from jwxyz_window_resized, and should
+    // probably be refactored.
+         
+    // Figure out which screen the window is currently on.
+    CGDirectDisplayID cgdpy = 0;
+
+    {
+//    int wx, wy;
+//    TODO: XTranslateCoordinates is returning (0,1200) on my system.
+//    Is this right?
+//    In any case, those weren't valid coordinates for CGGetDisplaysWithPoint.
+//    XTranslateCoordinates (xdpy, xwindow, NULL, 0, 0, &wx, &wy, NULL);
+//    p.x = wx;
+//    p.y = wy;
+
+      NSPoint p0 = {0, 0};
+      p0 = [window convertBaseToScreen:p0];
+      CGPoint p = {p0.x, p0.y};
+      CGDisplayCount n;
+      CGGetDisplaysWithPoint (p, 1, &cgdpy, &n);
+      NSAssert (cgdpy, @"unable to find CGDisplay");
+    }
+
+    {
+      // Figure out this screen's colorspace, and use that for every CGImage.
+      //
+      CMProfileRef profile = 0;
+
+      // CMGetProfileByAVID is deprecated as of OS X 10.6, but there's no
+      // documented replacement as of OS X 10.9.
+      // http://lists.apple.com/archives/colorsync-dev/2012/Nov/msg00001.html
+      CMGetProfileByAVID ((CMDisplayIDType) cgdpy, &profile);
+      NSAssert (profile, @"unable to find colorspace profile");
+      colorspace = CGColorSpaceCreateWithPlatformColorSpace (profile);
+      NSAssert (colorspace, @"unable to find colorspace");
+    }
+# else  // USE_CALAYER
+    // Was apparently faster until 10.9.
+    colorspace = CGColorSpaceCreateDeviceRGB ();
+# endif // USE_CALAYER
+
+# ifndef USE_CALAYER
+    window_ctx = [[window graphicsContext] graphicsPort];
+    CGContextRetain (window_ctx);
+# endif // !USE_CALAYER
+         
+    [self unlockFocus];
+  } else {
+# ifndef USE_CALAYER
+    window_ctx = NULL;
+# endif // !USE_CALAYER
+    colorspace = CGColorSpaceCreateDeviceRGB();
+  }
 
   if (backbuffer &&
       backbuffer_size.width  == new_w &&
@@ -692,15 +819,16 @@ double current_device_rotation (void)
   backbuffer_size.width  = new_w;
   backbuffer_size.height = new_h;
 
-  CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB();
   backbuffer = CGBitmapContextCreate (NULL,
                                       backbuffer_size.width,
                                       backbuffer_size.height,
                                       8, 
                                       backbuffer_size.width * 4,
-                                      cs,
-                                      kCGImageAlphaPremultipliedLast);
-  CGColorSpaceRelease (cs);
+                                      colorspace,
+                                      // kCGImageAlphaPremultipliedLast
+                                      (kCGImageAlphaNoneSkipFirst |
+                                       kCGBitmapByteOrder32Host)
+                                      );
   NSAssert (backbuffer, @"unable to allocate back buffer");
 
   // Clear it.
@@ -772,6 +900,13 @@ double current_device_rotation (void)
       xdpy = jwxyz_make_display (self, 0);
 # endif
       xwindow = XRootWindow (xdpy, 0);
+
+# ifdef USE_IPHONE
+      /* Some X11 hacks (fluidballs) want to ignore all rotation events. */
+      ignore_rotation_p =
+        get_boolean_resource (xdpy, "ignoreRotation", "IgnoreRotation");
+# endif // USE_IPHONE
+
       [self resize_x11];
     }
 
@@ -783,7 +918,8 @@ double current_device_rotation (void)
     initted_p = YES;
     resized_p = NO;
     NSAssert(!xdata, @"xdata already initialized");
-    
+
+
 # undef ya_rand_init
     ya_rand_init (0);
     
@@ -819,7 +955,11 @@ double current_device_rotation (void)
     if (get_boolean_resource (xdpy, "doFPS", "DoFPS")) {
       fpst = fps_init (xdpy, xwindow);
       if (! xsft->fps_cb) xsft->fps_cb = screenhack_do_fps;
+    } else {
+      xsft->fps_cb = 0;
     }
+
+    [self checkForUpdates];
   }
 
 
@@ -972,6 +1112,7 @@ double current_device_rotation (void)
 - (void) animateOneFrame
 {
   [self render_x11];
+  jwxyz_flush_context(xdpy);
 }
 
 #else  // USE_BACKBUFFER
@@ -994,30 +1135,117 @@ double current_device_rotation (void)
 
 # ifdef USE_IPHONE
   // Then compute the transformations for rotation.
+  double hs = [self hackedContentScaleFactor];
+  double s = [self contentScaleFactor];
 
   // The rotation origin for layer.affineTransform is in the center already.
-  CGAffineTransform t =
+  CGAffineTransform t = ignore_rotation_p ?
+    CGAffineTransformIdentity :
     CGAffineTransformMakeRotation (rot_current_angle / (180.0 / M_PI));
 
-  // Correct the aspect ratio.
-  CGRect frame = [self bounds];
-  double s = [self hackedContentScaleFactor];
-  t = CGAffineTransformScale(t,
-                             backbuffer_size.width  / (s * frame.size.width),
-                             backbuffer_size.height / (s * frame.size.height));
+  CGFloat f = s / hs;
+  self.layer.affineTransform = CGAffineTransformScale(t, f, f);
 
-  self.layer.affineTransform = t;
+  CGRect bounds;
+  bounds.origin.x = 0;
+  bounds.origin.y = 0;
+  bounds.size.width = backbuffer_size.width / s;
+  bounds.size.height = backbuffer_size.height / s;
+  self.layer.bounds = bounds;
 # endif // USE_IPHONE
+# ifdef USE_CALAYER
+  [self.layer setNeedsDisplay];
+# else // !USE_CALAYER
+  size_t
+    w = CGBitmapContextGetWidth (backbuffer),
+    h = CGBitmapContextGetHeight (backbuffer);
+  
+  size_t bpl = CGBitmapContextGetBytesPerRow (backbuffer);
+  CGDataProviderRef prov = CGDataProviderCreateWithData (NULL,
+                                            CGBitmapContextGetData(backbuffer),
+                                                         bpl * h,
+                                                         NULL);
+
+
+  CGImageRef img = CGImageCreate (w, h,
+                                  8, 32,
+                                  CGBitmapContextGetBytesPerRow(backbuffer),
+                                  colorspace,
+                                  CGBitmapContextGetBitmapInfo(backbuffer),
+                                  prov, NULL, NO,
+                                  kCGRenderingIntentDefault);
+
+  CGDataProviderRelease (prov);
+  
+  CGRect rect;
+  rect.origin.x = 0;
+  rect.origin.y = 0;
+  rect.size = backbuffer_size;
+  CGContextDrawImage (window_ctx, rect, img);
+  
+  CGImageRelease (img);
 
-  // Then copy that bitmap to the screen, by just stuffing it into
-  // the layer.  The superclass drawRect method will handle the rest.
+  CGContextFlush (window_ctx);
+# endif // !USE_CALAYER
+}
 
-  CGImageRef img = CGBitmapContextCreateImage (backbuffer);
-  self.layer.contents = (id)img;
-  CGImageRelease (img);
+# ifdef USE_CALAYER
+
+- (void) drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
+{
+  // This "isn't safe" if NULL is passed to CGBitmapCreateContext before iOS 4.
+  char *dest_data = (char *)CGBitmapContextGetData (ctx);
+
+  // The CGContext here is normally upside-down on iOS.
+  if (dest_data &&
+      CGBitmapContextGetBitmapInfo (ctx) ==
+        (kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host)
+#  ifdef USE_IPHONE
+      && CGContextGetCTM (ctx).d < 0
+#  endif // USE_IPHONE
+      )
+  {
+    size_t dest_height = CGBitmapContextGetHeight (ctx);
+    size_t dest_bpr = CGBitmapContextGetBytesPerRow (ctx);
+    size_t src_height = CGBitmapContextGetHeight (backbuffer);
+    size_t src_bpr = CGBitmapContextGetBytesPerRow (backbuffer);
+    char *src_data = (char *)CGBitmapContextGetData (backbuffer);
+
+    size_t height = src_height < dest_height ? src_height : dest_height;
+    
+    if (src_bpr == dest_bpr) {
+      // iPad 1: 4.0 ms, iPad 2: 6.7 ms
+      memcpy (dest_data, src_data, src_bpr * height);
+    } else {
+      // iPad 1: 4.6 ms, iPad 2: 7.2 ms
+      size_t bpr = src_bpr < dest_bpr ? src_bpr : dest_bpr;
+      while (height) {
+        memcpy (dest_data, src_data, bpr);
+        --height;
+        src_data += src_bpr;
+        dest_data += dest_bpr;
+      }
+    }
+  } else {
+
+    // iPad 1: 9.6 ms, iPad 2: 12.1 ms
+
+#  ifdef USE_IPHONE
+    CGContextScaleCTM (ctx, 1, -1);
+    CGFloat s = [self contentScaleFactor];
+    CGFloat hs = [self hackedContentScaleFactor];
+    CGContextTranslateCTM (ctx, 0, -backbuffer_size.height * hs / s);
+#  endif // USE_IPHONE
+    
+    CGImageRef img = CGBitmapContextCreateImage (backbuffer);
+    CGContextDrawImage (ctx, self.layer.bounds, img);
+    CGImageRelease (img);
+  }
 }
+# endif  // USE_CALAYER
 
-#endif // !USE_BACKBUFFER
+#endif // USE_BACKBUFFER
 
 
 
@@ -1050,6 +1278,40 @@ double current_device_rotation (void)
   return YES;
 }
 
++ (NSString *) decompressXML: (NSData *)data
+{
+  if (! data) return 0;
+  BOOL compressed_p = !!strncmp ((const char *) data.bytes, "<?xml", 5);
+
+  // If it's not already XML, decompress it.
+  NSAssert (compressed_p, @"xml isn't compressed");
+  if (compressed_p) {
+    NSMutableData *data2 = 0;
+    int ret = -1;
+    z_stream zs;
+    memset (&zs, 0, sizeof(zs));
+    ret = inflateInit2 (&zs, 16 + MAX_WBITS);
+    if (ret == Z_OK) {
+      UInt32 usize = * (UInt32 *) (data.bytes + data.length - 4);
+      data2 = [NSMutableData dataWithLength: usize];
+      zs.next_in   = (Bytef *) data.bytes;
+      zs.avail_in  = data.length;
+      zs.next_out  = (Bytef *) data2.bytes;
+      zs.avail_out = data2.length;
+      ret = inflate (&zs, Z_FINISH);
+      inflateEnd (&zs);
+    }
+    if (ret == Z_OK || ret == Z_STREAM_END)
+      data = data2;
+    else
+      NSAssert2 (0, @"gunzip error: %d: %s",
+                 ret, (zs.msg ? zs.msg : "<null>"));
+  }
+
+  return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+}
+
+
 #ifndef USE_IPHONE
 - (NSWindow *) configureSheet
 #else
@@ -1073,14 +1335,18 @@ double current_device_rotation (void)
   NSWindow *sheet;
 # endif // !USE_IPHONE
 
+  NSData *xmld = [NSData dataWithContentsOfFile:path];
+  NSString *xml = [[self class] decompressXML: xmld];
   sheet = [[XScreenSaverConfigSheet alloc]
-           initWithXMLFile:path
-           options:xsft->options
-           controller:[prefsReader userDefaultsController]
-             defaults:[prefsReader defaultOptions]];
+            initWithXML:[xml dataUsingEncoding:NSUTF8StringEncoding]
+                options:xsft->options
+             controller:[prefsReader userDefaultsController]
+       globalController:[prefsReader globalDefaultsController]
+               defaults:[prefsReader defaultOptions]];
 
   // #### am I expected to retain this, or not? wtf.
   //      I thought not, but if I don't do this, we (sometimes) crash.
+  // #### Analyze says "potential leak of an object stored into sheet"
   [sheet retain];
 
   return sheet;
@@ -1094,7 +1360,7 @@ double current_device_rotation (void)
 
 
 /* Announce our willingness to accept keyboard input.
-*/
+ */
 - (BOOL)acceptsFirstResponder
 {
   return YES;
@@ -1207,7 +1473,7 @@ double current_device_rotation (void)
         break;
       }
     default:
-      NSAssert (0, @"unknown X11 event type: %d", type);
+      NSAssert1 (0, @"unknown X11 event type: %d", type);
       break;
   }
 
@@ -1288,7 +1554,7 @@ double current_device_rotation (void)
 #else  // USE_IPHONE
 
 
-- (void) stopAndClose
+- (void) stopAndClose:(Bool)relaunch_p
 {
   if ([self isAnimating])
     [self stopAnimation];
@@ -1305,26 +1571,23 @@ double current_device_rotation (void)
   }
 
   UIView *fader = [self superview];  // the "backgroundView" view is our parent
-  [UIView animateWithDuration: 0.5
-          animations:^{ fader.alpha = 0.0; }
-          completion:^(BOOL finished) {
-            [fader removeFromSuperview];
-             fader.alpha = 1.0;
-          }];
-}
-
-
-- (void) stopAndClose:(Bool)relaunch_p
-{
-  [self stopAndClose];
 
   if (relaunch_p) {   // Fake a shake on the SaverListController.
-    UIViewController *v = [[self window] rootViewController];
+    // Why is [self window] sometimes null here?
+    UIWindow *w = [[UIApplication sharedApplication] keyWindow];
+    UIViewController *v = [w rootViewController];
     if ([v isKindOfClass: [UINavigationController class]]) {
       UINavigationController *n = (UINavigationController *) v;
       [[n topViewController] motionEnded: UIEventSubtypeMotionShake
                                withEvent: nil];
     }
+  } else {     // Not launching another, animate our return to the list.
+    [UIView animateWithDuration: 0.5
+            animations:^{ fader.alpha = 0.0; }
+            completion:^(BOOL finished) {
+               [fader removeFromSuperview];
+               fader.alpha = 1.0;
+            }];
   }
 }
 
@@ -1345,7 +1608,7 @@ double current_device_rotation (void)
 
    Possibly XScreenSaverView should use Core Animation, and 
    XScreenSaverGLView should override that.
-*/
+ */
 - (void)didRotate:(NSNotification *)notification
 {
   UIDeviceOrientation current = [[UIDevice currentDevice] orientation];
@@ -1409,29 +1672,27 @@ double current_device_rotation (void)
   default:                                    angle_to = 0;   break;
   }
 
-  NSRect ff = [self bounds];
-
   switch (orientation) {
   case UIDeviceOrientationLandscapeRight:      // from landscape
   case UIDeviceOrientationLandscapeLeft:
-    rot_from.width  = ff.size.height;
-    rot_from.height = ff.size.width;
+    rot_from.width  = initial_bounds.height;
+    rot_from.height = initial_bounds.width;
     break;
   default:                                     // from portrait
-    rot_from.width  = ff.size.width;
-    rot_from.height = ff.size.height;
+    rot_from.width  = initial_bounds.width;
+    rot_from.height = initial_bounds.height;
     break;
   }
 
   switch (new_orientation) {
   case UIDeviceOrientationLandscapeRight:      // to landscape
   case UIDeviceOrientationLandscapeLeft:
-    rot_to.width  = ff.size.height;
-    rot_to.height = ff.size.width;
+    rot_to.width  = initial_bounds.height;
+    rot_to.height = initial_bounds.width;
     break;
   default:                                     // to portrait
-    rot_to.width  = ff.size.width;
-    rot_to.height = ff.size.height;
+    rot_to.width  = initial_bounds.width;
+    rot_to.height = initial_bounds.height;
     break;
   }
 
@@ -1477,7 +1738,9 @@ double current_device_rotation (void)
 
 - (void) rotateMouse:(int)rot x:(int*)x y:(int *)y w:(int)w h:(int)h
 {
-  CGRect frame = [self bounds];                // Correct aspect ratio and scale.
+  // This is a no-op unless contentScaleFactor != hackedContentScaleFactor.
+  // Currently, this is the iPad Retina only.
+  CGRect frame = [self bounds];                // Scale.
   double s = [self hackedContentScaleFactor];
   *x *= (backbuffer_size.width  / frame.size.width)  / s;
   *y *= (backbuffer_size.height / frame.size.height) / s;
@@ -1505,6 +1768,10 @@ double current_device_rotation (void)
 
 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
 {
+  // If they are trying to pinch, just do nothing.
+  if ([[event allTouches] count] > 1)
+    return;
+
   tap_time = 0;
 
   if (xsft->event_cb && xwindow) {
@@ -1542,6 +1809,10 @@ double current_device_rotation (void)
 
 - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
 {
+  // If they are trying to pinch, just do nothing.
+  if ([[event allTouches] count] > 1)
+    return;
+
   if (xsft->event_cb && xwindow) {
     double s = [self hackedContentScaleFactor];
     XEvent xe;
@@ -1560,7 +1831,7 @@ double current_device_rotation (void)
       double dist = sqrt (((p.x - tap_point.x) * (p.x - tap_point.x)) +
                           ((p.y - tap_point.y) * (p.y - tap_point.y)));
       if (tap_time + 0.5 >= double_time() && dist < 20) {
-        [self stopAndClose];
+        [self stopAndClose:NO];
         return;
       }
 
@@ -1581,6 +1852,10 @@ double current_device_rotation (void)
 
 - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
 {
+  // If they are trying to pinch, just do nothing.
+  if ([[event allTouches] count] > 1)
+    return;
+
   if (xsft->event_cb && xwindow) {
     double s = [self hackedContentScaleFactor];
     XEvent xe;
@@ -1642,10 +1917,34 @@ double current_device_rotation (void)
   }
 }
 
-
 #endif // USE_IPHONE
 
 
+- (void) checkForUpdates
+{
+# ifndef USE_IPHONE
+  // We only check once at startup, even if there are multiple screens,
+  // and even if this saver is running for many days.
+  // (Uh, except this doesn't work because this static isn't shared,
+  // even if we make it an exported global. Not sure why. Oh well.)
+  static BOOL checked_p = NO;
+  if (checked_p) return;
+  checked_p = YES;
+  if (! get_boolean_resource (xdpy,
+                              SUSUEnableAutomaticChecksKey,
+                              SUSUEnableAutomaticChecksKey))
+    return;  // If it's off, don't bother running the updater.
+
+  // Otherwise, the updater will decide if it's time to hit the network.
+  NSString *updater = @"XScreenSaverUpdater";
+  if (! [[NSWorkspace sharedWorkspace]
+          launchApplication:updater showIcon:NO autolaunch:NO]) {
+    NSLog(@"Unable to launch %@", updater);
+  }
+# endif // USE_IPHONE
+}
+
+
 @end
 
 /* Utility functions...