X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=OSX%2FSaverRunner.m;h=80031e775f6456b895cda3c6da664462e5a4317c;hb=d5186197bc394e10a4402f7f6d23fbb14103bc50;hp=3fd451d9ae2b23cb7680e6d34cb3f3b93bc06e5c;hpb=6f5482d73adb0165c0130bb47d852644ab0c4869;p=xscreensaver diff --git a/OSX/SaverRunner.m b/OSX/SaverRunner.m index 3fd451d9..80031e77 100644 --- a/OSX/SaverRunner.m +++ b/OSX/SaverRunner.m @@ -1,4 +1,4 @@ -/* xscreensaver, Copyright (c) 2006-2012 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,7 +19,7 @@ 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/PlugIns/ directory, and it will load and run that + 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. @@ -38,13 +38,40 @@ #ifdef USE_IPHONE @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 YES; + return allowRotation; /* Deprecated in iOS 6 */ } + +- (BOOL)shouldAutorotate /* Added in iOS 6 */ +{ + return allowRotation; +} + +- (NSUInteger)supportedInterfaceOrientations /* Added in iOS 6 */ +{ + return UIInterfaceOrientationMaskAll; +} + @end #endif // USE_IPHONE @@ -93,10 +120,9 @@ stringByReplacingOccurrencesOfString:@" " withString:@""] stringByAppendingPathExtension:@"xml"]]; - NSString *xml = [NSString stringWithContentsOfFile:path - encoding:NSISOLatin1StringEncoding - error:nil]; - NSAssert (xml, @"no XML: %@", path); + 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 @@ -118,7 +144,10 @@ initWithFrame:rect saverName:module isPreview:YES]; - if (! instance) return 0; + if (! instance) { + NSLog(@"Failed to instantiate %@ for \"%@\"", new_class, module); + return 0; + } /* KLUGE: Inform the underlying program that we're in "standalone" @@ -211,6 +240,7 @@ relabel_menus (NSObject *v, NSString *old_str, NSString *new_str) } NSAssert (sv, @"no saver view"); + if (!sv) return; NSWindow *prefs = [sv configureSheet]; [NSApp beginSheet:prefs @@ -351,7 +381,7 @@ relabel_menus (NSObject *v, NSString *old_str, NSString *new_str) [prefs setObject:saver forKey:@"selectedSaverName"]; [prefs synchronize]; - [rootViewController pushViewController: [saverView configureView] + [rotating_nav pushViewController: [saverView configureView] animated:YES]; } @@ -362,8 +392,6 @@ relabel_menus (NSObject *v, NSString *old_str, NSString *new_str) - (void)loadSaver:(NSString *)name launch:(BOOL)launch { - // NSLog (@"selecting saver \"%@\"", name); - # ifndef USE_IPHONE if (saverName && [saverName isEqualToString: name]) { @@ -413,6 +441,10 @@ relabel_menus (NSObject *v, NSString *old_str, NSString *new_str) # else // USE_IPHONE +# if TARGET_IPHONE_SIMULATOR + NSLog (@"selecting saver \"%@\"", name); +# endif + NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults]; [prefs setObject:name forKey:@"selectedSaverName"]; [prefs synchronize]; @@ -430,10 +462,32 @@ relabel_menus (NSObject *v, NSString *old_str, NSString *new_str) if ([saverView isAnimating]) [saverView stopAnimation]; [saverView removeFromSuperview]; + [backgroundView removeFromSuperview]; + [[NSNotificationCenter defaultCenter] removeObserver:saverView]; + [saverView release]; } - NSSize size = [window frame].size; - saverView = [self makeSaverView:name withSize: size]; + /* We can't just use [window bounds] because that is the *rotated* rectangle + and we need the *unrotated* rectangle, so that the view is always created + in portrait orientation. Without that, the initial rotation event that + takes us from unknown->landscape will be out of step with reality. + */ + UIScreen *screen = [UIScreen mainScreen]; +# ifndef __IPHONE_8_0 // iOS 7 SDK + NSSize size = [screen bounds].size; + int ss = [screen scale]; +# else // iOS 8 SDK + NSSize size = ([screen respondsToSelector:@selector(nativeBounds)] + ? [screen nativeBounds].size // iOS 8 + : [screen bounds].size); // iOS 7 + int ss = ([screen respondsToSelector:@selector(nativeScale)] + ? [screen nativeScale] // iOS 8 + : [screen scale]); // iOS 7 +# endif // iOS 8 SDK + + size.width /= ss; + size.height /= ss; + saverView = [self makeSaverView:name withSize:size]; if (! saverView) { [[[UIAlertView alloc] initWithTitle: name @@ -445,19 +499,41 @@ relabel_menus (NSObject *v, NSString *old_str, NSString *new_str) return; } - [saverView setFrame: [window frame]]; - [saverView retain]; [[NSNotificationCenter defaultCenter] addObserver:saverView selector:@selector(didRotate:) name:UIDeviceOrientationDidChangeNotification object:nil]; LAUNCH: + if (launch) { [self saveScreenshot]; - [window addSubview: saverView]; - [saverView becomeFirstResponder]; + NSRect f = [saverWindow bounds]; + [backgroundView setFrame:f]; + [saverView setFrame:f]; + [saverWindow addSubview: backgroundView]; + [backgroundView addSubview: saverView]; + [saverView setBackgroundColor:[NSColor blackColor]]; + + /* WTF! Without creating and keying this window, we get no events + delivered on the saverView/saverWindow! Bad craziness. + */ + { + UIWindow *dummy = [[UIWindow alloc] initWithFrame:CGRectMake(0,0,0,0)]; + [dummy setRootViewController: nonrotating_nav]; // Must be this one. + [dummy setHidden:NO]; // required + [dummy setHidden:YES]; + [dummy release]; + } + + [saverWindow setHidden:NO]; + [saverWindow makeKeyAndVisible]; [saverView startAnimation]; + [self aboutPanel: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 } @@ -469,10 +545,10 @@ relabel_menus (NSObject *v, NSString *old_str, NSString *new_str) } -# ifndef USE_IPHONE - - (void)aboutPanel:(id)sender { +# ifndef USE_IPHONE + NSDictionary *bd = [saverBundle infoDictionary]; NSMutableDictionary *d = [NSMutableDictionary dictionaryWithCapacity:20]; @@ -488,9 +564,189 @@ relabel_menus (NSObject *v, NSString *old_str, NSString *new_str) [[NSApplication sharedApplication] orderFrontStandardAboutPanelWithOptions:d]; -} +# else // USE_IPHONE + + if ([saverNames count] == 1) + return; + NSString *name = saverName; + NSString *year = [self 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); + + UIInterfaceOrientation orient = [rotating_nav interfaceOrientation]; + + /* 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 UIDeviceOrientationLandscapeRight: + 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 UIDeviceOrientationLandscapeLeft: + rot = M_PI/2; + frame.origin.x = -(tsize.width - tsize.height) / 2; + frame.origin.y = ([saverView frame].size.height - tsize.height) / 2; + break; + case UIDeviceOrientationPortraitUpsideDown: + 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 = [[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"]; + + [backgroundView addSubview:aboutBox]; + + if (splashTimer) + [splashTimer invalidate]; + + splashTimer = + [NSTimer scheduledTimerWithTimeInterval: anim.duration + 2 + target:self + selector:@selector(aboutOff) + userInfo:nil + repeats:NO]; # endif // USE_IPHONE +} + + +# ifdef USE_IPHONE +- (void)aboutOff +{ + if (aboutBox) { + if (splashTimer) { + [splashTimer invalidate]; + splashTimer = 0; + } + 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"]; + } +} +#endif // USE_IPHONE @@ -527,28 +783,32 @@ relabel_menus (NSObject *v, NSString *old_str, NSString *new_str) if ([[p pathExtension] caseInsensitiveCompare: ext]) continue; -# ifndef USE_IPHONE NSString *name = [[p lastPathComponent] stringByDeletingPathExtension]; -# else // !USE_IPHONE +# ifdef USE_IPHONE // Get the saver name's capitalization right by reading the XML file. p = [dir stringByAppendingPathComponent: p]; - NSString *name = [NSString stringWithContentsOfFile:p - encoding:NSISOLatin1StringEncoding - error:nil]; - NSRange r = [name rangeOfString:@"_label=\"" options:0]; - name = [name substringFromIndex: r.location + r.length]; - r = [name rangeOfString:@"\"" options:0]; - name = [name substringToIndex: r.location]; - - NSAssert1 (name, @"no name in %@", 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 +# endif // USE_IPHONE - [result addObject: name]; + NSAssert1 (name, @"no name in %@", p); + if (name) [result addObject: name]; } + if (! [result count]) + result = 0; + return result; } @@ -559,7 +819,11 @@ relabel_menus (NSObject *v, NSString *old_str, NSString *new_str) NSMutableArray *dirs = [NSMutableArray arrayWithCapacity: 10]; # ifndef USE_IPHONE - // On MacOS, look in the "Contents/PlugIns/" directory in the bundle. + // 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. @@ -567,14 +831,16 @@ relabel_menus (NSObject *v, NSString *old_str, NSString *new_str) stringByDeletingLastPathComponent]]; // Finally, look in standard MacOS screensaver directories. - [dirs addObject: @"~/Library/Screen Savers"]; - [dirs addObject: @"/Library/Screen Savers"]; - [dirs addObject: @"/System/Library/Screen Savers"]; +// [dirs addObject: @"~/Library/Screen Savers"]; +// [dirs addObject: @"/Library/Screen Savers"]; +// [dirs addObject: @"/System/Library/Screen Savers"]; -# else - // On iOS, just look in the bundle's root directory. +# else // USE_IPHONE + + // On iOS, only look in the bundle's root directory. [dirs addObject: [[NSBundle mainBundle] bundlePath]]; -# endif + +# endif // USE_IPHONE int i; for (i = 0; i < [dirs count]; i++) { @@ -645,80 +911,105 @@ relabel_menus (NSObject *v, NSString *old_str, NSString *new_str) #else // USE_IPHONE -/* Create a dictionary of one-line descriptions of every saver, - for display on the UITableView. - */ -- (NSDictionary *)makeDescTable +- (NSString *) makeDesc:(NSString *)saver + yearOnly:(BOOL) yearp { - NSMutableDictionary *dict = - [NSMutableDictionary dictionaryWithCapacity:[saverNames count]]; + 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; + } - for (NSString *saver in saverNames) { - NSString *desc = 0; - NSString *path = [saverDir stringByAppendingPathComponent: - [[saver lowercaseString] - stringByReplacingOccurrencesOfString:@" " - withString:@""]]; - NSRange r; - - path = [path stringByAppendingPathExtension:@"xml"]; - desc = [NSString stringWithContentsOfFile:path - encoding:NSISOLatin1StringEncoding - error:nil]; - 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; } + } - // Delete everything after the first blank line. - r = [desc rangeOfString:@"\n\n" options:0]; - if (r.length > 0) - desc = [desc substringToIndex: r.location]; - - // Truncate really long ones. - int max = 140; - if ([desc length] > max) - desc = [desc substringToIndex: max]; + if (year) + desc = [year stringByAppendingString: + [@": " stringByAppendingString: desc]]; - if (year) - desc = [year stringByAppendingString: - [@": " stringByAppendingString: desc]]; + if (yearp) + desc = year ? year : @""; - FAIL: - if (! desc) { +FAIL: + if (! desc) { + if ([saverNames count] > 1) desc = @"Oops, this module appears to be incomplete."; - // NSLog(@"broken saver: %@", path); - } - - [dict setObject:desc forKey:saver]; + 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; } @@ -860,6 +1151,16 @@ relabel_menus (NSObject *v, NSString *old_str, NSString *new_str) return win; } + +- (void) animTimer +{ + for (NSWindow *win in windows) { + ScreenSaverView *sv = find_saverView ([win contentView]); + if ([sv isAnimating]) + [sv animateOneFrame]; + } +} + # endif // !USE_IPHONE @@ -888,29 +1189,71 @@ relabel_menus (NSObject *v, NSString *old_str, NSString *new_str) [NSString stringWithFormat:@"XScreenSaverWindow%d", i]]; [win setFrameUsingName:[win frameAutosaveName]]; [a addObject: win]; + // This prevents clicks from being seen by savers. + // [win setMovableByWindowBackground:YES]; } # else // USE_IPHONE # undef ya_rand_init ya_rand_init (0); // Now's a good time. - rootViewController = [[[RotateyViewController alloc] init] retain]; - [window setRootViewController: rootViewController]; + rotating_nav = [[[RotateyViewController alloc] initWithRotation:YES] + retain]; + [window setRootViewController: rotating_nav]; + [window setAutoresizesSubviews:YES]; + [window setAutoresizingMask: + (UIViewAutoresizingFlexibleWidth | + UIViewAutoresizingFlexibleHeight)]; + + nonrotating_nav = [[[RotateyViewController alloc] initWithRotation:NO] + retain]; + [nonrotating_nav setNavigationBarHidden:YES animated:NO]; + + /* We run the saver on a different UIWindow than the one the + SaverListController and preferences panels run on, because that's + the only way to make rotation work right. We want the system to + handle rotation of the UI stuff, but we want it to keep its hands + off of rotation of the savers. As of iOS 8, this seems to be the + only way to accomplish that. + + Also, we need to create saverWindow with a portrait rectangle, always. + Note that [UIScreen bounds] returns rotated and scaled values. + */ + UIScreen *screen = [UIScreen mainScreen]; +# ifndef __IPHONE_8_0 // iOS 7 SDK + NSRect frame = [screen bounds]; + int ss = [screen scale]; +# else // iOS 8 SDK + NSRect frame = ([screen respondsToSelector:@selector(nativeBounds)] + ? [screen nativeBounds] // iOS 8 + : [screen bounds]); // iOS 7 + int ss = ([screen respondsToSelector:@selector(nativeScale)] + ? [screen nativeScale] // iOS 8 + : [screen scale]); // iOS 7 +# endif // iOS 8 SDK + frame.size.width /= ss; + frame.size.height /= ss; + saverWindow = [[UIWindow alloc] initWithFrame:frame]; + [saverWindow setRootViewController: nonrotating_nav]; + [saverWindow setHidden:YES]; + + /* This view is the parent of the XScreenSaverView, and exists only + so that there is a black background behind it. Without this, when + rotation is in progress, the scrolling-list window's corners show + through in the corners. + */ + backgroundView = [[[NSView class] alloc] initWithFrame:[saverWindow frame]]; + [backgroundView setBackgroundColor:[NSColor blackColor]]; SaverListController *menu = [[SaverListController alloc] initWithNames:saverNames descriptions:[self makeDescTable]]; - [rootViewController pushViewController:menu animated:YES]; + [rotating_nav pushViewController:menu animated:YES]; [menu becomeFirstResponder]; - [window makeKeyAndVisible]; - [window setAutoresizesSubviews:YES]; - [window setAutoresizingMask: - (UIViewAutoresizingFlexibleWidth | - UIViewAutoresizingFlexibleHeight)]; - application.applicationSupportsShakeToEdit = YES; + # endif // USE_IPHONE NSString *forced = 0; @@ -958,19 +1301,53 @@ relabel_menus (NSObject *v, NSString *old_str, NSString *new_str) # ifdef USE_IPHONE /* Don't auto-launch the saver unless it was running last time. XScreenSaverView manages this, on crash_timer. + Unless forced. */ - if (! [prefs boolForKey:@"wasRunning"]) + 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: + @selector(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;