From d3a98cf6cbc3bd0b9efc570f58e8812c03931c18 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Tue, 16 Oct 2018 10:08:48 +0200 Subject: Original 5.40 --- OSX/SaverRunner.m | 1645 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1645 insertions(+) create mode 100644 OSX/SaverRunner.m (limited to 'OSX/SaverRunner.m') diff --git a/OSX/SaverRunner.m b/OSX/SaverRunner.m new file mode 100644 index 0000000..f358fb7 --- /dev/null +++ b/OSX/SaverRunner.m @@ -0,0 +1,1645 @@ +/* xscreensaver, Copyright (c) 2006-2017 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 + * 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 program serves three purposes: + + First, It is a test harness for screen savers. When it launches, it + looks around for .saver bundles (in the current directory, and then in + the standard directories) and puts up a pair of windows that allow you + to select the saver to run. This is less clicking than running them + through System Preferences. This is the "SaverTester.app" program. + + Second, it can be used to transform any screen saver into a standalone + program. Just put one (and only one) .saver bundle into the app + bundle's Contents/Resources/ directory, and it will load and run that + saver at start-up (without the saver-selection menu or other chrome). + This is how the "Phosphor.app" and "Apple2.app" programs work. + + Third, it is the scaffolding which turns a set of screen savers into + a single iPhone / iPad program. In that case, all of the savers are + linked in to this executable, since iOS does not allow dynamic loading + of bundles that have executable code in them. Bleh. + */ + +#import +#import "SaverRunner.h" +#import "SaverListController.h" +#import "XScreenSaverGLView.h" +#import "yarandom.h" + +#ifdef USE_IPHONE + +# ifndef __IPHONE_8_0 +# define UIInterfaceOrientationUnknown UIDeviceOrientationUnknown +# endif +# ifndef NSFoundationVersionNumber_iOS_7_1 +# define NSFoundationVersionNumber_iOS_7_1 1047.25 +# endif +# ifndef NSFoundationVersionNumber_iOS_8_0 +# define NSFoundationVersionNumber_iOS_8_0 1134.10 +# endif + +@interface RotateyViewController : UINavigationController +{ + BOOL allowRotation; +} +@end + +@implementation RotateyViewController + +/* This subclass exists so that we can ask that the SaverListController and + preferences panels be auto-rotated by the system. Note that the + XScreenSaverView is not auto-rotated because it is on a different UIWindow. + */ + +- (id)initWithRotation:(BOOL)rotatep +{ + self = [super init]; + allowRotation = rotatep; + return self; +} + +- (BOOL)shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)o +{ + return allowRotation; /* Deprecated in iOS 6 */ +} + +- (BOOL)shouldAutorotate /* Added in iOS 6 */ +{ + return allowRotation; +} + +- (UIInterfaceOrientationMask)supportedInterfaceOrientations /* Added in iOS 6 */ +{ + return UIInterfaceOrientationMaskAll; +} + +@end + + +@implementation SaverViewController + +@synthesize saverName; + +- (id)initWithSaverRunner:(SaverRunner *)parent + showAboutBox:(BOOL)showAboutBox +{ + self = [super init]; + if (self) { + _parent = parent; + // _storedOrientation = UIInterfaceOrientationUnknown; + _showAboutBox = showAboutBox; + + self.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; + +# ifndef __IPHONE_7_0 + self.wantsFullScreenLayout = YES; // Deprecated as of iOS 7 +# endif + } + return self; +} + +- (BOOL) prefersStatusBarHidden +{ + // Requires UIViewControllerBasedStatusBarAppearance = true in plist + return YES; +} + +- (void)dealloc +{ + [_saverName release]; + // iOS: When a UIView deallocs, it doesn't do [UIView removeFromSuperView] + // for its subviews, so the subviews end up with a dangling pointer in their + // superview properties. + [aboutBox removeFromSuperview]; + [aboutBox release]; + [_saverView removeFromSuperview]; + [_saverView release]; + [super dealloc]; +} + + +- (void)loadView +{ + // The UIViewController's view must never change, so it gets set here to + // a plain black background. + + // This background view doesn't block the status bar, but that's probably + // OK, because it's never on screen for more than a fraction of a second. + UIView *backgroundView = [[UIView alloc] initWithFrame:CGRectNull]; + backgroundView.backgroundColor = [UIColor blackColor]; + self.view = backgroundView; + [backgroundView release]; +} + + +- (void)aboutPanel:(UIView *)saverView + orientation:(UIInterfaceOrientation)orient +{ + if (!_showAboutBox) + return; + + NSString *name = _saverName; + NSString *year = [_parent makeDesc:_saverName yearOnly:YES]; + + + CGRect frame = [saverView frame]; + CGFloat rot; + CGFloat pt1 = 24; + CGFloat pt2 = 14; + UIFont *font1 = [UIFont boldSystemFontOfSize: pt1]; + UIFont *font2 = [UIFont italicSystemFontOfSize:pt2]; + +# ifdef __IPHONE_7_0 + CGSize s = CGSizeMake(frame.size.width, frame.size.height); + CGSize tsize1 = [[[NSAttributedString alloc] + initWithString: name + attributes:@{ NSFontAttributeName: font1 }] + boundingRectWithSize: s + options: NSStringDrawingUsesLineFragmentOrigin + context: nil].size; + CGSize tsize2 = [[[NSAttributedString alloc] + initWithString: name + attributes:@{ NSFontAttributeName: font2 }] + boundingRectWithSize: s + options: NSStringDrawingUsesLineFragmentOrigin + context: nil].size; +# else // iOS 6 or Cocoa + CGSize tsize1 = [name sizeWithFont:font1 + constrainedToSize:CGSizeMake(frame.size.width, + frame.size.height)]; + CGSize tsize2 = [year sizeWithFont:font2 + constrainedToSize:CGSizeMake(frame.size.width, + frame.size.height)]; +# endif + + CGSize tsize = CGSizeMake (tsize1.width > tsize2.width ? + tsize1.width : tsize2.width, + tsize1.height + tsize2.height); + + tsize.width = ceilf(tsize.width); + tsize.height = ceilf(tsize.height); + + // Don't know how to find inner margin of UITextView. + CGFloat margin = 10; + tsize.width += margin * 4; + tsize.height += margin * 2; + + if ([saverView frame].size.width >= 768) + tsize.height += pt1 * 3; // extra bottom margin on iPad + + frame = CGRectMake (0, 0, tsize.width, tsize.height); + + /* Get the text oriented properly, and move it to the bottom of the + screen, since many savers have action in the middle. + */ + switch (orient) { + case UIInterfaceOrientationLandscapeLeft: + rot = -M_PI/2; + frame.origin.x = ([saverView frame].size.width + - (tsize.width - tsize.height) / 2 + - tsize.height); + frame.origin.y = ([saverView frame].size.height - tsize.height) / 2; + break; + case UIInterfaceOrientationLandscapeRight: + rot = M_PI/2; + frame.origin.x = -(tsize.width - tsize.height) / 2; + frame.origin.y = ([saverView frame].size.height - tsize.height) / 2; + break; + case UIInterfaceOrientationPortraitUpsideDown: + rot = M_PI; + frame.origin.x = ([saverView frame].size.width - tsize.width) / 2; + frame.origin.y = 0; + break; + default: + rot = 0; + frame.origin.x = ([saverView frame].size.width - tsize.width) / 2; + frame.origin.y = [saverView frame].size.height - tsize.height; + break; + } + + if (aboutBox) { + [aboutBox removeFromSuperview]; + [aboutBox release]; + } + + aboutBox = [[UIView alloc] initWithFrame:frame]; + + aboutBox.transform = CGAffineTransformMakeRotation (rot); + aboutBox.backgroundColor = [UIColor clearColor]; + + /* There seems to be no easy way to stroke the font, so instead draw + it 5 times, 4 in black and 1 in yellow, offset by 1 pixel, and add + a black shadow to each. (You'd think the shadow alone would be + enough, but there's no way to make it dark enough to be legible.) + */ + for (int i = 0; i < 5; i++) { + UITextView *textview; + int off = 1; + frame.origin.x = frame.origin.y = 0; + switch (i) { + case 0: frame.origin.x = -off; break; + case 1: frame.origin.x = off; break; + case 2: frame.origin.y = -off; break; + case 3: frame.origin.y = off; break; + } + + for (int j = 0; j < 2; j++) { + + frame.origin.y = (j == 0 ? 0 : pt1); + textview = [[UITextView alloc] initWithFrame:frame]; + textview.font = (j == 0 ? font1 : font2); + textview.text = (j == 0 ? name : year); + textview.textAlignment = NSTextAlignmentCenter; + textview.showsHorizontalScrollIndicator = NO; + textview.showsVerticalScrollIndicator = NO; + textview.scrollEnabled = NO; + textview.editable = NO; + textview.userInteractionEnabled = NO; + textview.backgroundColor = [UIColor clearColor]; + textview.textColor = (i == 4 + ? [UIColor yellowColor] + : [UIColor blackColor]); + + CALayer *textLayer = (CALayer *) + [textview.layer.sublayers objectAtIndex:0]; + textLayer.shadowColor = [UIColor blackColor].CGColor; + textLayer.shadowOffset = CGSizeMake(0, 0); + textLayer.shadowOpacity = 1; + textLayer.shadowRadius = 2; + + [aboutBox addSubview:textview]; + } + } + + CABasicAnimation *anim = + [CABasicAnimation animationWithKeyPath:@"opacity"]; + anim.duration = 0.3; + anim.repeatCount = 1; + anim.autoreverses = NO; + anim.fromValue = [NSNumber numberWithFloat:0.0]; + anim.toValue = [NSNumber numberWithFloat:1.0]; + [aboutBox.layer addAnimation:anim forKey:@"animateOpacity"]; + + [saverView addSubview:aboutBox]; + + if (splashTimer) + [splashTimer invalidate]; + + splashTimer = + [NSTimer scheduledTimerWithTimeInterval: anim.duration + 2 + target:self + selector:@selector(aboutOff) + userInfo:nil + repeats:NO]; +} + + +- (void)aboutOff +{ + [self aboutOff:FALSE]; +} + +- (void)aboutOff:(BOOL)fast +{ + if (aboutBox) { + if (splashTimer) { + [splashTimer invalidate]; + splashTimer = 0; + } + if (fast) { + aboutBox.layer.opacity = 0; + return; + } + + CABasicAnimation *anim = + [CABasicAnimation animationWithKeyPath:@"opacity"]; + anim.duration = 0.3; + anim.repeatCount = 1; + anim.autoreverses = NO; + anim.fromValue = [NSNumber numberWithFloat: 1]; + anim.toValue = [NSNumber numberWithFloat: 0]; + // anim.delegate = self; + aboutBox.layer.opacity = 0; + [aboutBox.layer addAnimation:anim forKey:@"animateOpacity"]; + } +} + + +- (void)createSaverView +{ + UIView *parentView = self.view; + + if (_saverView) { + [_saverView removeFromSuperview]; + [_saverView release]; + } + +# if 0 + if (_storedOrientation != UIInterfaceOrientationUnknown) { + [[UIApplication sharedApplication] + setStatusBarOrientation:_storedOrientation + animated:NO]; + } +# endif + + _saverView = [_parent newSaverView:_saverName + withSize:parentView.bounds.size]; + + if (! _saverView) { + UIAlertController *c = [UIAlertController + alertControllerWithTitle:@"Unable to load!" + message:@"" + preferredStyle:UIAlertControllerStyleAlert]; + [c addAction: [UIAlertAction actionWithTitle: @"Bummer" + style: UIAlertActionStyleDefault + handler: ^(UIAlertAction *a) { + // #### Should expose the SaverListController... + }]]; + [self presentViewController:c animated:YES completion:nil]; + + return; + } + + _saverView.delegate = _parent; + _saverView.autoresizingMask = + UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + + [self.view addSubview:_saverView]; + + // The first responder must be set only after the view was placed in the view + // heirarchy. + [_saverView becomeFirstResponder]; // For shakes on iOS 6. + [_saverView startAnimation]; + [self aboutPanel:_saverView + orientation:/* _storedOrientation */ UIInterfaceOrientationPortrait]; +} + + +- (void)viewDidAppear:(BOOL)animated +{ + [super viewDidAppear:animated]; + [self createSaverView]; +} + + +- (BOOL)shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)o +{ + return NO; /* Deprecated in iOS 6 */ +} + + +- (BOOL)shouldAutorotate /* Added in iOS 6 */ +{ + return + NSFoundationVersionNumber < NSFoundationVersionNumber_iOS_8_0 ? + ![_saverView suppressRotationAnimation] : + YES; +} + + +- (UIInterfaceOrientationMask)supportedInterfaceOrientations /* Added in iOS 6 */ +{ + // Lies from the iOS docs: + // "This method is only called if the view controller's shouldAutorotate + // method returns YES." + return UIInterfaceOrientationMaskAll; +} + + +/* +- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation +{ + return UIInterfaceOrientationPortrait; +} +*/ + + +- (void)setSaverName:(NSString *)name +{ + [name retain]; + [_saverName release]; + _saverName = name; + // _storedOrientation = + // [UIApplication sharedApplication].statusBarOrientation; + + if (_saverView) + [self createSaverView]; +} + + +- (void)viewWillTransitionToSize: (CGSize)size + withTransitionCoordinator: + (id) coordinator +{ + [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; + + if (!_saverView) + return; + + [CATransaction begin]; + + // Completely suppress the rotation animation, since we + // will not (visually) be rotating at all. + if ([_saverView suppressRotationAnimation]) + [CATransaction setDisableActions:YES]; + + [self aboutOff:TRUE]; // It does goofy things if we rotate while it's up + + [coordinator animateAlongsideTransition:^ + (id context) { + // This executes repeatedly during the rotation. + } completion:^(id context) { + // This executes once when the rotation has finished. + [CATransaction commit]; + [_saverView orientationChanged]; + }]; + // No code goes here, as it would execute before the above completes. +} + +@end + +#endif // USE_IPHONE + + +@implementation SaverRunner + + +- (XScreenSaverView *) newSaverView: (NSString *) module + withSize: (NSSize) size +{ + Class new_class = 0; + +# ifndef USE_IPHONE + + // Load the XScreenSaverView subclass and code from a ".saver" bundle. + + NSString *name = [module stringByAppendingPathExtension:@"saver"]; + NSString *path = [saverDir stringByAppendingPathComponent:name]; + + if (! [[NSFileManager defaultManager] fileExistsAtPath:path]) { + NSLog(@"bundle \"%@\" does not exist", path); + return 0; + } + + NSLog(@"Loading %@", path); + + // NSBundle *obundle = saverBundle; + + saverBundle = [NSBundle bundleWithPath:path]; + if (saverBundle) + new_class = [saverBundle principalClass]; + + // Not entirely unsurprisingly, this tends to break the world. + // if (obundle && obundle != saverBundle) + // [obundle unload]; + +# else // USE_IPHONE + + // Determine whether to create an X11 view or an OpenGL view by + // looking for the "gl" tag in the xml file. This is kind of awful. + + NSString *path = [saverDir + stringByAppendingPathComponent: + [[[module lowercaseString] + stringByReplacingOccurrencesOfString:@" " + withString:@""] + stringByAppendingPathExtension:@"xml"]]; + NSData *xmld = [NSData dataWithContentsOfFile:path]; + NSAssert (xmld, @"no XML: %@", path); + NSString *xml = [XScreenSaverView decompressXML:xmld]; + Bool gl_p = (xml && [xml rangeOfString:@"gl=\"yes\""].length > 0); + + new_class = (gl_p + ? [XScreenSaverGLView class] + : [XScreenSaverView class]); + +# endif // USE_IPHONE + + if (! new_class) + return 0; + + NSRect rect; + rect.origin.x = rect.origin.y = 0; + rect.size.width = size.width; + rect.size.height = size.height; + + XScreenSaverView *instance = + [(XScreenSaverView *) [new_class alloc] + initWithFrame:rect + saverName:module + isPreview:YES]; + if (! instance) { + NSLog(@"Failed to instantiate %@ for \"%@\"", new_class, module); + return 0; + } + + + /* KLUGE: Inform the underlying program that we're in "standalone" + mode, e.g. running as "Phosphor.app" rather than "Phosphor.saver". + This is kind of horrible but I haven't thought of a more sensible + way to make this work. + */ +# ifndef USE_IPHONE + if ([saverNames count] == 1) { + setenv ("XSCREENSAVER_STANDALONE", "1", 1); + } +# endif + + return (XScreenSaverView *) instance; +} + + +#ifndef USE_IPHONE + +static ScreenSaverView * +find_saverView_child (NSView *v) +{ + NSArray *kids = [v subviews]; + NSUInteger nkids = [kids count]; + NSUInteger i; + for (i = 0; i < nkids; i++) { + NSObject *kid = [kids objectAtIndex:i]; + if ([kid isKindOfClass:[ScreenSaverView class]]) { + return (ScreenSaverView *) kid; + } else { + ScreenSaverView *sv = find_saverView_child ((NSView *) kid); + if (sv) return sv; + } + } + return 0; +} + + +static ScreenSaverView * +find_saverView (NSView *v) +{ + while (1) { + NSView *p = [v superview]; + if (p) v = p; + else break; + } + return find_saverView_child (v); +} + + +/* Changes the contents of the menubar menus to correspond to + the running saver. Desktop only. + */ +static void +relabel_menus (NSObject *v, NSString *old_str, NSString *new_str) +{ + if ([v isKindOfClass:[NSMenu class]]) { + NSMenu *m = (NSMenu *)v; + [m setTitle: [[m title] stringByReplacingOccurrencesOfString:old_str + withString:new_str]]; + NSArray *kids = [m itemArray]; + NSUInteger nkids = [kids count]; + NSUInteger i; + for (i = 0; i < nkids; i++) { + relabel_menus ([kids objectAtIndex:i], old_str, new_str); + } + } else if ([v isKindOfClass:[NSMenuItem class]]) { + NSMenuItem *mi = (NSMenuItem *)v; + [mi setTitle: [[mi title] stringByReplacingOccurrencesOfString:old_str + withString:new_str]]; + NSMenu *m = [mi submenu]; + if (m) relabel_menus (m, old_str, new_str); + } +} + + +- (void) openPreferences: (id) sender +{ + ScreenSaverView *sv; + if ([sender isKindOfClass:[NSView class]]) { // Sent from button + sv = find_saverView ((NSView *) sender); + } else { + long i; + NSWindow *w = 0; + for (i = [windows count]-1; i >= 0; i--) { // Sent from menubar + w = [windows objectAtIndex:i]; + if ([w isKeyWindow]) break; + } + sv = find_saverView ([w contentView]); + } + + NSAssert (sv, @"no saver view"); + if (!sv) return; + NSWindow *prefs = [sv configureSheet]; + + [NSApp beginSheet:prefs + modalForWindow:[sv window] + modalDelegate:self + didEndSelector:@selector(preferencesClosed:returnCode:contextInfo:) + contextInfo:nil]; + NSUInteger code = [NSApp runModalForWindow:prefs]; + + /* Restart the animation if the "OK" button was hit, but not if "Cancel". + We have to restart *both* animations, because the xlockmore-style + ones will blow up if one re-inits but the other doesn't. + */ + if (code != NSCancelButton) { + if ([sv isAnimating]) + [sv stopAnimation]; + [sv startAnimation]; + } +} + + +- (void) preferencesClosed: (NSWindow *) sheet + returnCode: (int) returnCode + contextInfo: (void *) contextInfo +{ + [NSApp stopModalWithCode:returnCode]; +} + +#else // USE_IPHONE + + +- (UIImage *) screenshot +{ + return saved_screenshot; +} + +- (void) saveScreenshot +{ + // Most of this is from: + // http://developer.apple.com/library/ios/#qa/qa1703/_index.html + // The rotation stuff is by me. + + CGSize size = [[UIScreen mainScreen] bounds].size; + + // iOS 7: Needs to be [[window rootViewController] interfaceOrientation]. + // iOS 8: Needs to be UIInterfaceOrientationPortrait. + // (interfaceOrientation deprecated in iOS 8) + + UIInterfaceOrientation orient = UIInterfaceOrientationPortrait; + /* iOS 8 broke -[UIScreen bounds]. */ + + if (orient == UIInterfaceOrientationLandscapeLeft || + orient == UIInterfaceOrientationLandscapeRight) { + // Rotate the shape of the canvas 90 degrees. + double s = size.width; + size.width = size.height; + size.height = s; + } + + + // Create a graphics context with the target size + // On iOS 4 and later, use UIGraphicsBeginImageContextWithOptions to + // take the scale into consideration + // On iOS prior to 4, fall back to use UIGraphicsBeginImageContext + + UIGraphicsBeginImageContextWithOptions (size, NO, 0); + + CGContextRef ctx = UIGraphicsGetCurrentContext(); + + + // Rotate the graphics context to match current hardware rotation. + // + switch (orient) { + case UIInterfaceOrientationPortraitUpsideDown: + CGContextTranslateCTM (ctx, [window center].x, [window center].y); + CGContextRotateCTM (ctx, M_PI); + CGContextTranslateCTM (ctx, -[window center].x, -[window center].y); + break; + case UIInterfaceOrientationLandscapeLeft: + case UIInterfaceOrientationLandscapeRight: + CGContextTranslateCTM (ctx, + ([window frame].size.height - + [window frame].size.width) / 2, + ([window frame].size.width - + [window frame].size.height) / 2); + CGContextTranslateCTM (ctx, [window center].x, [window center].y); + CGContextRotateCTM (ctx, + (orient == UIInterfaceOrientationLandscapeLeft + ? M_PI/2 + : -M_PI/2)); + CGContextTranslateCTM (ctx, -[window center].x, -[window center].y); + break; + default: + break; + } + + // Iterate over every window from back to front + // + for (UIWindow *win in [[UIApplication sharedApplication] windows]) { + if (![win respondsToSelector:@selector(screen)] || + [win screen] == [UIScreen mainScreen]) { + + // -renderInContext: renders in the coordinate space of the layer, + // so we must first apply the layer's geometry to the graphics context + CGContextSaveGState (ctx); + + // Center the context around the window's anchor point + CGContextTranslateCTM (ctx, [win center].x, [win center].y); + + // Apply the window's transform about the anchor point + CGContextConcatCTM (ctx, [win transform]); + + // Offset by the portion of the bounds left of and above anchor point + CGContextTranslateCTM (ctx, + -[win bounds].size.width * [[win layer] anchorPoint].x, + -[win bounds].size.height * [[win layer] anchorPoint].y); + + // Render the layer hierarchy to the current context + [[win layer] renderInContext:ctx]; + + // Restore the context + CGContextRestoreGState (ctx); + } + } + + if (saved_screenshot) + [saved_screenshot release]; + saved_screenshot = [UIGraphicsGetImageFromCurrentImageContext() retain]; + + UIGraphicsEndImageContext(); +} + + +- (void) openPreferences: (NSString *) saver +{ + XScreenSaverView *saverView = [self newSaverView:saver + withSize:CGSizeMake(0, 0)]; + if (! saverView) return; + + NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults]; + [prefs setObject:saver forKey:@"selectedSaverName"]; + [prefs synchronize]; + + [rotating_nav pushViewController: [saverView configureView] + animated:YES]; +} + + +#endif // USE_IPHONE + + + +- (void)loadSaver:(NSString *)name +{ +# ifndef USE_IPHONE + + if (saverName && [saverName isEqualToString: name]) { + for (NSWindow *win in windows) { + ScreenSaverView *sv = find_saverView ([win contentView]); + if (![sv isAnimating]) + [sv startAnimation]; + } + return; + } + + saverName = name; + + for (NSWindow *win in windows) { + NSView *cv = [win contentView]; + NSString *old_title = [win title]; + if (!old_title) old_title = @"XScreenSaver"; + [win setTitle: name]; + relabel_menus (menubar, old_title, name); + + ScreenSaverView *old_view = find_saverView (cv); + NSView *sup = old_view ? [old_view superview] : cv; + + if (old_view) { + if ([old_view isAnimating]) + [old_view stopAnimation]; + [old_view removeFromSuperview]; + } + + NSSize size = [cv frame].size; + ScreenSaverView *new_view = [self newSaverView:name withSize: size]; + NSAssert (new_view, @"unable to make a saver view"); + + [new_view setFrame: (old_view ? [old_view frame] : [cv frame])]; + [sup addSubview: new_view]; + [win makeFirstResponder:new_view]; + [new_view setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable]; + [new_view startAnimation]; + [new_view release]; + } + + NSUserDefaultsController *ctl = + [NSUserDefaultsController sharedUserDefaultsController]; + [ctl save:self]; + +# else // USE_IPHONE + +# if !defined __OPTIMIZE__ || TARGET_IPHONE_SIMULATOR + NSLog (@"selecting saver \"%@\"", name); +# endif + + NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults]; + [prefs setObject:name forKey:@"selectedSaverName"]; + [prefs synchronize]; + +/* Cacheing this screws up rotation when starting a saver twice in a row. + if (saverName && [saverName isEqualToString: name]) { + if ([saverView isAnimating]) + return; + else + goto LAUNCH; + } +*/ + + saverName = name; + + if (nonrotating_controller) { + nonrotating_controller.saverName = name; + return; + } + +# if !defined __OPTIMIZE__ || TARGET_IPHONE_SIMULATOR + UIScreen *screen = [UIScreen mainScreen]; + + /* 'nativeScale' is very confusing. + + iPhone 4s: + bounds: 320x480 scale: 2 + nativeBounds: 640x960 nativeScale: 2 + iPhone 5s: + bounds: 320x568 scale: 2 + nativeBounds: 640x1136 nativeScale: 2 + iPad 2: + bounds: 768x1024 scale: 1 + nativeBounds: 768x1024 nativeScale: 1 + iPad Retina/Air: + bounds: 768x1024 scale: 2 + nativeBounds: 1536x2048 nativeScale: 2 + iPhone 6: + bounds: 320x568 scale: 2 + nativeBounds: 640x1136 nativeScale: 2 + iPhone 6+: + bounds: 320x568 scale: 2 + nativeBounds: 960x1704 nativeScale: 3 + + According to a StackOverflow comment: + + The iPhone 6+ renders internally using @3x assets at a virtual + resolution of 2208x1242 (with 736x414 points), then samples that down + for display. The same as using a scaled resolution on a Retina MacBook + -- it lets them hit an integral multiple for pixel assets while still + having e.g. 12pt text look the same size on the screen. + + The 6, the 5s, the 5, the 4s and the 4 are all 326 pixels per inch, + and use @2x assets to stick to the approximately 160 points per inch + of all previous devices. + + The 6+ is 401 pixels per inch. So it'd hypothetically need roughly + @2.46x assets. Instead Apple uses @3x assets and scales the complete + output down to about 84% of its natural size. + + In practice Apple has decided to go with more like 87%, turning the + 1080 into 1242. No doubt that was to find something as close as + possible to 84% that still produced integral sizes in both directions + -- 1242/1080 = 2208/1920 exactly, whereas if you'd turned the 1080 + into, say, 1286, you'd somehow need to render 2286.22 pixels + vertically to scale well. + */ + + NSLog(@"screen: %.0fx%0.f", + [[screen currentMode] size].width, + [[screen currentMode] size].height); + NSLog(@"bounds: %.0fx%0.f x %.1f = %.0fx%0.f", + [screen bounds].size.width, + [screen bounds].size.height, + [screen scale], + [screen scale] * [screen bounds].size.width, + [screen scale] * [screen bounds].size.height); + +# ifdef __IPHONE_8_0 + if ([screen respondsToSelector:@selector(nativeBounds)]) + NSLog(@"native: %.0fx%0.f / %.1f = %.0fx%0.f", + [screen nativeBounds].size.width, + [screen nativeBounds].size.height, + [screen nativeScale], + [screen nativeBounds].size.width / [screen nativeScale], + [screen nativeBounds].size.height / [screen nativeScale]); +# endif +# endif // TARGET_IPHONE_SIMULATOR + + // Take the screen shot before creating the screen saver view, because this + // can screw with the layout. + [self saveScreenshot]; + + // iOS 3.2. Before this were iPhones (and iPods) only, which always did modal + // presentation full screen. + rotating_nav.modalPresentationStyle = UIModalPresentationFullScreen; + + nonrotating_controller = [[SaverViewController alloc] + initWithSaverRunner:self + showAboutBox:[saverNames count] != 1]; + nonrotating_controller.saverName = name; + + /* LAUNCH: */ + + [rotating_nav presentViewController:nonrotating_controller animated:NO completion:nil]; + + // Doing this makes savers cut back to the list instead of fading, + // even though [XScreenSaverView stopAndClose] does setHidden:NO first. + // [window setHidden:YES]; + +# endif // USE_IPHONE +} + + +#ifndef USE_IPHONE + +- (void)aboutPanel:(id)sender +{ + NSDictionary *bd = [saverBundle infoDictionary]; + NSMutableDictionary *d = [NSMutableDictionary dictionaryWithCapacity:20]; + + [d setValue:[bd objectForKey:@"CFBundleName"] forKey:@"ApplicationName"]; + [d setValue:[bd objectForKey:@"CFBundleVersion"] forKey:@"Version"]; + [d setValue:[bd objectForKey:@"CFBundleShortVersionString"] + forKey:@"ApplicationVersion"]; + [d setValue:[bd objectForKey:@"NSHumanReadableCopyright"] forKey:@"Copy"]; + NSAttributedString *s = [[NSAttributedString alloc] + initWithString: (NSString *) + [bd objectForKey:@"CFBundleGetInfoString"]]; + [d setValue:s forKey:@"Credits"]; + [s release]; + + [[NSApplication sharedApplication] + orderFrontStandardAboutPanelWithOptions:d]; +} + +#endif // !USE_IPHONE + + + +- (void)selectedSaverDidChange:(NSDictionary *)change +{ + NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults]; + NSString *name = [prefs stringForKey:@"selectedSaverName"]; + + if (! name) return; + + if (! [saverNames containsObject:name]) { + NSLog (@"saver \"%@\" does not exist", name); + return; + } + + [self loadSaver: name]; +} + + +- (NSArray *) listSaverBundleNamesInDir:(NSString *)dir +{ +# ifndef USE_IPHONE + NSString *ext = @"saver"; +# else + NSString *ext = @"xml"; +# endif + + NSArray *files = [[NSFileManager defaultManager] + contentsOfDirectoryAtPath:dir error:nil]; + if (! files) return 0; + NSMutableArray *result = [NSMutableArray arrayWithCapacity: [files count]+1]; + + for (NSString *p in files) { + if ([[p pathExtension] caseInsensitiveCompare: ext]) + continue; + + NSString *name = [[p lastPathComponent] stringByDeletingPathExtension]; + +# ifdef USE_IPHONE + // Get the saver name's capitalization right by reading the XML file. + + p = [dir stringByAppendingPathComponent: p]; + NSData *xmld = [NSData dataWithContentsOfFile:p]; + NSAssert (xmld, @"no XML: %@", p); + NSString *xml = [XScreenSaverView decompressXML:xmld]; + NSRange r = [xml rangeOfString:@"_label=\"" options:0]; + NSAssert1 (r.length, @"no name in %@", p); + if (r.length) { + xml = [xml substringFromIndex: r.location + r.length]; + r = [xml rangeOfString:@"\"" options:0]; + if (r.length) name = [xml substringToIndex: r.location]; + } + +# endif // USE_IPHONE + + NSAssert1 (name, @"no name in %@", p); + if (name) [result addObject: name]; + } + + if (! [result count]) + result = 0; + + return result; +} + + + +- (NSArray *) listSaverBundleNames +{ + NSMutableArray *dirs = [NSMutableArray arrayWithCapacity: 10]; + +# ifndef USE_IPHONE + // On MacOS, look in the "Contents/Resources/" and "Contents/PlugIns/" + // directories in the bundle. + [dirs addObject: [[[[NSBundle mainBundle] bundlePath] + stringByAppendingPathComponent:@"Contents"] + stringByAppendingPathComponent:@"Resources"]]; + [dirs addObject: [[NSBundle mainBundle] builtInPlugInsPath]]; + + // Also look in the same directory as the executable. + [dirs addObject: [[[NSBundle mainBundle] bundlePath] + stringByDeletingLastPathComponent]]; + + // Finally, look in standard MacOS screensaver directories. +// [dirs addObject: @"~/Library/Screen Savers"]; +// [dirs addObject: @"/Library/Screen Savers"]; +// [dirs addObject: @"/System/Library/Screen Savers"]; + +# else // USE_IPHONE + + // On iOS, only look in the bundle's root directory. + [dirs addObject: [[NSBundle mainBundle] bundlePath]]; + +# endif // USE_IPHONE + + int i; + for (i = 0; i < [dirs count]; i++) { + NSString *dir = [dirs objectAtIndex:i]; + NSArray *names = [self listSaverBundleNamesInDir:dir]; + if (! names) continue; + saverDir = [dir retain]; + saverNames = [names retain]; + return names; + } + + NSString *err = @"no .saver bundles found in: "; + for (i = 0; i < [dirs count]; i++) { + if (i) err = [err stringByAppendingString:@", "]; + err = [err stringByAppendingString:[[dirs objectAtIndex:i] + stringByAbbreviatingWithTildeInPath]]; + err = [err stringByAppendingString:@"/"]; + } + NSLog (@"%@", err); + return [NSArray array]; +} + + +/* Create the popup menu of available saver names. + */ +#ifndef USE_IPHONE + +- (NSPopUpButton *) makeMenu +{ + NSRect rect; + rect.origin.x = rect.origin.y = 0; + rect.size.width = 10; + rect.size.height = 10; + NSPopUpButton *popup = [[NSPopUpButton alloc] initWithFrame:rect + pullsDown:NO]; + int i; + float max_width = 0; + for (i = 0; i < [saverNames count]; i++) { + NSString *name = [saverNames objectAtIndex:i]; + [popup addItemWithTitle:name]; + [[popup itemWithTitle:name] setRepresentedObject:name]; + [popup sizeToFit]; + NSRect r = [popup frame]; + if (r.size.width > max_width) max_width = r.size.width; + } + + // Bind the menu to preferences, and trigger a callback when an item + // is selected. + // + NSString *key = @"values.selectedSaverName"; + NSUserDefaultsController *prefs = + [NSUserDefaultsController sharedUserDefaultsController]; + [prefs addObserver:self + forKeyPath:key + options:0 + context:@selector(selectedSaverDidChange:)]; + [popup bind:@"selectedObject" + toObject:prefs + withKeyPath:key + options:nil]; + [prefs setAppliesImmediately:YES]; + + NSRect r = [popup frame]; + r.size.width = max_width; + [popup setFrame:r]; + [popup autorelease]; + return popup; +} + +#else // USE_IPHONE + +- (NSString *) makeDesc:(NSString *)saver + yearOnly:(BOOL) yearp +{ + NSString *desc = 0; + NSString *path = [saverDir stringByAppendingPathComponent: + [[saver lowercaseString] + stringByReplacingOccurrencesOfString:@" " + withString:@""]]; + NSRange r; + + path = [path stringByAppendingPathExtension:@"xml"]; + NSData *xmld = [NSData dataWithContentsOfFile:path]; + if (! xmld) goto FAIL; + desc = [XScreenSaverView decompressXML:xmld]; + if (! desc) goto FAIL; + + r = [desc rangeOfString:@"<_description>" + options:NSCaseInsensitiveSearch]; + if (r.length == 0) { + desc = 0; + goto FAIL; + } + desc = [desc substringFromIndex: r.location + r.length]; + r = [desc rangeOfString:@"" + options:NSCaseInsensitiveSearch]; + if (r.length > 0) + desc = [desc substringToIndex: r.location]; + + // Leading and trailing whitespace. + desc = [desc stringByTrimmingCharactersInSet: + [NSCharacterSet whitespaceAndNewlineCharacterSet]]; + + // Let's see if we can find a year on the last line. + r = [desc rangeOfString:@"\n" options:NSBackwardsSearch]; + NSString *year = 0; + for (NSString *word in + [[desc substringFromIndex:r.location + r.length] + componentsSeparatedByCharactersInSet: + [NSCharacterSet characterSetWithCharactersInString: + @" \t\n-."]]) { + int n = [word doubleValue]; + if (n > 1970 && n < 2100) + year = word; + } + + // Delete everything after the first blank line. + // + r = [desc rangeOfString:@"\n\n" options:0]; + if (r.length > 0) + desc = [desc substringToIndex: r.location]; + + // Unwrap lines and compress whitespace. + { + NSString *result = @""; + for (NSString *s in [desc componentsSeparatedByCharactersInSet: + [NSCharacterSet whitespaceAndNewlineCharacterSet]]) { + if ([result length] == 0) + result = s; + else if ([s length] > 0) + result = [NSString stringWithFormat: @"%@ %@", result, s]; + desc = result; + } + } + + if (year) + desc = [year stringByAppendingString: + [@": " stringByAppendingString: desc]]; + + if (yearp) + desc = year ? year : @""; + +FAIL: + if (! desc) { + if ([saverNames count] > 1) + desc = @"Oops, this module appears to be incomplete."; + else + desc = @""; + } + + return desc; +} + +- (NSString *) makeDesc:(NSString *)saver +{ + return [self makeDesc:saver yearOnly:NO]; +} + + + +/* Create a dictionary of one-line descriptions of every saver, + for display on the UITableView. + */ +- (NSDictionary *)makeDescTable +{ + NSMutableDictionary *dict = + [NSMutableDictionary dictionaryWithCapacity:[saverNames count]]; + for (NSString *saver in saverNames) { + [dict setObject:[self makeDesc:saver] forKey:saver]; + } + return dict; +} + + +- (void) wantsFadeOut:(XScreenSaverView *)sender +{ + rotating_nav.view.hidden = NO; // In case it was hidden during startup. + + /* The XScreenSaverView screws with the status bar orientation, mostly to + keep the simulator oriented properly. But on iOS 8.1 (and maybe 8.0 + and/or 8.2), this confuses the UINavigationController, so put the + orientation back to portrait before dismissing the SaverViewController. + */ +# if 0 + [[UIApplication sharedApplication] + setStatusBarOrientation:UIInterfaceOrientationPortrait + animated:NO]; +# endif + + /* Make sure the most-recently-run saver is visible. Sometimes it ends + up scrolled half a line off the bottom of the screen. + */ + if (saverName) { + for (UIViewController *v in [rotating_nav viewControllers]) { + if ([v isKindOfClass:[SaverListController class]]) { + [(SaverListController *)v scrollTo: saverName]; + break; + } + } + } + + [rotating_nav dismissViewControllerAnimated:YES completion:^() { + [nonrotating_controller release]; + nonrotating_controller = nil; + [[rotating_nav view] becomeFirstResponder]; + }]; +} + + +- (void) didShake:(XScreenSaverView *)sender +{ +# if TARGET_IPHONE_SIMULATOR + NSLog (@"simulating shake on saver list"); +# endif + [[rotating_nav topViewController] motionEnded: UIEventSubtypeMotionShake + withEvent: nil]; +} + + +#endif // USE_IPHONE + + + +/* This is called when the "selectedSaverName" pref changes, e.g., + when a menu selection is made. + */ +- (void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object + change:(NSDictionary *)change + context:(void *)context +{ + SEL dispatchSelector = (SEL)context; + if (dispatchSelector != NULL) { + [self performSelector:dispatchSelector withObject:change]; + } else { + [super observeValueForKeyPath:keyPath + ofObject:object + change:change + context:context]; + } +} + + +# ifndef USE_IPHONE + +/* Create the desktop window shell, possibly including a preferences button. + */ +- (NSWindow *) makeWindow +{ + NSRect rect; + static int count = 0; + Bool simple_p = ([saverNames count] == 1); + NSButton *pb = 0; + NSPopUpButton *menu = 0; + NSBox *gbox = 0; + NSBox *pbox = 0; + + NSRect sv_rect; + sv_rect.origin.x = sv_rect.origin.y = 0; + sv_rect.size.width = 320; + sv_rect.size.height = 240; + ScreenSaverView *sv = [[ScreenSaverView alloc] // dummy placeholder + initWithFrame:sv_rect + isPreview:YES]; + + // make a "Preferences" button + // + if (! simple_p) { + rect.origin.x = 0; + rect.origin.y = 0; + rect.size.width = rect.size.height = 10; + pb = [[NSButton alloc] initWithFrame:rect]; + [pb setTitle:@"Preferences"]; + [pb setBezelStyle:NSRoundedBezelStyle]; + [pb sizeToFit]; + + rect.origin.x = ([sv frame].size.width - + [pb frame].size.width) / 2; + [pb setFrameOrigin:rect.origin]; + + // grab the click + // + [pb setTarget:self]; + [pb setAction:@selector(openPreferences:)]; + + // Make a saver selection menu + // + menu = [self makeMenu]; + rect.origin.x = 2; + rect.origin.y = 2; + [menu setFrameOrigin:rect.origin]; + + // make a box to wrap the saverView + // + rect = [sv frame]; + rect.origin.x = 0; + rect.origin.y = [pb frame].origin.y + [pb frame].size.height; + gbox = [[NSBox alloc] initWithFrame:rect]; + rect.size.width = rect.size.height = 10; + [gbox setContentViewMargins:rect.size]; + [gbox setTitlePosition:NSNoTitle]; + [gbox addSubview:sv]; + [gbox sizeToFit]; + + // make a box to wrap the other two boxes + // + rect.origin.x = rect.origin.y = 0; + rect.size.width = [gbox frame].size.width; + rect.size.height = [gbox frame].size.height + [gbox frame].origin.y; + pbox = [[NSBox alloc] initWithFrame:rect]; + [pbox setTitlePosition:NSNoTitle]; + [pbox setBorderType:NSNoBorder]; + [pbox addSubview:gbox]; + [gbox release]; + if (menu) [pbox addSubview:menu]; + if (pb) [pbox addSubview:pb]; + [pb release]; + [pbox sizeToFit]; + + [pb setAutoresizingMask:NSViewMinXMargin|NSViewMaxXMargin]; + [menu setAutoresizingMask:NSViewMinXMargin|NSViewMaxXMargin]; + [gbox setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable]; + [pbox setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable]; + } + + [sv setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable]; + + + // and make a window to hold that. + // + NSScreen *screen = [NSScreen mainScreen]; + rect = pbox ? [pbox frame] : [sv frame]; + rect.origin.x = ([screen frame].size.width - rect.size.width) / 2; + rect.origin.y = ([screen frame].size.height - rect.size.height) / 2; + + rect.origin.x += rect.size.width * (count ? 0.55 : -0.55); + + NSWindow *win = [[NSWindow alloc] + initWithContentRect:rect + styleMask:(NSTitledWindowMask | + NSClosableWindowMask | + NSMiniaturizableWindowMask | + NSResizableWindowMask) + backing:NSBackingStoreBuffered + defer:YES + screen:screen]; +// [win setMinSize:[win frameRectForContentRect:rect].size]; + [[win contentView] addSubview: (pbox ? (NSView *) pbox : (NSView *) sv)]; + [pbox release]; + + [win makeKeyAndOrderFront:win]; + + [sv startAnimation]; // this is the dummy saver + [sv autorelease]; + + count++; + + return win; +} + + +- (void) animTimer +{ + for (NSWindow *win in windows) { + ScreenSaverView *sv = find_saverView ([win contentView]); + if ([sv isAnimating]) + [sv animateOneFrame]; + } +} + +# endif // !USE_IPHONE + + +- (void)applicationDidFinishLaunching: +# ifndef USE_IPHONE + (NSNotification *) notif +# else // USE_IPHONE + (UIApplication *) application +# endif // USE_IPHONE +{ + [self listSaverBundleNames]; + + NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults]; + +# ifndef USE_IPHONE + int window_count = ([saverNames count] <= 1 ? 1 : 2); + NSMutableArray *a = [[NSMutableArray arrayWithCapacity: window_count+1] + retain]; + windows = a; + + int i; + // Create either one window (for standalone, e.g. Phosphor.app) + // or two windows for SaverTester.app. + for (i = 0; i < window_count; i++) { + NSWindow *win = [self makeWindow]; + [win setDelegate:self]; + // Get the last-saved window position out of preferences. + [win setFrameAutosaveName: + [NSString stringWithFormat:@"XScreenSaverWindow%d", i]]; + [win setFrameUsingName:[win frameAutosaveName]]; + [a addObject: win]; + // This prevents clicks from being seen by savers. + // [win setMovableByWindowBackground:YES]; + win.releasedWhenClosed = NO; + [win release]; + } +# else // USE_IPHONE + +# undef ya_rand_init + ya_rand_init (0); // Now's a good time. + + + /* iOS docs say: + "You must call this method before attempting to get orientation data from + the receiver. This method enables the device's accelerometer hardware + and begins the delivery of acceleration events to the receiver." + + Adding or removing this doesn't seem to make any difference. It's + probably getting called by the UINavigationController. Still... */ + [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; + + rotating_nav = [[[RotateyViewController alloc] initWithRotation:YES] + retain]; + + if ([prefs boolForKey:@"wasRunning"]) // Prevents menu flicker on startup. + rotating_nav.view.hidden = YES; + + [window setRootViewController: rotating_nav]; + [window setAutoresizesSubviews:YES]; + [window setAutoresizingMask: + (UIViewAutoresizingFlexibleWidth | + UIViewAutoresizingFlexibleHeight)]; + + SaverListController *menu = [[SaverListController alloc] + initWithNames:saverNames + descriptions:[self makeDescTable]]; + [rotating_nav pushViewController:menu animated:YES]; + [menu becomeFirstResponder]; + [menu autorelease]; + + application.applicationSupportsShakeToEdit = YES; + + +# endif // USE_IPHONE + + NSString *forced = 0; + /* In the XCode project, each .saver scheme sets this env var when + launching SaverTester.app so that it knows which one we are + currently debugging. If this is set, it overrides the default + selection in the popup menu. If unset, that menu persists to + whatever it was last time. + */ + const char *f = getenv ("SELECTED_SAVER"); + if (f && *f) + forced = [NSString stringWithCString:(char *)f + encoding:NSUTF8StringEncoding]; + + if (forced && ![saverNames containsObject:forced]) { + NSLog(@"forced saver \"%@\" does not exist", forced); + forced = 0; + } + + // If there's only one saver, run that. + if (!forced && [saverNames count] == 1) + forced = [saverNames objectAtIndex:0]; + +# ifdef USE_IPHONE + NSString *prev = [prefs stringForKey:@"selectedSaverName"]; + + if (forced) + prev = forced; + + // If nothing was selected (e.g., this is the first launch) + // then scroll randomly instead of starting up at "A". + // + if (!prev) + prev = [saverNames objectAtIndex: (random() % [saverNames count])]; + + if (prev) + [menu scrollTo: prev]; +# endif // USE_IPHONE + + if (forced) + [prefs setObject:forced forKey:@"selectedSaverName"]; + +# ifdef USE_IPHONE + /* Don't auto-launch the saver unless it was running last time. + XScreenSaverView manages this, on crash_timer. + Unless forced. + */ + if (!forced && ![prefs boolForKey:@"wasRunning"]) + return; +# endif + + [self selectedSaverDidChange:nil]; +// [NSTimer scheduledTimerWithTimeInterval: 0 +// target:self +// selector:@selector(selectedSaverDidChange:) +// userInfo:nil +// repeats:NO]; + + + +# ifndef USE_IPHONE + /* On 10.8 and earlier, [ScreenSaverView startAnimation] causes the + ScreenSaverView to run its own timer calling animateOneFrame. + On 10.9, that fails because the private class ScreenSaverModule + is only initialized properly by ScreenSaverEngine, and in the + context of SaverRunner, the null ScreenSaverEngine instance + behaves as if [ScreenSaverEngine needsAnimationTimer] returned false. + So, if it looks like this is the 10.9 version of ScreenSaverModule + instead of the 10.8 version, we run our own timer here. This sucks. + */ + if (!anim_timer) { + Class ssm = NSClassFromString (@"ScreenSaverModule"); + if (ssm && [ssm instancesRespondToSelector: + NSSelectorFromString(@"needsAnimationTimer")]) { + NSWindow *win = [windows objectAtIndex:0]; + ScreenSaverView *sv = find_saverView ([win contentView]); + anim_timer = [NSTimer scheduledTimerWithTimeInterval: + [sv animationTimeInterval] + target:self + selector:@selector(animTimer) + userInfo:nil + repeats:YES]; + } + } +# endif // !USE_IPHONE +} + + +#ifndef USE_IPHONE + +/* When the window closes, exit (even if prefs still open.) + */ +- (BOOL) applicationShouldTerminateAfterLastWindowClosed: (NSApplication *) n +{ + return YES; +} + +/* When the window is about to close, stop its animation. + Without this, timers might fire after the window is dead. + */ +- (void)windowWillClose:(NSNotification *)notification +{ + NSWindow *win = [notification object]; + NSView *cv = win ? [win contentView] : 0; + ScreenSaverView *sv = cv ? find_saverView (cv) : 0; + if (sv && [sv isAnimating]) + [sv stopAnimation]; +} + +# else // USE_IPHONE + +- (void)applicationWillResignActive:(UIApplication *)app +{ + [(XScreenSaverView *)view setScreenLocked:YES]; +} + +- (void)applicationDidBecomeActive:(UIApplication *)app +{ + [(XScreenSaverView *)view setScreenLocked:NO]; +} + +- (void)applicationDidEnterBackground:(UIApplication *)application +{ + [(XScreenSaverView *)view setScreenLocked:YES]; +} + +#endif // USE_IPHONE + + +@end -- cgit v1.2.3-55-g7522