-/* xscreensaver, Copyright (c) 2006-2013 Jamie Zawinski <jwz@jwz.org>
+/* xscreensaver, Copyright (c) 2006-2014 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
#import <zlib.h>
#import "XScreenSaverView.h"
#import "XScreenSaverConfigSheet.h"
+#import "Updater.h"
#import "screenhackI.h"
#import "xlockmoreI.h"
#import "jwxyz-timers.h"
{ "-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 [] = {
# 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
};
- (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
}
{
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)
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
{
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
# ifdef USE_IPHONE
[UIApplication sharedApplication].idleTimerDisabled =
([UIDevice currentDevice].batteryState != UIDeviceBatteryStateUnplugged);
+ [[UIApplication sharedApplication]
+ setStatusBarHidden:YES withAnimation:UIStatusBarAnimationNone];
# endif
}
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;
//
# ifdef USE_IPHONE
[UIApplication sharedApplication].idleTimerDisabled = NO;
+ [[UIApplication sharedApplication]
+ setStatusBarHidden:NO withAnimation:UIStatusBarAnimationNone];
# endif
}
// 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
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.
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();
}
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];
}
}
- /* 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);
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);
CGImageRelease (img);
CGContextFlush (window_ctx);
-# endif // !USE_CALAYER
+# endif // BACKBUFFER_CGCONTEXT
}
-# ifdef USE_CALAYER
+# ifdef BACKBUFFER_CALAYER
- (void) drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
{
CGImageRelease (img);
}
}
-# endif // USE_CALAYER
+# endif // BACKBUFFER_CALAYER
#endif // USE_BACKBUFFER
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);
}
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;
}
/* Announce our willingness to accept keyboard input.
-*/
+ */
- (BOOL)acceptsFirstResponder
{
return YES;
Possibly XScreenSaverView should use Core Animation, and
XScreenSaverGLView should override that.
-*/
+ */
- (void)didRotate:(NSNotification *)notification
{
UIDeviceOrientation current = [[UIDevice currentDevice] orientation];
}
}
-
#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...