1 /* xscreensaver, Copyright (c) 2006-2012 Jamie Zawinski <jwz@jwz.org>
3 * Permission to use, copy, modify, distribute, and sell this software and its
4 * documentation for any purpose is hereby granted without fee, provided that
5 * the above copyright notice appear in all copies and that both that
6 * copyright notice and this permission notice appear in supporting
7 * documentation. No representations are made about the suitability of this
8 * software for any purpose. It is provided "as is" without express or
12 /* This program serves three purposes:
14 First, It is a test harness for screen savers. When it launches, it
15 looks around for .saver bundles (in the current directory, and then in
16 the standard directories) and puts up a pair of windows that allow you
17 to select the saver to run. This is less clicking than running them
18 through System Preferences. This is the "SaverTester.app" program.
20 Second, it can be used to transform any screen saver into a standalone
21 program. Just put one (and only one) .saver bundle into the app
22 bundle's Contents/PlugIns/ directory, and it will load and run that
23 saver at start-up (without the saver-selection menu or other chrome).
24 This is how the "Phosphor.app" and "Apple2.app" programs work.
26 Third, it is the scaffolding which turns a set of screen savers into
27 a single iPhone / iPad program. In that case, all of the savers are
28 linked in to this executable, since iOS does not allow dynamic loading
29 of bundles that have executable code in them. Bleh.
32 #import "SaverRunner.h"
33 #import "SaverListController.h"
34 #import "XScreenSaverGLView.h"
35 #import <TargetConditionals.h>
39 @interface RotateyViewController : UINavigationController
42 @implementation RotateyViewController
43 - (BOOL)shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)o
52 @implementation SaverRunner
55 - (ScreenSaverView *) makeSaverView: (NSString *) module
56 withSize: (NSSize) size
62 // Load the XScreenSaverView subclass and code from a ".saver" bundle.
64 NSString *name = [module stringByAppendingPathExtension:@"saver"];
65 NSString *path = [saverDir stringByAppendingPathComponent:name];
67 if (! [[NSFileManager defaultManager] fileExistsAtPath:path]) {
68 NSLog(@"bundle \"%@\" does not exist", path);
72 NSLog(@"Loading %@", path);
74 // NSBundle *obundle = saverBundle;
76 saverBundle = [NSBundle bundleWithPath:path];
78 new_class = [saverBundle principalClass];
80 // Not entirely unsurprisingly, this tends to break the world.
81 // if (obundle && obundle != saverBundle)
86 // Determine whether to create an X11 view or an OpenGL view by
87 // looking for the "gl" tag in the xml file. This is kind of awful.
89 NSString *path = [saverDir
90 stringByAppendingPathComponent:
91 [[[module lowercaseString]
92 stringByReplacingOccurrencesOfString:@" "
94 stringByAppendingPathExtension:@"xml"]];
95 NSString *xml = [NSString stringWithContentsOfFile:path
96 encoding:NSISOLatin1StringEncoding
98 NSAssert (xml, @"no XML: %@", path);
99 Bool gl_p = (xml && [xml rangeOfString:@"gl=\"yes\""].length > 0);
102 ? [XScreenSaverGLView class]
103 : [XScreenSaverView class]);
105 # endif // USE_IPHONE
111 rect.origin.x = rect.origin.y = 0;
112 rect.size.width = size.width;
113 rect.size.height = size.height;
115 XScreenSaverView *instance =
116 [(XScreenSaverView *) [new_class alloc]
120 if (! instance) return 0;
123 /* KLUGE: Inform the underlying program that we're in "standalone"
124 mode, e.g. running as "Phosphor.app" rather than "Phosphor.saver".
125 This is kind of horrible but I haven't thought of a more sensible
126 way to make this work.
129 if ([saverNames count] == 1) {
130 putenv (strdup ("XSCREENSAVER_STANDALONE=1"));
134 return (ScreenSaverView *) instance;
140 static ScreenSaverView *
141 find_saverView_child (NSView *v)
143 NSArray *kids = [v subviews];
144 int nkids = [kids count];
146 for (i = 0; i < nkids; i++) {
147 NSObject *kid = [kids objectAtIndex:i];
148 if ([kid isKindOfClass:[ScreenSaverView class]]) {
149 return (ScreenSaverView *) kid;
151 ScreenSaverView *sv = find_saverView_child ((NSView *) kid);
159 static ScreenSaverView *
160 find_saverView (NSView *v)
163 NSView *p = [v superview];
167 return find_saverView_child (v);
171 /* Changes the contents of the menubar menus to correspond to
172 the running saver. Desktop only.
175 relabel_menus (NSObject *v, NSString *old_str, NSString *new_str)
177 if ([v isKindOfClass:[NSMenu class]]) {
178 NSMenu *m = (NSMenu *)v;
179 [m setTitle: [[m title] stringByReplacingOccurrencesOfString:old_str
180 withString:new_str]];
181 NSArray *kids = [m itemArray];
182 int nkids = [kids count];
184 for (i = 0; i < nkids; i++) {
185 relabel_menus ([kids objectAtIndex:i], old_str, new_str);
187 } else if ([v isKindOfClass:[NSMenuItem class]]) {
188 NSMenuItem *mi = (NSMenuItem *)v;
189 [mi setTitle: [[mi title] stringByReplacingOccurrencesOfString:old_str
190 withString:new_str]];
191 NSMenu *m = [mi submenu];
192 if (m) relabel_menus (m, old_str, new_str);
197 - (void) openPreferences: (id) sender
200 if ([sender isKindOfClass:[NSView class]]) { // Sent from button
201 sv = find_saverView ((NSView *) sender);
205 for (i = [windows count]-1; i >= 0; i--) { // Sent from menubar
206 w = [windows objectAtIndex:i];
207 if ([w isKeyWindow]) break;
209 sv = find_saverView ([w contentView]);
212 NSAssert (sv, @"no saver view");
213 NSWindow *prefs = [sv configureSheet];
215 [NSApp beginSheet:prefs
216 modalForWindow:[sv window]
218 didEndSelector:@selector(preferencesClosed:returnCode:contextInfo:)
220 int code = [NSApp runModalForWindow:prefs];
222 /* Restart the animation if the "OK" button was hit, but not if "Cancel".
223 We have to restart *both* animations, because the xlockmore-style
224 ones will blow up if one re-inits but the other doesn't.
226 if (code != NSCancelButton) {
227 if ([sv isAnimating])
234 - (void) preferencesClosed: (NSWindow *) sheet
235 returnCode: (int) returnCode
236 contextInfo: (void *) contextInfo
238 [NSApp stopModalWithCode:returnCode];
244 - (UIImage *) screenshot
246 return saved_screenshot;
249 - (void) saveScreenshot
251 // Most of this is from:
252 // http://developer.apple.com/library/ios/#qa/qa1703/_index.html
253 // The rotation stuff is by me.
255 CGSize size = [[UIScreen mainScreen] bounds].size;
257 UIInterfaceOrientation orient =
258 [[window rootViewController] interfaceOrientation];
259 if (orient == UIInterfaceOrientationLandscapeLeft ||
260 orient == UIInterfaceOrientationLandscapeRight) {
261 // Rotate the shape of the canvas 90 degrees.
262 double s = size.width;
263 size.width = size.height;
268 // Create a graphics context with the target size
269 // On iOS 4 and later, use UIGraphicsBeginImageContextWithOptions to
270 // take the scale into consideration
271 // On iOS prior to 4, fall back to use UIGraphicsBeginImageContext
273 if (UIGraphicsBeginImageContextWithOptions)
274 UIGraphicsBeginImageContextWithOptions (size, NO, 0);
276 UIGraphicsBeginImageContext (size);
278 CGContextRef ctx = UIGraphicsGetCurrentContext();
281 // Rotate the graphics context to match current hardware rotation.
284 case UIInterfaceOrientationPortraitUpsideDown:
285 CGContextTranslateCTM (ctx, [window center].x, [window center].y);
286 CGContextRotateCTM (ctx, M_PI);
287 CGContextTranslateCTM (ctx, -[window center].x, -[window center].y);
289 case UIInterfaceOrientationLandscapeLeft:
290 case UIInterfaceOrientationLandscapeRight:
291 CGContextTranslateCTM (ctx,
292 ([window frame].size.height -
293 [window frame].size.width) / 2,
294 ([window frame].size.width -
295 [window frame].size.height) / 2);
296 CGContextTranslateCTM (ctx, [window center].x, [window center].y);
297 CGContextRotateCTM (ctx,
298 (orient == UIInterfaceOrientationLandscapeLeft
301 CGContextTranslateCTM (ctx, -[window center].x, -[window center].y);
307 // Iterate over every window from back to front
309 for (UIWindow *win in [[UIApplication sharedApplication] windows]) {
310 if (![win respondsToSelector:@selector(screen)] ||
311 [win screen] == [UIScreen mainScreen]) {
313 // -renderInContext: renders in the coordinate space of the layer,
314 // so we must first apply the layer's geometry to the graphics context
315 CGContextSaveGState (ctx);
317 // Center the context around the window's anchor point
318 CGContextTranslateCTM (ctx, [win center].x, [win center].y);
320 // Apply the window's transform about the anchor point
321 CGContextConcatCTM (ctx, [win transform]);
323 // Offset by the portion of the bounds left of and above anchor point
324 CGContextTranslateCTM (ctx,
325 -[win bounds].size.width * [[win layer] anchorPoint].x,
326 -[win bounds].size.height * [[win layer] anchorPoint].y);
328 // Render the layer hierarchy to the current context
329 [[win layer] renderInContext:ctx];
331 // Restore the context
332 CGContextRestoreGState (ctx);
336 if (saved_screenshot)
337 [saved_screenshot release];
338 saved_screenshot = [UIGraphicsGetImageFromCurrentImageContext() retain];
340 UIGraphicsEndImageContext();
344 - (void) openPreferences: (NSString *) saver
346 [self loadSaver:saver launch:NO];
347 if (! saverView) return;
349 NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
350 [prefs setObject:saver forKey:@"selectedSaverName"];
353 [rootViewController pushViewController: [saverView configureView]
362 - (void)loadSaver:(NSString *)name launch:(BOOL)launch
364 // NSLog (@"selecting saver \"%@\"", name);
368 if (saverName && [saverName isEqualToString: name]) {
370 for (NSWindow *win in windows) {
371 ScreenSaverView *sv = find_saverView ([win contentView]);
372 if (![sv isAnimating])
380 for (NSWindow *win in windows) {
381 NSView *cv = [win contentView];
382 NSString *old_title = [win title];
383 if (!old_title) old_title = @"XScreenSaver";
384 [win setTitle: name];
385 relabel_menus (menubar, old_title, name);
387 ScreenSaverView *old_view = find_saverView (cv);
388 NSView *sup = old_view ? [old_view superview] : cv;
391 if ([old_view isAnimating])
392 [old_view stopAnimation];
393 [old_view removeFromSuperview];
396 NSSize size = [cv frame].size;
397 ScreenSaverView *new_view = [self makeSaverView:name withSize: size];
398 NSAssert (new_view, @"unable to make a saver view");
400 [new_view setFrame: (old_view ? [old_view frame] : [cv frame])];
401 [sup addSubview: new_view];
402 [win makeFirstResponder:new_view];
403 [new_view setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
406 [new_view startAnimation];
409 NSUserDefaultsController *ctl =
410 [NSUserDefaultsController sharedUserDefaultsController];
415 NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
416 [prefs setObject:name forKey:@"selectedSaverName"];
419 if (saverName && [saverName isEqualToString: name]) {
420 if (launch && ![saverView isAnimating]) {
421 [window addSubview: saverView];
422 [saverView startAnimation];
430 if ([saverView isAnimating])
431 [saverView stopAnimation];
432 [saverView removeFromSuperview];
435 NSSize size = [window frame].size;
436 saverView = [self makeSaverView:name withSize: size];
439 [[[UIAlertView alloc] initWithTitle: name
440 message: @"Unable to load!"
442 cancelButtonTitle: @"Bummer"
443 otherButtonTitles: nil]
448 [saverView setFrame: [window frame]];
450 [[NSNotificationCenter defaultCenter]
451 addObserver:saverView
452 selector:@selector(didRotate:)
453 name:UIDeviceOrientationDidChangeNotification object:nil];
456 [window addSubview: saverView];
457 [saverView startAnimation];
459 # endif // USE_IPHONE
463 - (void)loadSaver:(NSString *)name
465 [self loadSaver:name launch:YES];
469 - (void)aboutPanel:(id)sender
472 NSDictionary *bd = [saverBundle infoDictionary];
473 NSMutableDictionary *d = [NSMutableDictionary dictionaryWithCapacity:20];
475 [d setValue:[bd objectForKey:@"CFBundleName"] forKey:@"ApplicationName"];
476 [d setValue:[bd objectForKey:@"CFBundleVersion"] forKey:@"Version"];
477 [d setValue:[bd objectForKey:@"CFBundleShortVersionString"]
478 forKey:@"ApplicationVersion"];
479 [d setValue:[bd objectForKey:@"NSHumanReadableCopyright"] forKey:@"Copy"];
480 [d setValue:[[NSAttributedString alloc]
481 initWithString: (NSString *)
482 [bd objectForKey:@"CFBundleGetInfoString"]]
485 [[NSApplication sharedApplication]
486 orderFrontStandardAboutPanelWithOptions:d];
490 NSDictionary *bd = [[NSBundle mainBundle] infoDictionary];
491 NSString *body = [bd objectForKey:@"CFBundleGetInfoString"];
493 body = [body stringByReplacingOccurrencesOfString:@", " withString:@",\n"];
494 body = [body stringByAppendingString:
496 "Double-tap to run.\n\n"
497 "Double-tap again to\n"
498 "return to this list."];
500 [[[UIAlertView alloc] initWithTitle: @""
503 cancelButtonTitle: @"OK"
504 otherButtonTitles: nil]
507 # endif // USE_IPHONE
512 - (void)selectedSaverDidChange:(NSDictionary *)change
514 NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
515 NSString *name = [prefs stringForKey:@"selectedSaverName"];
519 if (! [saverNames containsObject:name]) {
520 NSLog (@"saver \"%@\" does not exist", name);
524 [self loadSaver: name];
528 - (NSArray *) listSaverBundleNamesInDir:(NSString *)dir
531 NSString *ext = @"saver";
533 NSString *ext = @"xml";
536 NSArray *files = [[NSFileManager defaultManager]
537 contentsOfDirectoryAtPath:dir error:nil];
538 if (! files) return 0;
539 NSMutableArray *result = [NSMutableArray arrayWithCapacity: [files count]+1];
541 for (NSString *p in files) {
542 if ([[p pathExtension] caseInsensitiveCompare: ext])
546 NSString *name = [[p lastPathComponent] stringByDeletingPathExtension];
547 # else // !USE_IPHONE
549 // Get the saver name's capitalization right by reading the XML file.
551 p = [dir stringByAppendingPathComponent: p];
552 NSString *name = [NSString stringWithContentsOfFile:p
553 encoding:NSISOLatin1StringEncoding
555 NSRange r = [name rangeOfString:@"_label=\"" options:0];
556 name = [name substringFromIndex: r.location + r.length];
557 r = [name rangeOfString:@"\"" options:0];
558 name = [name substringToIndex: r.location];
560 NSAssert1 (name, @"no name in %@", p);
562 # endif // !USE_IPHONE
564 [result addObject: name];
572 - (NSArray *) listSaverBundleNames
574 NSMutableArray *dirs = [NSMutableArray arrayWithCapacity: 10];
577 // On MacOS, look in the "Contents/PlugIns/" directory in the bundle.
578 [dirs addObject: [[NSBundle mainBundle] builtInPlugInsPath]];
580 // Also look in the same directory as the executable.
581 [dirs addObject: [[[NSBundle mainBundle] bundlePath]
582 stringByDeletingLastPathComponent]];
584 // Finally, look in standard MacOS screensaver directories.
585 [dirs addObject: @"~/Library/Screen Savers"];
586 [dirs addObject: @"/Library/Screen Savers"];
587 [dirs addObject: @"/System/Library/Screen Savers"];
590 // On iOS, just look in the bundle's root directory.
591 [dirs addObject: [[NSBundle mainBundle] bundlePath]];
595 for (i = 0; i < [dirs count]; i++) {
596 NSString *dir = [dirs objectAtIndex:i];
597 NSArray *names = [self listSaverBundleNamesInDir:dir];
598 if (! names) continue;
599 saverDir = [dir retain];
600 saverNames = [names retain];
604 NSString *err = @"no .saver bundles found in: ";
605 for (i = 0; i < [dirs count]; i++) {
606 if (i) err = [err stringByAppendingString:@", "];
607 err = [err stringByAppendingString:[[dirs objectAtIndex:i]
608 stringByAbbreviatingWithTildeInPath]];
609 err = [err stringByAppendingString:@"/"];
612 return [NSArray array];
616 /* Create the popup menu of available saver names.
620 - (NSPopUpButton *) makeMenu
623 rect.origin.x = rect.origin.y = 0;
624 rect.size.width = 10;
625 rect.size.height = 10;
626 NSPopUpButton *popup = [[NSPopUpButton alloc] initWithFrame:rect
630 for (i = 0; i < [saverNames count]; i++) {
631 NSString *name = [saverNames objectAtIndex:i];
632 [popup addItemWithTitle:name];
633 [[popup itemWithTitle:name] setRepresentedObject:name];
635 NSRect r = [popup frame];
636 if (r.size.width > max_width) max_width = r.size.width;
639 // Bind the menu to preferences, and trigger a callback when an item
642 NSString *key = @"values.selectedSaverName";
643 NSUserDefaultsController *prefs =
644 [NSUserDefaultsController sharedUserDefaultsController];
645 [prefs addObserver:self
648 context:@selector(selectedSaverDidChange:)];
649 [popup bind:@"selectedObject"
653 [prefs setAppliesImmediately:YES];
655 NSRect r = [popup frame];
656 r.size.width = max_width;
663 /* Create a dictionary of one-line descriptions of every saver,
664 for display on the UITableView.
666 - (NSDictionary *)makeDescTable
668 NSMutableDictionary *dict =
669 [NSMutableDictionary dictionaryWithCapacity:[saverNames count]];
671 for (NSString *saver in saverNames) {
673 NSString *path = [saverDir stringByAppendingPathComponent:
674 [[saver lowercaseString]
675 stringByReplacingOccurrencesOfString:@" "
679 path = [path stringByAppendingPathExtension:@"xml"];
680 desc = [NSString stringWithContentsOfFile:path
681 encoding:NSISOLatin1StringEncoding
683 if (! desc) goto FAIL;
685 r = [desc rangeOfString:@"<_description>"
686 options:NSCaseInsensitiveSearch];
691 desc = [desc substringFromIndex: r.location + r.length];
692 r = [desc rangeOfString:@"</_description>"
693 options:NSCaseInsensitiveSearch];
695 desc = [desc substringToIndex: r.location];
697 // Leading and trailing whitespace.
698 desc = [desc stringByTrimmingCharactersInSet:
699 [NSCharacterSet whitespaceAndNewlineCharacterSet]];
701 // Let's see if we can find a year on the last line.
702 r = [desc rangeOfString:@"\n" options:NSBackwardsSearch];
704 for (NSString *word in
705 [[desc substringFromIndex:r.location + r.length]
706 componentsSeparatedByCharactersInSet:
707 [NSCharacterSet characterSetWithCharactersInString:
709 int n = [word doubleValue];
710 if (n > 1970 && n < 2100)
714 // Delete everything after the first blank line.
715 r = [desc rangeOfString:@"\n\n" options:0];
717 desc = [desc substringToIndex: r.location];
719 // Truncate really long ones.
721 if ([desc length] > max)
722 desc = [desc substringToIndex: max];
725 desc = [year stringByAppendingString:
726 [@": " stringByAppendingString: desc]];
730 desc = @"Oops, this module appears to be incomplete.";
731 // NSLog(@"broken saver: %@", path);
734 [dict setObject:desc forKey:saver];
745 /* This is called when the "selectedSaverName" pref changes, e.g.,
746 when a menu selection is made.
748 - (void)observeValueForKeyPath:(NSString *)keyPath
750 change:(NSDictionary *)change
751 context:(void *)context
753 SEL dispatchSelector = (SEL)context;
754 if (dispatchSelector != NULL) {
755 [self performSelector:dispatchSelector withObject:change];
757 [super observeValueForKeyPath:keyPath
767 /* Create the desktop window shell, possibly including a preferences button.
769 - (NSWindow *) makeWindow
772 static int count = 0;
773 Bool simple_p = ([saverNames count] == 1);
775 NSPopUpButton *menu = 0;
780 sv_rect.origin.x = sv_rect.origin.y = 0;
781 sv_rect.size.width = 320;
782 sv_rect.size.height = 240;
783 ScreenSaverView *sv = [[ScreenSaverView alloc] // dummy placeholder
784 initWithFrame:sv_rect
787 // make a "Preferences" button
792 rect.size.width = rect.size.height = 10;
793 pb = [[NSButton alloc] initWithFrame:rect];
794 [pb setTitle:@"Preferences"];
795 [pb setBezelStyle:NSRoundedBezelStyle];
798 rect.origin.x = ([sv frame].size.width -
799 [pb frame].size.width) / 2;
800 [pb setFrameOrigin:rect.origin];
805 [pb setAction:@selector(openPreferences:)];
807 // Make a saver selection menu
809 menu = [self makeMenu];
812 [menu setFrameOrigin:rect.origin];
814 // make a box to wrap the saverView
818 rect.origin.y = [pb frame].origin.y + [pb frame].size.height;
819 gbox = [[NSBox alloc] initWithFrame:rect];
820 rect.size.width = rect.size.height = 10;
821 [gbox setContentViewMargins:rect.size];
822 [gbox setTitlePosition:NSNoTitle];
823 [gbox addSubview:sv];
826 // make a box to wrap the other two boxes
828 rect.origin.x = rect.origin.y = 0;
829 rect.size.width = [gbox frame].size.width;
830 rect.size.height = [gbox frame].size.height + [gbox frame].origin.y;
831 pbox = [[NSBox alloc] initWithFrame:rect];
832 [pbox setTitlePosition:NSNoTitle];
833 [pbox setBorderType:NSNoBorder];
834 [pbox addSubview:gbox];
835 if (menu) [pbox addSubview:menu];
836 if (pb) [pbox addSubview:pb];
839 [pb setAutoresizingMask:NSViewMinXMargin|NSViewMaxXMargin];
840 [menu setAutoresizingMask:NSViewMinXMargin|NSViewMaxXMargin];
841 [gbox setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
842 [pbox setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
845 [sv setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
848 // and make a window to hold that.
850 NSScreen *screen = [NSScreen mainScreen];
851 rect = pbox ? [pbox frame] : [sv frame];
852 rect.origin.x = ([screen frame].size.width - rect.size.width) / 2;
853 rect.origin.y = ([screen frame].size.height - rect.size.height) / 2;
855 rect.origin.x += rect.size.width * (count ? 0.55 : -0.55);
857 NSWindow *win = [[NSWindow alloc]
858 initWithContentRect:rect
859 styleMask:(NSTitledWindowMask |
860 NSClosableWindowMask |
861 NSMiniaturizableWindowMask |
862 NSResizableWindowMask)
863 backing:NSBackingStoreBuffered
866 [win setMinSize:[win frameRectForContentRect:rect].size];
867 [[win contentView] addSubview: (pbox ? (NSView *) pbox : (NSView *) sv)];
869 [win makeKeyAndOrderFront:win];
871 [sv startAnimation]; // this is the dummy saver
878 # endif // !USE_IPHONE
881 - (void)applicationDidFinishLaunching:
883 (NSNotification *) notif
885 (UIApplication *) application
886 # endif // USE_IPHONE
888 [self listSaverBundleNames];
891 int window_count = ([saverNames count] <= 1 ? 1 : 2);
892 NSMutableArray *a = [[NSMutableArray arrayWithCapacity: window_count+1]
897 // Create either one window (for standalone, e.g. Phosphor.app)
898 // or two windows for SaverTester.app.
899 for (i = 0; i < window_count; i++) {
900 NSWindow *win = [self makeWindow];
901 // Get the last-saved window position out of preferences.
902 [win setFrameAutosaveName:
903 [NSString stringWithFormat:@"XScreenSaverWindow%d", i]];
904 [win setFrameUsingName:[win frameAutosaveName]];
910 ya_rand_init (0); // Now's a good time.
912 rootViewController = [[[RotateyViewController alloc] init] retain];
913 [window setRootViewController: rootViewController];
915 SaverListController *menu = [[SaverListController alloc]
916 initWithNames:saverNames
917 descriptions:[self makeDescTable]];
918 [rootViewController pushViewController:menu animated:YES];
920 [window makeKeyAndVisible];
921 [window setAutoresizesSubviews:YES];
922 [window setAutoresizingMask:
923 (UIViewAutoresizingFlexibleWidth |
924 UIViewAutoresizingFlexibleHeight)];
926 // Has to be after the list window is up, or we get black.
927 [self saveScreenshot];
929 # endif // USE_IPHONE
931 NSString *forced = 0;
932 /* In the XCode project, each .saver scheme sets this env var when
933 launching SaverTester.app so that it knows which one we are
934 currently debugging. If this is set, it overrides the default
935 selection in the popup menu. If unset, that menu persists to
936 whatever it was last time.
938 const char *f = getenv ("SELECTED_SAVER");
940 forced = [NSString stringWithCString:(char *)f
941 encoding:NSUTF8StringEncoding];
943 if (forced && ![saverNames containsObject:forced]) {
944 NSLog(@"forced saver \"%@\" does not exist", forced);
948 // If there's only one saver, run that.
949 if (!forced && [saverNames count] == 1)
950 forced = [saverNames objectAtIndex:0];
952 NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
955 NSString *prev = [prefs stringForKey:@"selectedSaverName"];
960 // If nothing was selected (e.g., this is the first launch)
961 // then scroll randomly instead of starting up at "A".
964 prev = [saverNames objectAtIndex: (random() % [saverNames count])];
967 [menu scrollTo: prev];
968 # endif // USE_IPHONE
971 [prefs setObject:forced forKey:@"selectedSaverName"];
974 /* Don't auto-launch the saver unless it was running last time.
975 XScreenSaverView manages this, on crash_timer.
977 if (! [prefs boolForKey:@"wasRunning"])
981 [self selectedSaverDidChange:nil];
987 /* When the window closes, exit (even if prefs still open.)
989 - (BOOL) applicationShouldTerminateAfterLastWindowClosed: (NSApplication *) n
996 - (void)applicationWillResignActive:(UIApplication *)app
998 [(XScreenSaverView *)view setScreenLocked:YES];
1001 - (void)applicationDidBecomeActive:(UIApplication *)app
1003 [(XScreenSaverView *)view setScreenLocked:NO];
1006 - (void)applicationDidEnterBackground:(UIApplication *)application
1008 [(XScreenSaverView *)view setScreenLocked:YES];
1011 #endif // USE_IPHONE