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 <TargetConditionals.h>
33 #import "SaverRunner.h"
34 #import "SaverListController.h"
35 #import "XScreenSaverGLView.h"
40 @interface RotateyViewController : UINavigationController
43 @implementation RotateyViewController
44 - (BOOL)shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)o
53 @implementation SaverRunner
56 - (ScreenSaverView *) makeSaverView: (NSString *) module
57 withSize: (NSSize) size
63 // Load the XScreenSaverView subclass and code from a ".saver" bundle.
65 NSString *name = [module stringByAppendingPathExtension:@"saver"];
66 NSString *path = [saverDir stringByAppendingPathComponent:name];
68 if (! [[NSFileManager defaultManager] fileExistsAtPath:path]) {
69 NSLog(@"bundle \"%@\" does not exist", path);
73 NSLog(@"Loading %@", path);
75 // NSBundle *obundle = saverBundle;
77 saverBundle = [NSBundle bundleWithPath:path];
79 new_class = [saverBundle principalClass];
81 // Not entirely unsurprisingly, this tends to break the world.
82 // if (obundle && obundle != saverBundle)
87 // Determine whether to create an X11 view or an OpenGL view by
88 // looking for the "gl" tag in the xml file. This is kind of awful.
90 NSString *path = [saverDir
91 stringByAppendingPathComponent:
92 [[[module lowercaseString]
93 stringByReplacingOccurrencesOfString:@" "
95 stringByAppendingPathExtension:@"xml"]];
96 NSString *xml = [NSString stringWithContentsOfFile:path
97 encoding:NSISOLatin1StringEncoding
99 NSAssert (xml, @"no XML: %@", path);
100 Bool gl_p = (xml && [xml rangeOfString:@"gl=\"yes\""].length > 0);
103 ? [XScreenSaverGLView class]
104 : [XScreenSaverView class]);
106 # endif // USE_IPHONE
112 rect.origin.x = rect.origin.y = 0;
113 rect.size.width = size.width;
114 rect.size.height = size.height;
116 XScreenSaverView *instance =
117 [(XScreenSaverView *) [new_class alloc]
121 if (! instance) return 0;
124 /* KLUGE: Inform the underlying program that we're in "standalone"
125 mode, e.g. running as "Phosphor.app" rather than "Phosphor.saver".
126 This is kind of horrible but I haven't thought of a more sensible
127 way to make this work.
130 if ([saverNames count] == 1) {
131 putenv (strdup ("XSCREENSAVER_STANDALONE=1"));
135 return (ScreenSaverView *) instance;
141 static ScreenSaverView *
142 find_saverView_child (NSView *v)
144 NSArray *kids = [v subviews];
145 int nkids = [kids count];
147 for (i = 0; i < nkids; i++) {
148 NSObject *kid = [kids objectAtIndex:i];
149 if ([kid isKindOfClass:[ScreenSaverView class]]) {
150 return (ScreenSaverView *) kid;
152 ScreenSaverView *sv = find_saverView_child ((NSView *) kid);
160 static ScreenSaverView *
161 find_saverView (NSView *v)
164 NSView *p = [v superview];
168 return find_saverView_child (v);
172 /* Changes the contents of the menubar menus to correspond to
173 the running saver. Desktop only.
176 relabel_menus (NSObject *v, NSString *old_str, NSString *new_str)
178 if ([v isKindOfClass:[NSMenu class]]) {
179 NSMenu *m = (NSMenu *)v;
180 [m setTitle: [[m title] stringByReplacingOccurrencesOfString:old_str
181 withString:new_str]];
182 NSArray *kids = [m itemArray];
183 int nkids = [kids count];
185 for (i = 0; i < nkids; i++) {
186 relabel_menus ([kids objectAtIndex:i], old_str, new_str);
188 } else if ([v isKindOfClass:[NSMenuItem class]]) {
189 NSMenuItem *mi = (NSMenuItem *)v;
190 [mi setTitle: [[mi title] stringByReplacingOccurrencesOfString:old_str
191 withString:new_str]];
192 NSMenu *m = [mi submenu];
193 if (m) relabel_menus (m, old_str, new_str);
198 - (void) openPreferences: (id) sender
201 if ([sender isKindOfClass:[NSView class]]) { // Sent from button
202 sv = find_saverView ((NSView *) sender);
206 for (i = [windows count]-1; i >= 0; i--) { // Sent from menubar
207 w = [windows objectAtIndex:i];
208 if ([w isKeyWindow]) break;
210 sv = find_saverView ([w contentView]);
213 NSAssert (sv, @"no saver view");
214 NSWindow *prefs = [sv configureSheet];
216 [NSApp beginSheet:prefs
217 modalForWindow:[sv window]
219 didEndSelector:@selector(preferencesClosed:returnCode:contextInfo:)
221 int code = [NSApp runModalForWindow:prefs];
223 /* Restart the animation if the "OK" button was hit, but not if "Cancel".
224 We have to restart *both* animations, because the xlockmore-style
225 ones will blow up if one re-inits but the other doesn't.
227 if (code != NSCancelButton) {
228 if ([sv isAnimating])
235 - (void) preferencesClosed: (NSWindow *) sheet
236 returnCode: (int) returnCode
237 contextInfo: (void *) contextInfo
239 [NSApp stopModalWithCode:returnCode];
245 - (UIImage *) screenshot
247 return saved_screenshot;
250 - (void) saveScreenshot
252 // Most of this is from:
253 // http://developer.apple.com/library/ios/#qa/qa1703/_index.html
254 // The rotation stuff is by me.
256 CGSize size = [[UIScreen mainScreen] bounds].size;
258 UIInterfaceOrientation orient =
259 [[window rootViewController] interfaceOrientation];
260 if (orient == UIInterfaceOrientationLandscapeLeft ||
261 orient == UIInterfaceOrientationLandscapeRight) {
262 // Rotate the shape of the canvas 90 degrees.
263 double s = size.width;
264 size.width = size.height;
269 // Create a graphics context with the target size
270 // On iOS 4 and later, use UIGraphicsBeginImageContextWithOptions to
271 // take the scale into consideration
272 // On iOS prior to 4, fall back to use UIGraphicsBeginImageContext
274 if (UIGraphicsBeginImageContextWithOptions)
275 UIGraphicsBeginImageContextWithOptions (size, NO, 0);
277 UIGraphicsBeginImageContext (size);
279 CGContextRef ctx = UIGraphicsGetCurrentContext();
282 // Rotate the graphics context to match current hardware rotation.
285 case UIInterfaceOrientationPortraitUpsideDown:
286 CGContextTranslateCTM (ctx, [window center].x, [window center].y);
287 CGContextRotateCTM (ctx, M_PI);
288 CGContextTranslateCTM (ctx, -[window center].x, -[window center].y);
290 case UIInterfaceOrientationLandscapeLeft:
291 case UIInterfaceOrientationLandscapeRight:
292 CGContextTranslateCTM (ctx,
293 ([window frame].size.height -
294 [window frame].size.width) / 2,
295 ([window frame].size.width -
296 [window frame].size.height) / 2);
297 CGContextTranslateCTM (ctx, [window center].x, [window center].y);
298 CGContextRotateCTM (ctx,
299 (orient == UIInterfaceOrientationLandscapeLeft
302 CGContextTranslateCTM (ctx, -[window center].x, -[window center].y);
308 // Iterate over every window from back to front
310 for (UIWindow *win in [[UIApplication sharedApplication] windows]) {
311 if (![win respondsToSelector:@selector(screen)] ||
312 [win screen] == [UIScreen mainScreen]) {
314 // -renderInContext: renders in the coordinate space of the layer,
315 // so we must first apply the layer's geometry to the graphics context
316 CGContextSaveGState (ctx);
318 // Center the context around the window's anchor point
319 CGContextTranslateCTM (ctx, [win center].x, [win center].y);
321 // Apply the window's transform about the anchor point
322 CGContextConcatCTM (ctx, [win transform]);
324 // Offset by the portion of the bounds left of and above anchor point
325 CGContextTranslateCTM (ctx,
326 -[win bounds].size.width * [[win layer] anchorPoint].x,
327 -[win bounds].size.height * [[win layer] anchorPoint].y);
329 // Render the layer hierarchy to the current context
330 [[win layer] renderInContext:ctx];
332 // Restore the context
333 CGContextRestoreGState (ctx);
337 if (saved_screenshot)
338 [saved_screenshot release];
339 saved_screenshot = [UIGraphicsGetImageFromCurrentImageContext() retain];
341 UIGraphicsEndImageContext();
345 - (void) openPreferences: (NSString *) saver
347 [self loadSaver:saver launch:NO];
348 if (! saverView) return;
350 NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
351 [prefs setObject:saver forKey:@"selectedSaverName"];
354 [rootViewController pushViewController: [saverView configureView]
363 - (void)loadSaver:(NSString *)name launch:(BOOL)launch
365 // NSLog (@"selecting saver \"%@\"", name);
369 if (saverName && [saverName isEqualToString: name]) {
371 for (NSWindow *win in windows) {
372 ScreenSaverView *sv = find_saverView ([win contentView]);
373 if (![sv isAnimating])
381 for (NSWindow *win in windows) {
382 NSView *cv = [win contentView];
383 NSString *old_title = [win title];
384 if (!old_title) old_title = @"XScreenSaver";
385 [win setTitle: name];
386 relabel_menus (menubar, old_title, name);
388 ScreenSaverView *old_view = find_saverView (cv);
389 NSView *sup = old_view ? [old_view superview] : cv;
392 if ([old_view isAnimating])
393 [old_view stopAnimation];
394 [old_view removeFromSuperview];
397 NSSize size = [cv frame].size;
398 ScreenSaverView *new_view = [self makeSaverView:name withSize: size];
399 NSAssert (new_view, @"unable to make a saver view");
401 [new_view setFrame: (old_view ? [old_view frame] : [cv frame])];
402 [sup addSubview: new_view];
403 [win makeFirstResponder:new_view];
404 [new_view setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
407 [new_view startAnimation];
410 NSUserDefaultsController *ctl =
411 [NSUserDefaultsController sharedUserDefaultsController];
416 NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
417 [prefs setObject:name forKey:@"selectedSaverName"];
420 if (saverName && [saverName isEqualToString: name]) {
421 if ([saverView isAnimating])
429 if (! backgroundView) {
430 // This view is the parent of the XScreenSaverView, and exists only
431 // so that there is a black background behind it. Without this, when
432 // rotation is in progresss, the scrolling-list window's corners show
433 // through in the corners.
434 backgroundView = [[[NSView class] alloc] initWithFrame:[window frame]];
435 [backgroundView setBackgroundColor:[NSColor blackColor]];
439 if ([saverView isAnimating])
440 [saverView stopAnimation];
441 [saverView removeFromSuperview];
442 [backgroundView removeFromSuperview];
445 NSSize size = [window frame].size;
446 saverView = [self makeSaverView:name withSize: size];
449 [[[UIAlertView alloc] initWithTitle: name
450 message: @"Unable to load!"
452 cancelButtonTitle: @"Bummer"
453 otherButtonTitles: nil]
458 [saverView setFrame: [window frame]];
460 [[NSNotificationCenter defaultCenter]
461 addObserver:saverView
462 selector:@selector(didRotate:)
463 name:UIDeviceOrientationDidChangeNotification object:nil];
467 [self saveScreenshot];
468 [window addSubview: backgroundView];
469 [backgroundView addSubview: saverView];
470 [saverView becomeFirstResponder];
471 [saverView startAnimation];
473 # endif // USE_IPHONE
477 - (void)loadSaver:(NSString *)name
479 [self loadSaver:name launch:YES];
485 - (void)aboutPanel:(id)sender
487 NSDictionary *bd = [saverBundle infoDictionary];
488 NSMutableDictionary *d = [NSMutableDictionary dictionaryWithCapacity:20];
490 [d setValue:[bd objectForKey:@"CFBundleName"] forKey:@"ApplicationName"];
491 [d setValue:[bd objectForKey:@"CFBundleVersion"] forKey:@"Version"];
492 [d setValue:[bd objectForKey:@"CFBundleShortVersionString"]
493 forKey:@"ApplicationVersion"];
494 [d setValue:[bd objectForKey:@"NSHumanReadableCopyright"] forKey:@"Copy"];
495 [d setValue:[[NSAttributedString alloc]
496 initWithString: (NSString *)
497 [bd objectForKey:@"CFBundleGetInfoString"]]
500 [[NSApplication sharedApplication]
501 orderFrontStandardAboutPanelWithOptions:d];
504 # endif // USE_IPHONE
508 - (void)selectedSaverDidChange:(NSDictionary *)change
510 NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
511 NSString *name = [prefs stringForKey:@"selectedSaverName"];
515 if (! [saverNames containsObject:name]) {
516 NSLog (@"saver \"%@\" does not exist", name);
520 [self loadSaver: name];
524 - (NSArray *) listSaverBundleNamesInDir:(NSString *)dir
527 NSString *ext = @"saver";
529 NSString *ext = @"xml";
532 NSArray *files = [[NSFileManager defaultManager]
533 contentsOfDirectoryAtPath:dir error:nil];
534 if (! files) return 0;
535 NSMutableArray *result = [NSMutableArray arrayWithCapacity: [files count]+1];
537 for (NSString *p in files) {
538 if ([[p pathExtension] caseInsensitiveCompare: ext])
542 NSString *name = [[p lastPathComponent] stringByDeletingPathExtension];
543 # else // !USE_IPHONE
545 // Get the saver name's capitalization right by reading the XML file.
547 p = [dir stringByAppendingPathComponent: p];
548 NSString *name = [NSString stringWithContentsOfFile:p
549 encoding:NSISOLatin1StringEncoding
551 NSRange r = [name rangeOfString:@"_label=\"" options:0];
552 name = [name substringFromIndex: r.location + r.length];
553 r = [name rangeOfString:@"\"" options:0];
554 name = [name substringToIndex: r.location];
556 NSAssert1 (name, @"no name in %@", p);
558 # endif // !USE_IPHONE
560 [result addObject: name];
568 - (NSArray *) listSaverBundleNames
570 NSMutableArray *dirs = [NSMutableArray arrayWithCapacity: 10];
573 // On MacOS, look in the "Contents/PlugIns/" directory in the bundle.
574 [dirs addObject: [[NSBundle mainBundle] builtInPlugInsPath]];
576 // Also look in the same directory as the executable.
577 [dirs addObject: [[[NSBundle mainBundle] bundlePath]
578 stringByDeletingLastPathComponent]];
580 // Finally, look in standard MacOS screensaver directories.
581 [dirs addObject: @"~/Library/Screen Savers"];
582 [dirs addObject: @"/Library/Screen Savers"];
583 [dirs addObject: @"/System/Library/Screen Savers"];
586 // On iOS, just look in the bundle's root directory.
587 [dirs addObject: [[NSBundle mainBundle] bundlePath]];
591 for (i = 0; i < [dirs count]; i++) {
592 NSString *dir = [dirs objectAtIndex:i];
593 NSArray *names = [self listSaverBundleNamesInDir:dir];
594 if (! names) continue;
595 saverDir = [dir retain];
596 saverNames = [names retain];
600 NSString *err = @"no .saver bundles found in: ";
601 for (i = 0; i < [dirs count]; i++) {
602 if (i) err = [err stringByAppendingString:@", "];
603 err = [err stringByAppendingString:[[dirs objectAtIndex:i]
604 stringByAbbreviatingWithTildeInPath]];
605 err = [err stringByAppendingString:@"/"];
608 return [NSArray array];
612 /* Create the popup menu of available saver names.
616 - (NSPopUpButton *) makeMenu
619 rect.origin.x = rect.origin.y = 0;
620 rect.size.width = 10;
621 rect.size.height = 10;
622 NSPopUpButton *popup = [[NSPopUpButton alloc] initWithFrame:rect
626 for (i = 0; i < [saverNames count]; i++) {
627 NSString *name = [saverNames objectAtIndex:i];
628 [popup addItemWithTitle:name];
629 [[popup itemWithTitle:name] setRepresentedObject:name];
631 NSRect r = [popup frame];
632 if (r.size.width > max_width) max_width = r.size.width;
635 // Bind the menu to preferences, and trigger a callback when an item
638 NSString *key = @"values.selectedSaverName";
639 NSUserDefaultsController *prefs =
640 [NSUserDefaultsController sharedUserDefaultsController];
641 [prefs addObserver:self
644 context:@selector(selectedSaverDidChange:)];
645 [popup bind:@"selectedObject"
649 [prefs setAppliesImmediately:YES];
651 NSRect r = [popup frame];
652 r.size.width = max_width;
659 /* Create a dictionary of one-line descriptions of every saver,
660 for display on the UITableView.
662 - (NSDictionary *)makeDescTable
664 NSMutableDictionary *dict =
665 [NSMutableDictionary dictionaryWithCapacity:[saverNames count]];
667 for (NSString *saver in saverNames) {
669 NSString *path = [saverDir stringByAppendingPathComponent:
670 [[saver lowercaseString]
671 stringByReplacingOccurrencesOfString:@" "
675 path = [path stringByAppendingPathExtension:@"xml"];
676 desc = [NSString stringWithContentsOfFile:path
677 encoding:NSISOLatin1StringEncoding
679 if (! desc) goto FAIL;
681 r = [desc rangeOfString:@"<_description>"
682 options:NSCaseInsensitiveSearch];
687 desc = [desc substringFromIndex: r.location + r.length];
688 r = [desc rangeOfString:@"</_description>"
689 options:NSCaseInsensitiveSearch];
691 desc = [desc substringToIndex: r.location];
693 // Leading and trailing whitespace.
694 desc = [desc stringByTrimmingCharactersInSet:
695 [NSCharacterSet whitespaceAndNewlineCharacterSet]];
697 // Let's see if we can find a year on the last line.
698 r = [desc rangeOfString:@"\n" options:NSBackwardsSearch];
700 for (NSString *word in
701 [[desc substringFromIndex:r.location + r.length]
702 componentsSeparatedByCharactersInSet:
703 [NSCharacterSet characterSetWithCharactersInString:
705 int n = [word doubleValue];
706 if (n > 1970 && n < 2100)
710 // Delete everything after the first blank line.
711 r = [desc rangeOfString:@"\n\n" options:0];
713 desc = [desc substringToIndex: r.location];
715 // Truncate really long ones.
717 if ([desc length] > max)
718 desc = [desc substringToIndex: max];
721 desc = [year stringByAppendingString:
722 [@": " stringByAppendingString: desc]];
726 desc = @"Oops, this module appears to be incomplete.";
727 // NSLog(@"broken saver: %@", path);
730 [dict setObject:desc forKey:saver];
741 /* This is called when the "selectedSaverName" pref changes, e.g.,
742 when a menu selection is made.
744 - (void)observeValueForKeyPath:(NSString *)keyPath
746 change:(NSDictionary *)change
747 context:(void *)context
749 SEL dispatchSelector = (SEL)context;
750 if (dispatchSelector != NULL) {
751 [self performSelector:dispatchSelector withObject:change];
753 [super observeValueForKeyPath:keyPath
763 /* Create the desktop window shell, possibly including a preferences button.
765 - (NSWindow *) makeWindow
768 static int count = 0;
769 Bool simple_p = ([saverNames count] == 1);
771 NSPopUpButton *menu = 0;
776 sv_rect.origin.x = sv_rect.origin.y = 0;
777 sv_rect.size.width = 320;
778 sv_rect.size.height = 240;
779 ScreenSaverView *sv = [[ScreenSaverView alloc] // dummy placeholder
780 initWithFrame:sv_rect
783 // make a "Preferences" button
788 rect.size.width = rect.size.height = 10;
789 pb = [[NSButton alloc] initWithFrame:rect];
790 [pb setTitle:@"Preferences"];
791 [pb setBezelStyle:NSRoundedBezelStyle];
794 rect.origin.x = ([sv frame].size.width -
795 [pb frame].size.width) / 2;
796 [pb setFrameOrigin:rect.origin];
801 [pb setAction:@selector(openPreferences:)];
803 // Make a saver selection menu
805 menu = [self makeMenu];
808 [menu setFrameOrigin:rect.origin];
810 // make a box to wrap the saverView
814 rect.origin.y = [pb frame].origin.y + [pb frame].size.height;
815 gbox = [[NSBox alloc] initWithFrame:rect];
816 rect.size.width = rect.size.height = 10;
817 [gbox setContentViewMargins:rect.size];
818 [gbox setTitlePosition:NSNoTitle];
819 [gbox addSubview:sv];
822 // make a box to wrap the other two boxes
824 rect.origin.x = rect.origin.y = 0;
825 rect.size.width = [gbox frame].size.width;
826 rect.size.height = [gbox frame].size.height + [gbox frame].origin.y;
827 pbox = [[NSBox alloc] initWithFrame:rect];
828 [pbox setTitlePosition:NSNoTitle];
829 [pbox setBorderType:NSNoBorder];
830 [pbox addSubview:gbox];
831 if (menu) [pbox addSubview:menu];
832 if (pb) [pbox addSubview:pb];
835 [pb setAutoresizingMask:NSViewMinXMargin|NSViewMaxXMargin];
836 [menu setAutoresizingMask:NSViewMinXMargin|NSViewMaxXMargin];
837 [gbox setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
838 [pbox setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
841 [sv setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
844 // and make a window to hold that.
846 NSScreen *screen = [NSScreen mainScreen];
847 rect = pbox ? [pbox frame] : [sv frame];
848 rect.origin.x = ([screen frame].size.width - rect.size.width) / 2;
849 rect.origin.y = ([screen frame].size.height - rect.size.height) / 2;
851 rect.origin.x += rect.size.width * (count ? 0.55 : -0.55);
853 NSWindow *win = [[NSWindow alloc]
854 initWithContentRect:rect
855 styleMask:(NSTitledWindowMask |
856 NSClosableWindowMask |
857 NSMiniaturizableWindowMask |
858 NSResizableWindowMask)
859 backing:NSBackingStoreBuffered
862 [win setMinSize:[win frameRectForContentRect:rect].size];
863 [[win contentView] addSubview: (pbox ? (NSView *) pbox : (NSView *) sv)];
865 [win makeKeyAndOrderFront:win];
867 [sv startAnimation]; // this is the dummy saver
874 # endif // !USE_IPHONE
877 - (void)applicationDidFinishLaunching:
879 (NSNotification *) notif
881 (UIApplication *) application
882 # endif // USE_IPHONE
884 [self listSaverBundleNames];
887 int window_count = ([saverNames count] <= 1 ? 1 : 2);
888 NSMutableArray *a = [[NSMutableArray arrayWithCapacity: window_count+1]
893 // Create either one window (for standalone, e.g. Phosphor.app)
894 // or two windows for SaverTester.app.
895 for (i = 0; i < window_count; i++) {
896 NSWindow *win = [self makeWindow];
897 // Get the last-saved window position out of preferences.
898 [win setFrameAutosaveName:
899 [NSString stringWithFormat:@"XScreenSaverWindow%d", i]];
900 [win setFrameUsingName:[win frameAutosaveName]];
906 ya_rand_init (0); // Now's a good time.
908 rootViewController = [[[RotateyViewController alloc] init] retain];
909 [window setRootViewController: rootViewController];
911 SaverListController *menu = [[SaverListController alloc]
912 initWithNames:saverNames
913 descriptions:[self makeDescTable]];
914 [rootViewController pushViewController:menu animated:YES];
915 [menu becomeFirstResponder];
917 [window makeKeyAndVisible];
918 [window setAutoresizesSubviews:YES];
919 [window setAutoresizingMask:
920 (UIViewAutoresizingFlexibleWidth |
921 UIViewAutoresizingFlexibleHeight)];
923 application.applicationSupportsShakeToEdit = YES;
925 # endif // USE_IPHONE
927 NSString *forced = 0;
928 /* In the XCode project, each .saver scheme sets this env var when
929 launching SaverTester.app so that it knows which one we are
930 currently debugging. If this is set, it overrides the default
931 selection in the popup menu. If unset, that menu persists to
932 whatever it was last time.
934 const char *f = getenv ("SELECTED_SAVER");
936 forced = [NSString stringWithCString:(char *)f
937 encoding:NSUTF8StringEncoding];
939 if (forced && ![saverNames containsObject:forced]) {
940 NSLog(@"forced saver \"%@\" does not exist", forced);
944 // If there's only one saver, run that.
945 if (!forced && [saverNames count] == 1)
946 forced = [saverNames objectAtIndex:0];
948 NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
951 NSString *prev = [prefs stringForKey:@"selectedSaverName"];
956 // If nothing was selected (e.g., this is the first launch)
957 // then scroll randomly instead of starting up at "A".
960 prev = [saverNames objectAtIndex: (random() % [saverNames count])];
963 [menu scrollTo: prev];
964 # endif // USE_IPHONE
967 [prefs setObject:forced forKey:@"selectedSaverName"];
970 /* Don't auto-launch the saver unless it was running last time.
971 XScreenSaverView manages this, on crash_timer.
973 if (! [prefs boolForKey:@"wasRunning"])
977 [self selectedSaverDidChange:nil];
983 /* When the window closes, exit (even if prefs still open.)
985 - (BOOL) applicationShouldTerminateAfterLastWindowClosed: (NSApplication *) n
992 - (void)applicationWillResignActive:(UIApplication *)app
994 [(XScreenSaverView *)view setScreenLocked:YES];
997 - (void)applicationDidBecomeActive:(UIApplication *)app
999 [(XScreenSaverView *)view setScreenLocked:NO];
1002 - (void)applicationDidEnterBackground:(UIApplication *)application
1004 [(XScreenSaverView *)view setScreenLocked:YES];
1007 #endif // USE_IPHONE