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])
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];
457 [self saveScreenshot];
458 [window addSubview: saverView];
459 [saverView becomeFirstResponder];
460 [saverView startAnimation];
462 # endif // USE_IPHONE
466 - (void)loadSaver:(NSString *)name
468 [self loadSaver:name launch:YES];
474 - (void)aboutPanel:(id)sender
476 NSDictionary *bd = [saverBundle infoDictionary];
477 NSMutableDictionary *d = [NSMutableDictionary dictionaryWithCapacity:20];
479 [d setValue:[bd objectForKey:@"CFBundleName"] forKey:@"ApplicationName"];
480 [d setValue:[bd objectForKey:@"CFBundleVersion"] forKey:@"Version"];
481 [d setValue:[bd objectForKey:@"CFBundleShortVersionString"]
482 forKey:@"ApplicationVersion"];
483 [d setValue:[bd objectForKey:@"NSHumanReadableCopyright"] forKey:@"Copy"];
484 [d setValue:[[NSAttributedString alloc]
485 initWithString: (NSString *)
486 [bd objectForKey:@"CFBundleGetInfoString"]]
489 [[NSApplication sharedApplication]
490 orderFrontStandardAboutPanelWithOptions:d];
493 # endif // USE_IPHONE
497 - (void)selectedSaverDidChange:(NSDictionary *)change
499 NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
500 NSString *name = [prefs stringForKey:@"selectedSaverName"];
504 if (! [saverNames containsObject:name]) {
505 NSLog (@"saver \"%@\" does not exist", name);
509 [self loadSaver: name];
513 - (NSArray *) listSaverBundleNamesInDir:(NSString *)dir
516 NSString *ext = @"saver";
518 NSString *ext = @"xml";
521 NSArray *files = [[NSFileManager defaultManager]
522 contentsOfDirectoryAtPath:dir error:nil];
523 if (! files) return 0;
524 NSMutableArray *result = [NSMutableArray arrayWithCapacity: [files count]+1];
526 for (NSString *p in files) {
527 if ([[p pathExtension] caseInsensitiveCompare: ext])
531 NSString *name = [[p lastPathComponent] stringByDeletingPathExtension];
532 # else // !USE_IPHONE
534 // Get the saver name's capitalization right by reading the XML file.
536 p = [dir stringByAppendingPathComponent: p];
537 NSString *name = [NSString stringWithContentsOfFile:p
538 encoding:NSISOLatin1StringEncoding
540 NSRange r = [name rangeOfString:@"_label=\"" options:0];
541 name = [name substringFromIndex: r.location + r.length];
542 r = [name rangeOfString:@"\"" options:0];
543 name = [name substringToIndex: r.location];
545 NSAssert1 (name, @"no name in %@", p);
547 # endif // !USE_IPHONE
549 [result addObject: name];
557 - (NSArray *) listSaverBundleNames
559 NSMutableArray *dirs = [NSMutableArray arrayWithCapacity: 10];
562 // On MacOS, look in the "Contents/PlugIns/" directory in the bundle.
563 [dirs addObject: [[NSBundle mainBundle] builtInPlugInsPath]];
565 // Also look in the same directory as the executable.
566 [dirs addObject: [[[NSBundle mainBundle] bundlePath]
567 stringByDeletingLastPathComponent]];
569 // Finally, look in standard MacOS screensaver directories.
570 [dirs addObject: @"~/Library/Screen Savers"];
571 [dirs addObject: @"/Library/Screen Savers"];
572 [dirs addObject: @"/System/Library/Screen Savers"];
575 // On iOS, just look in the bundle's root directory.
576 [dirs addObject: [[NSBundle mainBundle] bundlePath]];
580 for (i = 0; i < [dirs count]; i++) {
581 NSString *dir = [dirs objectAtIndex:i];
582 NSArray *names = [self listSaverBundleNamesInDir:dir];
583 if (! names) continue;
584 saverDir = [dir retain];
585 saverNames = [names retain];
589 NSString *err = @"no .saver bundles found in: ";
590 for (i = 0; i < [dirs count]; i++) {
591 if (i) err = [err stringByAppendingString:@", "];
592 err = [err stringByAppendingString:[[dirs objectAtIndex:i]
593 stringByAbbreviatingWithTildeInPath]];
594 err = [err stringByAppendingString:@"/"];
597 return [NSArray array];
601 /* Create the popup menu of available saver names.
605 - (NSPopUpButton *) makeMenu
608 rect.origin.x = rect.origin.y = 0;
609 rect.size.width = 10;
610 rect.size.height = 10;
611 NSPopUpButton *popup = [[NSPopUpButton alloc] initWithFrame:rect
615 for (i = 0; i < [saverNames count]; i++) {
616 NSString *name = [saverNames objectAtIndex:i];
617 [popup addItemWithTitle:name];
618 [[popup itemWithTitle:name] setRepresentedObject:name];
620 NSRect r = [popup frame];
621 if (r.size.width > max_width) max_width = r.size.width;
624 // Bind the menu to preferences, and trigger a callback when an item
627 NSString *key = @"values.selectedSaverName";
628 NSUserDefaultsController *prefs =
629 [NSUserDefaultsController sharedUserDefaultsController];
630 [prefs addObserver:self
633 context:@selector(selectedSaverDidChange:)];
634 [popup bind:@"selectedObject"
638 [prefs setAppliesImmediately:YES];
640 NSRect r = [popup frame];
641 r.size.width = max_width;
648 /* Create a dictionary of one-line descriptions of every saver,
649 for display on the UITableView.
651 - (NSDictionary *)makeDescTable
653 NSMutableDictionary *dict =
654 [NSMutableDictionary dictionaryWithCapacity:[saverNames count]];
656 for (NSString *saver in saverNames) {
658 NSString *path = [saverDir stringByAppendingPathComponent:
659 [[saver lowercaseString]
660 stringByReplacingOccurrencesOfString:@" "
664 path = [path stringByAppendingPathExtension:@"xml"];
665 desc = [NSString stringWithContentsOfFile:path
666 encoding:NSISOLatin1StringEncoding
668 if (! desc) goto FAIL;
670 r = [desc rangeOfString:@"<_description>"
671 options:NSCaseInsensitiveSearch];
676 desc = [desc substringFromIndex: r.location + r.length];
677 r = [desc rangeOfString:@"</_description>"
678 options:NSCaseInsensitiveSearch];
680 desc = [desc substringToIndex: r.location];
682 // Leading and trailing whitespace.
683 desc = [desc stringByTrimmingCharactersInSet:
684 [NSCharacterSet whitespaceAndNewlineCharacterSet]];
686 // Let's see if we can find a year on the last line.
687 r = [desc rangeOfString:@"\n" options:NSBackwardsSearch];
689 for (NSString *word in
690 [[desc substringFromIndex:r.location + r.length]
691 componentsSeparatedByCharactersInSet:
692 [NSCharacterSet characterSetWithCharactersInString:
694 int n = [word doubleValue];
695 if (n > 1970 && n < 2100)
699 // Delete everything after the first blank line.
700 r = [desc rangeOfString:@"\n\n" options:0];
702 desc = [desc substringToIndex: r.location];
704 // Truncate really long ones.
706 if ([desc length] > max)
707 desc = [desc substringToIndex: max];
710 desc = [year stringByAppendingString:
711 [@": " stringByAppendingString: desc]];
715 desc = @"Oops, this module appears to be incomplete.";
716 // NSLog(@"broken saver: %@", path);
719 [dict setObject:desc forKey:saver];
730 /* This is called when the "selectedSaverName" pref changes, e.g.,
731 when a menu selection is made.
733 - (void)observeValueForKeyPath:(NSString *)keyPath
735 change:(NSDictionary *)change
736 context:(void *)context
738 SEL dispatchSelector = (SEL)context;
739 if (dispatchSelector != NULL) {
740 [self performSelector:dispatchSelector withObject:change];
742 [super observeValueForKeyPath:keyPath
752 /* Create the desktop window shell, possibly including a preferences button.
754 - (NSWindow *) makeWindow
757 static int count = 0;
758 Bool simple_p = ([saverNames count] == 1);
760 NSPopUpButton *menu = 0;
765 sv_rect.origin.x = sv_rect.origin.y = 0;
766 sv_rect.size.width = 320;
767 sv_rect.size.height = 240;
768 ScreenSaverView *sv = [[ScreenSaverView alloc] // dummy placeholder
769 initWithFrame:sv_rect
772 // make a "Preferences" button
777 rect.size.width = rect.size.height = 10;
778 pb = [[NSButton alloc] initWithFrame:rect];
779 [pb setTitle:@"Preferences"];
780 [pb setBezelStyle:NSRoundedBezelStyle];
783 rect.origin.x = ([sv frame].size.width -
784 [pb frame].size.width) / 2;
785 [pb setFrameOrigin:rect.origin];
790 [pb setAction:@selector(openPreferences:)];
792 // Make a saver selection menu
794 menu = [self makeMenu];
797 [menu setFrameOrigin:rect.origin];
799 // make a box to wrap the saverView
803 rect.origin.y = [pb frame].origin.y + [pb frame].size.height;
804 gbox = [[NSBox alloc] initWithFrame:rect];
805 rect.size.width = rect.size.height = 10;
806 [gbox setContentViewMargins:rect.size];
807 [gbox setTitlePosition:NSNoTitle];
808 [gbox addSubview:sv];
811 // make a box to wrap the other two boxes
813 rect.origin.x = rect.origin.y = 0;
814 rect.size.width = [gbox frame].size.width;
815 rect.size.height = [gbox frame].size.height + [gbox frame].origin.y;
816 pbox = [[NSBox alloc] initWithFrame:rect];
817 [pbox setTitlePosition:NSNoTitle];
818 [pbox setBorderType:NSNoBorder];
819 [pbox addSubview:gbox];
820 if (menu) [pbox addSubview:menu];
821 if (pb) [pbox addSubview:pb];
824 [pb setAutoresizingMask:NSViewMinXMargin|NSViewMaxXMargin];
825 [menu setAutoresizingMask:NSViewMinXMargin|NSViewMaxXMargin];
826 [gbox setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
827 [pbox setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
830 [sv setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
833 // and make a window to hold that.
835 NSScreen *screen = [NSScreen mainScreen];
836 rect = pbox ? [pbox frame] : [sv frame];
837 rect.origin.x = ([screen frame].size.width - rect.size.width) / 2;
838 rect.origin.y = ([screen frame].size.height - rect.size.height) / 2;
840 rect.origin.x += rect.size.width * (count ? 0.55 : -0.55);
842 NSWindow *win = [[NSWindow alloc]
843 initWithContentRect:rect
844 styleMask:(NSTitledWindowMask |
845 NSClosableWindowMask |
846 NSMiniaturizableWindowMask |
847 NSResizableWindowMask)
848 backing:NSBackingStoreBuffered
851 [win setMinSize:[win frameRectForContentRect:rect].size];
852 [[win contentView] addSubview: (pbox ? (NSView *) pbox : (NSView *) sv)];
854 [win makeKeyAndOrderFront:win];
856 [sv startAnimation]; // this is the dummy saver
863 # endif // !USE_IPHONE
866 - (void)applicationDidFinishLaunching:
868 (NSNotification *) notif
870 (UIApplication *) application
871 # endif // USE_IPHONE
873 [self listSaverBundleNames];
876 int window_count = ([saverNames count] <= 1 ? 1 : 2);
877 NSMutableArray *a = [[NSMutableArray arrayWithCapacity: window_count+1]
882 // Create either one window (for standalone, e.g. Phosphor.app)
883 // or two windows for SaverTester.app.
884 for (i = 0; i < window_count; i++) {
885 NSWindow *win = [self makeWindow];
886 // Get the last-saved window position out of preferences.
887 [win setFrameAutosaveName:
888 [NSString stringWithFormat:@"XScreenSaverWindow%d", i]];
889 [win setFrameUsingName:[win frameAutosaveName]];
895 ya_rand_init (0); // Now's a good time.
897 rootViewController = [[[RotateyViewController alloc] init] retain];
898 [window setRootViewController: rootViewController];
900 SaverListController *menu = [[SaverListController alloc]
901 initWithNames:saverNames
902 descriptions:[self makeDescTable]];
903 [rootViewController pushViewController:menu animated:YES];
904 [menu becomeFirstResponder];
906 [window makeKeyAndVisible];
907 [window setAutoresizesSubviews:YES];
908 [window setAutoresizingMask:
909 (UIViewAutoresizingFlexibleWidth |
910 UIViewAutoresizingFlexibleHeight)];
912 application.applicationSupportsShakeToEdit = YES;
914 # endif // USE_IPHONE
916 NSString *forced = 0;
917 /* In the XCode project, each .saver scheme sets this env var when
918 launching SaverTester.app so that it knows which one we are
919 currently debugging. If this is set, it overrides the default
920 selection in the popup menu. If unset, that menu persists to
921 whatever it was last time.
923 const char *f = getenv ("SELECTED_SAVER");
925 forced = [NSString stringWithCString:(char *)f
926 encoding:NSUTF8StringEncoding];
928 if (forced && ![saverNames containsObject:forced]) {
929 NSLog(@"forced saver \"%@\" does not exist", forced);
933 // If there's only one saver, run that.
934 if (!forced && [saverNames count] == 1)
935 forced = [saverNames objectAtIndex:0];
937 NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
940 NSString *prev = [prefs stringForKey:@"selectedSaverName"];
945 // If nothing was selected (e.g., this is the first launch)
946 // then scroll randomly instead of starting up at "A".
949 prev = [saverNames objectAtIndex: (random() % [saverNames count])];
952 [menu scrollTo: prev];
953 # endif // USE_IPHONE
956 [prefs setObject:forced forKey:@"selectedSaverName"];
959 /* Don't auto-launch the saver unless it was running last time.
960 XScreenSaverView manages this, on crash_timer.
962 if (! [prefs boolForKey:@"wasRunning"])
966 [self selectedSaverDidChange:nil];
972 /* When the window closes, exit (even if prefs still open.)
974 - (BOOL) applicationShouldTerminateAfterLastWindowClosed: (NSApplication *) n
981 - (void)applicationWillResignActive:(UIApplication *)app
983 [(XScreenSaverView *)view setScreenLocked:YES];
986 - (void)applicationDidBecomeActive:(UIApplication *)app
988 [(XScreenSaverView *)view setScreenLocked:NO];
991 - (void)applicationDidEnterBackground:(UIApplication *)application
993 [(XScreenSaverView *)view setScreenLocked:YES];