X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=OSX%2FXScreenSaverView.m;h=1b3af09ff9b0c5965d2d1b0ee1728bb03f6388ad;hb=dba664f31aa87285db4d76cf8c5e66335299703a;hp=ba87a89d633a1f3ec45dbcfb1335228f935d179f;hpb=2762a7d7cf8d83e68b8f635941f6609119d630ae;p=xscreensaver diff --git a/OSX/XScreenSaverView.m b/OSX/XScreenSaverView.m index ba87a89d..1b3af09f 100644 --- a/OSX/XScreenSaverView.m +++ b/OSX/XScreenSaverView.m @@ -1,4 +1,4 @@ -/* xscreensaver, Copyright (c) 2006-2013 Jamie Zawinski +/* xscreensaver, Copyright (c) 2006-2014 Jamie Zawinski * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that @@ -19,6 +19,7 @@ #import #import "XScreenSaverView.h" #import "XScreenSaverConfigSheet.h" +#import "Updater.h" #import "screenhackI.h" #import "xlockmoreI.h" #import "jwxyz-timers.h" @@ -225,6 +226,25 @@ add_default_options (const XrmOptionDescRec *opts, { "-fg", ".foreground", XrmoptionSepArg, 0 }, { "-background", ".background", XrmoptionSepArg, 0 }, { "-bg", ".background", XrmoptionSepArg, 0 }, + +# ifndef USE_IPHONE + // + { "-" 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 [] = { @@ -248,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 }; @@ -391,12 +426,12 @@ double_time (void) - (void) initLayer { -# if !defined(USE_IPHONE) && defined(USE_CALAYER) +# if !defined(USE_IPHONE) && defined(BACKBUFFER_CALAYER) [self setLayer: [CALayer layer]]; self.layer.delegate = self; self.layer.opaque = YES; [self setWantsLayer: YES]; -# endif // !USE_IPHONE && USE_CALAYER +# endif // !USE_IPHONE && BACKBUFFER_CALAYER } @@ -410,8 +445,7 @@ double_time (void) { NSAssert(![self isAnimating], @"still animating"); NSAssert(!xdata, @"xdata not yet freed"); - if (xdpy) - jwxyz_free_display (xdpy); + NSAssert(!xdpy, @"xdpy not yet freed"); # ifdef USE_BACKBUFFER if (backbuffer) @@ -420,10 +454,10 @@ double_time (void) if (colorspace) CGColorSpaceRelease (colorspace); -# ifndef USE_CALAYER +# ifdef BACKBUFFER_CGCONTEXT if (window_ctx) CGContextRelease (window_ctx); -# endif // !USE_CALAYER +# endif // BACKBUFFER_CGCONTEXT # endif // USE_BACKBUFFER @@ -469,6 +503,10 @@ double_time (void) { NSAssert(![self isAnimating], @"already animating"); NSAssert(!initted_p && !xdata, @"already initialized"); + + // See comment in render_x11() for why this value is important: + [self setAnimationTimeInterval: 1.0 / 120.0]; + [super startAnimation]; /* We can't draw on the window from this method, so we actually do the initialization of the screen saver (xsft->init_cb) in the first call @@ -497,6 +535,8 @@ double_time (void) # ifdef USE_IPHONE [UIApplication sharedApplication].idleTimerDisabled = ([UIDevice currentDevice].batteryState != UIDeviceBatteryStateUnplugged); + [[UIApplication sharedApplication] + setStatusBarHidden:YES withAnimation:UIStatusBarAnimationNone]; # endif } @@ -519,6 +559,12 @@ double_time (void) xsft->free_cb (xdpy, xwindow, xdata); [self unlockFocus]; + // xdpy must be freed before dealloc is called, because xdpy owns a + // circular reference to the parent XScreenSaverView. + jwxyz_free_display (xdpy); + xdpy = NULL; + xwindow = NULL; + // setup_p = NO; // #### wait, do we need this? initted_p = NO; xdata = 0; @@ -540,6 +586,8 @@ double_time (void) // # ifdef USE_IPHONE [UIApplication sharedApplication].idleTimerDisabled = NO; + [[UIApplication sharedApplication] + setStatusBarHidden:NO withAnimation:UIStatusBarAnimationNone]; # endif } @@ -704,7 +752,7 @@ double current_device_rotation (void) // Colorspaces and CGContexts only happen with non-GL hacks. if (colorspace) CGColorSpaceRelease (colorspace); -# ifndef USE_CALAYER +# ifdef BACKBUFFER_CGCONTEXT if (window_ctx) CGContextRelease (window_ctx); # endif @@ -714,7 +762,7 @@ double current_device_rotation (void) if (window && xdpy) { [self lockFocus]; -# ifndef USE_CALAYER +# if defined(BACKBUFFER_CGCONTEXT) // TODO: This was borrowed from jwxyz_window_resized, and should // probably be refactored. @@ -751,21 +799,21 @@ double current_device_rotation (void) colorspace = CGColorSpaceCreateWithPlatformColorSpace (profile); NSAssert (colorspace, @"unable to find colorspace"); } -# else // USE_CALAYER +# elif defined(BACKBUFFER_CALAYER) // Was apparently faster until 10.9. colorspace = CGColorSpaceCreateDeviceRGB (); -# endif // USE_CALAYER +# endif // BACKBUFFER_CALAYER -# ifndef USE_CALAYER +# ifdef BACKBUFFER_CGCONTEXT window_ctx = [[window graphicsContext] graphicsPort]; CGContextRetain (window_ctx); -# endif // !USE_CALAYER +# endif // BACKBUFFER_CGCONTEXT [self unlockFocus]; } else { -# ifndef USE_CALAYER +# ifdef BACKBUFFER_CGCONTEXT window_ctx = NULL; -# endif // !USE_CALAYER +# endif // BACKBUFFER_CGCONTEXT colorspace = CGColorSpaceCreateDeviceRGB(); } @@ -912,11 +960,17 @@ double current_device_rotation (void) (void *(*) (Display *, Window, void *)) xsft->init_cb; xdata = init_cb (xdpy, xwindow, xsft->setup_arg); + // NSAssert(xdata, @"no xdata from init"); + if (! xdata) abort(); 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]; } @@ -941,24 +995,39 @@ double current_device_rotation (void) } - /* It turns out that [ScreenSaverView setAnimationTimeInterval] does nothing. - This is bad, because some of the screen hacks want to delay for long - periods (like 5 seconds or a minute!) between frames, and running them - all at 60 FPS is no good. - - So, we don't use setAnimationTimeInterval, and just let the framework call - us whenever. But, we only invoke the screen hack's "draw frame" method - when enough time has expired. + /* It turns out that on some systems (possibly only 10.5 and older?) + [ScreenSaverView setAnimationTimeInterval] does nothing. This means + that we cannot rely on it. + + Some of the screen hacks want to delay for long periods, and letting the + framework run the update function at 30 FPS when it really wanted half a + minute between frames would be bad. So instead, we assume that the + framework's animation timer might fire whenever, but we only invoke the + screen hack's "draw frame" method when enough time has expired. This means two extra calls to gettimeofday() per frame. For fast-cycling screen savers, that might actually slow them down. Oh well. - #### Also, we do not run the draw callback faster than the system's - animationTimeInterval, so if any savers are pickier about timing - than that, this may slow them down too much. If that's a problem, - then we could call draw_cb in a loop here (with usleep) until the - next call would put us past animationTimeInterval... But a better - approach would probably be to just change the saver to not do that. + A side-effect of this is that it's not possible for a saver to request + an animation interval that is faster than animationTimeInterval. + + HOWEVER! On modern systems where setAnimationTimeInterval is *not* + ignored, it's important that it be faster than 30 FPS. 120 FPS is good. + + An NSTimer won't fire if the timer is already running the invocation + function from a previous firing. So, if we use a 30 FPS + animationTimeInterval (33333 µs) and a screenhack takes 40000 µs for a + frame, there will be a 26666 µs delay until the next frame, 66666 µs + after the beginning of the current frame. In other words, 25 FPS + becomes 15 FPS. + + Frame rates tend to snap to values of 30/N, where N is a positive + integer, i.e. 30 FPS, 15 FPS, 10, 7.5, 6. And the 'snapped' frame rate + is rounded down from what it would normally be. + + So if we set animationTimeInterval to 1/120 instead of 1/30, frame rates + become values of 60/N, 120/N, or 240/N, with coarser or finer frame rate + steps for higher or lower animation time intervals respectively. */ struct timeval tv; gettimeofday (&tv, 0); @@ -997,6 +1066,8 @@ double current_device_rotation (void) # ifndef USE_IPHONE NSDisableScreenUpdates(); # endif + // NSAssert(xdata, @"no xdata when drawing"); + if (! xdata) abort(); unsigned long delay = xsft->draw_cb (xdpy, xwindow, xdata); if (fpst) xsft->fps_cb (xdpy, xwindow, fpst, xdata); # ifndef USE_IPHONE @@ -1111,9 +1182,9 @@ double current_device_rotation (void) self.layer.bounds = bounds; # endif // USE_IPHONE -# ifdef USE_CALAYER +# if defined(BACKBUFFER_CALAYER) [self.layer setNeedsDisplay]; -# else // !USE_CALAYER +# elif defined(BACKBUFFER_CGCONTEXT) size_t w = CGBitmapContextGetWidth (backbuffer), h = CGBitmapContextGetHeight (backbuffer); @@ -1144,10 +1215,10 @@ double current_device_rotation (void) CGImageRelease (img); CGContextFlush (window_ctx); -# endif // !USE_CALAYER +# endif // BACKBUFFER_CGCONTEXT } -# ifdef USE_CALAYER +# ifdef BACKBUFFER_CALAYER - (void) drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx { @@ -1200,7 +1271,7 @@ double current_device_rotation (void) CGImageRelease (img); } } -# endif // USE_CALAYER +# endif // BACKBUFFER_CALAYER #endif // USE_BACKBUFFER @@ -1252,9 +1323,9 @@ double current_device_rotation (void) UInt32 usize = * (UInt32 *) (data.bytes + data.length - 4); data2 = [NSMutableData dataWithLength: usize]; zs.next_in = (Bytef *) data.bytes; - zs.avail_in = data.length; + zs.avail_in = (uint) data.length; zs.next_out = (Bytef *) data2.bytes; - zs.avail_out = data2.length; + zs.avail_out = (uint) data2.length; ret = inflate (&zs, Z_FINISH); inflateEnd (&zs); } @@ -1298,12 +1369,13 @@ double current_device_rotation (void) 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]; + // [sheet retain]; return sheet; } @@ -1316,7 +1388,7 @@ double current_device_rotation (void) /* Announce our willingness to accept keyboard input. -*/ + */ - (BOOL)acceptsFirstResponder { return YES; @@ -1564,7 +1636,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]; @@ -1873,10 +1945,76 @@ 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 it's off, don't bother running the updater. Otherwise, the + // updater will decide if it's time to hit the network. + if (! get_boolean_resource (xdpy, + SUSUEnableAutomaticChecksKey, + SUSUEnableAutomaticChecksKey)) + return; + + NSString *updater = @"XScreenSaverUpdater.app"; + + // There may be multiple copies of the updater: e.g., one in /Applications + // and one in the mounted installer DMG! It's important that we run the + // one from the disk and not the DMG, so search for the right one. + // + NSWorkspace *workspace = [NSWorkspace sharedWorkspace]; + NSBundle *bundle = [NSBundle bundleForClass:[self class]]; + NSArray *search = + @[[[bundle bundlePath] stringByDeletingLastPathComponent], + [@"~/Library/Screen Savers" stringByExpandingTildeInPath], + @"/Library/Screen Savers", + @"/System/Library/Screen Savers", + @"/Applications", + @"/Applications/Utilities"]; + NSString *app_path = nil; + for (NSString *dir in search) { + NSString *p = [dir stringByAppendingPathComponent:updater]; + if ([[NSFileManager defaultManager] fileExistsAtPath:p]) { + app_path = p; + break; + } + } + + if (! app_path) + app_path = [workspace fullPathForApplication:updater]; + + if (app_path && [app_path hasPrefix:@"/Volumes/XScreenSaver "]) + app_path = 0; // The DMG version will not do. + + if (!app_path) { + NSLog(@"Unable to find %@", updater); + return; + } + + NSError *err = nil; + if (! [workspace launchApplicationAtURL:[NSURL fileURLWithPath:app_path] + options:(NSWorkspaceLaunchWithoutAddingToRecents | + NSWorkspaceLaunchWithoutActivation | + NSWorkspaceLaunchAndHide) + configuration:nil + error:&err]) { + NSLog(@"Unable to launch %@: %@", app_path, err); + } + +# endif // !USE_IPHONE +} + + @end /* Utility functions...