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: (id) sender
346 NSString *saver = [listController selected];
349 [self loadSaver:saver launch:NO];
350 if (! saverView) return;
352 [rootViewController pushViewController: [saverView configureView]
357 - (void) loadSaverMenu: (id) sender
359 NSString *saver = [listController selected];
360 NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
362 [prefs setObject:saver forKey:@"selectedSaverName"];
364 [prefs removeObjectForKey:@"selectedSaverName"];
366 [self saveScreenshot];
367 [self selectedSaverDidChange:nil];
374 - (void)loadSaver:(NSString *)name launch:(BOOL)launch
376 // NSLog (@"selecting saver \"%@\"", name);
380 if (saverName && [saverName isEqualToString: name]) {
382 for (NSWindow *win in windows) {
383 ScreenSaverView *sv = find_saverView ([win contentView]);
384 if (![sv isAnimating])
392 for (NSWindow *win in windows) {
393 NSView *cv = [win contentView];
394 NSString *old_title = [win title];
395 if (!old_title) old_title = @"XScreenSaver";
396 [win setTitle: name];
397 relabel_menus (menubar, old_title, name);
399 ScreenSaverView *old_view = find_saverView (cv);
400 NSView *sup = old_view ? [old_view superview] : cv;
403 if ([old_view isAnimating])
404 [old_view stopAnimation];
405 [old_view removeFromSuperview];
408 NSSize size = [cv frame].size;
409 ScreenSaverView *new_view = [self makeSaverView:name withSize: size];
410 NSAssert (new_view, @"unable to make a saver view");
412 [new_view setFrame: (old_view ? [old_view frame] : [cv frame])];
413 [sup addSubview: new_view];
414 [win makeFirstResponder:new_view];
415 [new_view setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
418 [new_view startAnimation];
421 NSUserDefaultsController *ctl =
422 [NSUserDefaultsController sharedUserDefaultsController];
427 if (saverName && [saverName isEqualToString: name]) {
428 if (launch && ![saverView isAnimating]) {
429 [window addSubview: saverView];
430 [saverView startAnimation];
438 if ([saverView isAnimating])
439 [saverView stopAnimation];
440 [saverView removeFromSuperview];
443 NSSize size = [window frame].size;
444 saverView = [self makeSaverView:name withSize: size];
447 [[[UIAlertView alloc] initWithTitle: name
448 message: @"Unable to load!"
450 cancelButtonTitle: @"Bummer"
451 otherButtonTitles: nil]
456 [saverView setFrame: [window frame]];
458 [[NSNotificationCenter defaultCenter]
459 addObserver:saverView
460 selector:@selector(didRotate:)
461 name:UIDeviceOrientationDidChangeNotification object:nil];
464 [window addSubview: saverView];
465 [saverView startAnimation];
467 # endif // USE_IPHONE
471 - (void)loadSaver:(NSString *)name
473 [self loadSaver:name launch:YES];
477 - (void)aboutPanel:(id)sender
480 NSDictionary *bd = [saverBundle infoDictionary];
481 NSMutableDictionary *d = [NSMutableDictionary dictionaryWithCapacity:20];
483 [d setValue:[bd objectForKey:@"CFBundleName"] forKey:@"ApplicationName"];
484 [d setValue:[bd objectForKey:@"CFBundleVersion"] forKey:@"Version"];
485 [d setValue:[bd objectForKey:@"CFBundleShortVersionString"]
486 forKey:@"ApplicationVersion"];
487 [d setValue:[bd objectForKey:@"NSHumanReadableCopyright"] forKey:@"Copy"];
488 [d setValue:[[NSAttributedString alloc]
489 initWithString: (NSString *)
490 [bd objectForKey:@"CFBundleGetInfoString"]]
493 [[NSApplication sharedApplication]
494 orderFrontStandardAboutPanelWithOptions:d];
498 NSDictionary *bd = [[NSBundle mainBundle] infoDictionary];
499 NSString *body = [bd objectForKey:@"CFBundleGetInfoString"];
501 body = [body stringByReplacingOccurrencesOfString:@", " withString:@",\n"];
502 body = [body stringByAppendingString:
504 "Double-tap to run.\n\n"
505 "Double-tap again to\n"
506 "return to this list."];
508 [[[UIAlertView alloc] initWithTitle: @""
511 cancelButtonTitle: @"OK"
512 otherButtonTitles: nil]
515 # endif // USE_IPHONE
520 - (void)selectedSaverDidChange:(NSDictionary *)change
522 NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
523 NSString *name = [prefs stringForKey:@"selectedSaverName"];
527 if (! [saverNames containsObject:name]) {
528 NSLog (@"saver \"%@\" does not exist", name);
532 [self loadSaver: name];
536 - (NSArray *) listSaverBundleNamesInDir:(NSString *)dir
539 NSString *ext = @"saver";
541 NSString *ext = @"xml";
544 NSArray *files = [[NSFileManager defaultManager]
545 contentsOfDirectoryAtPath:dir error:nil];
546 if (! files) return 0;
547 NSMutableArray *result = [NSMutableArray arrayWithCapacity: [files count]+1];
549 for (NSString *p in files) {
550 if ([[p pathExtension] caseInsensitiveCompare: ext])
554 NSString *name = [[p lastPathComponent] stringByDeletingPathExtension];
555 # else // !USE_IPHONE
557 // Get the saver name's capitalization right by reading the XML file.
559 p = [dir stringByAppendingPathComponent: p];
560 NSString *name = [NSString stringWithContentsOfFile:p
561 encoding:NSISOLatin1StringEncoding
563 NSRange r = [name rangeOfString:@"_label=\"" options:0];
564 name = [name substringFromIndex: r.location + r.length];
565 r = [name rangeOfString:@"\"" options:0];
566 name = [name substringToIndex: r.location];
568 NSAssert1 (name, @"no name in %@", p);
570 # endif // !USE_IPHONE
572 [result addObject: name];
580 - (NSArray *) listSaverBundleNames
582 NSMutableArray *dirs = [NSMutableArray arrayWithCapacity: 10];
585 // On MacOS, look in the "Contents/PlugIns/" directory in the bundle.
586 [dirs addObject: [[NSBundle mainBundle] builtInPlugInsPath]];
588 // Also look in the same directory as the executable.
589 [dirs addObject: [[[NSBundle mainBundle] bundlePath]
590 stringByDeletingLastPathComponent]];
592 // Finally, look in standard MacOS screensaver directories.
593 [dirs addObject: @"~/Library/Screen Savers"];
594 [dirs addObject: @"/Library/Screen Savers"];
595 [dirs addObject: @"/System/Library/Screen Savers"];
598 // On iOS, just look in the bundle's root directory.
599 [dirs addObject: [[NSBundle mainBundle] bundlePath]];
603 for (i = 0; i < [dirs count]; i++) {
604 NSString *dir = [dirs objectAtIndex:i];
605 NSArray *names = [self listSaverBundleNamesInDir:dir];
606 if (! names) continue;
607 saverDir = [dir retain];
608 saverNames = [names retain];
612 NSString *err = @"no .saver bundles found in: ";
613 for (i = 0; i < [dirs count]; i++) {
614 if (i) err = [err stringByAppendingString:@", "];
615 err = [err stringByAppendingString:[[dirs objectAtIndex:i]
616 stringByAbbreviatingWithTildeInPath]];
617 err = [err stringByAppendingString:@"/"];
620 return [NSArray array];
624 /* Create the popup menu of available saver names.
628 - (NSPopUpButton *) makeMenu
631 rect.origin.x = rect.origin.y = 0;
632 rect.size.width = 10;
633 rect.size.height = 10;
634 NSPopUpButton *popup = [[NSPopUpButton alloc] initWithFrame:rect
638 for (i = 0; i < [saverNames count]; i++) {
639 NSString *name = [saverNames objectAtIndex:i];
640 [popup addItemWithTitle:name];
641 [[popup itemWithTitle:name] setRepresentedObject:name];
643 NSRect r = [popup frame];
644 if (r.size.width > max_width) max_width = r.size.width;
647 // Bind the menu to preferences, and trigger a callback when an item
650 NSString *key = @"values.selectedSaverName";
651 NSUserDefaultsController *prefs =
652 [NSUserDefaultsController sharedUserDefaultsController];
653 [prefs addObserver:self
656 context:@selector(selectedSaverDidChange:)];
657 [popup bind:@"selectedObject"
661 [prefs setAppliesImmediately:YES];
663 NSRect r = [popup frame];
664 r.size.width = max_width;
671 /* Create a dictionary of one-line descriptions of every saver,
672 for display on the UITableView.
674 - (NSDictionary *)makeDescTable
676 NSMutableDictionary *dict =
677 [NSMutableDictionary dictionaryWithCapacity:[saverNames count]];
679 for (NSString *saver in saverNames) {
681 NSString *path = [saverDir stringByAppendingPathComponent:
682 [[saver lowercaseString]
683 stringByReplacingOccurrencesOfString:@" "
687 path = [path stringByAppendingPathExtension:@"xml"];
688 desc = [NSString stringWithContentsOfFile:path
689 encoding:NSISOLatin1StringEncoding
691 if (! desc) goto FAIL;
693 r = [desc rangeOfString:@"<_description>"
694 options:NSCaseInsensitiveSearch];
699 desc = [desc substringFromIndex: r.location + r.length];
700 r = [desc rangeOfString:@"</_description>"
701 options:NSCaseInsensitiveSearch];
703 desc = [desc substringToIndex: r.location];
705 // Leading and trailing whitespace.
706 desc = [desc stringByTrimmingCharactersInSet:
707 [NSCharacterSet whitespaceAndNewlineCharacterSet]];
709 // Let's see if we can find a year on the last line.
710 r = [desc rangeOfString:@"\n" options:NSBackwardsSearch];
712 for (NSString *word in
713 [[desc substringFromIndex:r.location + r.length]
714 componentsSeparatedByCharactersInSet:
715 [NSCharacterSet characterSetWithCharactersInString:
717 int n = [word doubleValue];
718 if (n > 1970 && n < 2100)
722 // Delete everything after the first blank line.
723 r = [desc rangeOfString:@"\n\n" options:0];
725 desc = [desc substringToIndex: r.location];
727 // Truncate really long ones.
729 if ([desc length] > max)
730 desc = [desc substringToIndex: max];
733 desc = [year stringByAppendingString:
734 [@": " stringByAppendingString: desc]];
738 desc = @"Oops, this module appears to be incomplete.";
739 // NSLog(@"broken saver: %@", path);
742 [dict setObject:desc forKey:saver];
749 - (UIViewController *) makeMenu
751 listController = [[[SaverListController alloc]
752 initWithNames:saverNames
753 descriptions:[self makeDescTable]]
755 UIBarButtonItem *run = [[[UIBarButtonItem alloc]
757 style: UIBarButtonItemStylePlain
759 action: @selector(loadSaverMenu:)]
761 UIBarButtonItem *about = [[[UIBarButtonItem alloc]
762 initWithTitle: @"About"
763 style: UIBarButtonItemStylePlain
765 action: @selector(aboutPanel:)]
767 UIBarButtonItem *pref = [[[UIBarButtonItem alloc]
768 initWithTitle:@"Settings"
769 style: UIBarButtonItemStylePlain
771 action: @selector(openPreferences:)]
773 NSArray *a = [NSArray arrayWithObjects: pref, about, nil];
776 [about setEnabled:YES];
777 [pref setEnabled:NO];
778 listController.navigationItem.leftBarButtonItem = run;
779 listController.navigationItem.rightBarButtonItems = a;
781 return listController;
788 /* This is called when the "selectedSaverName" pref changes, e.g.,
789 when a menu selection is made.
791 - (void)observeValueForKeyPath:(NSString *)keyPath
793 change:(NSDictionary *)change
794 context:(void *)context
796 SEL dispatchSelector = (SEL)context;
797 if (dispatchSelector != NULL) {
798 [self performSelector:dispatchSelector withObject:change];
800 [super observeValueForKeyPath:keyPath
810 /* Create the desktop window shell, possibly including a preferences button.
812 - (NSWindow *) makeWindow
815 static int count = 0;
816 Bool simple_p = ([saverNames count] == 1);
818 NSPopUpButton *menu = 0;
823 sv_rect.origin.x = sv_rect.origin.y = 0;
824 sv_rect.size.width = 320;
825 sv_rect.size.height = 240;
826 ScreenSaverView *sv = [[ScreenSaverView alloc] // dummy placeholder
827 initWithFrame:sv_rect
830 // make a "Preferences" button
835 rect.size.width = rect.size.height = 10;
836 pb = [[NSButton alloc] initWithFrame:rect];
837 [pb setTitle:@"Preferences"];
838 [pb setBezelStyle:NSRoundedBezelStyle];
841 rect.origin.x = ([sv frame].size.width -
842 [pb frame].size.width) / 2;
843 [pb setFrameOrigin:rect.origin];
848 [pb setAction:@selector(openPreferences:)];
850 // Make a saver selection menu
852 menu = [self makeMenu];
855 [menu setFrameOrigin:rect.origin];
857 // make a box to wrap the saverView
861 rect.origin.y = [pb frame].origin.y + [pb frame].size.height;
862 gbox = [[NSBox alloc] initWithFrame:rect];
863 rect.size.width = rect.size.height = 10;
864 [gbox setContentViewMargins:rect.size];
865 [gbox setTitlePosition:NSNoTitle];
866 [gbox addSubview:sv];
869 // make a box to wrap the other two boxes
871 rect.origin.x = rect.origin.y = 0;
872 rect.size.width = [gbox frame].size.width;
873 rect.size.height = [gbox frame].size.height + [gbox frame].origin.y;
874 pbox = [[NSBox alloc] initWithFrame:rect];
875 [pbox setTitlePosition:NSNoTitle];
876 [pbox setBorderType:NSNoBorder];
877 [pbox addSubview:gbox];
878 if (menu) [pbox addSubview:menu];
879 if (pb) [pbox addSubview:pb];
882 [pb setAutoresizingMask:NSViewMinXMargin|NSViewMaxXMargin];
883 [menu setAutoresizingMask:NSViewMinXMargin|NSViewMaxXMargin];
884 [gbox setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
885 [pbox setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
888 [sv setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
891 // and make a window to hold that.
893 NSScreen *screen = [NSScreen mainScreen];
894 rect = pbox ? [pbox frame] : [sv frame];
895 rect.origin.x = ([screen frame].size.width - rect.size.width) / 2;
896 rect.origin.y = ([screen frame].size.height - rect.size.height) / 2;
898 rect.origin.x += rect.size.width * (count ? 0.55 : -0.55);
900 NSWindow *win = [[NSWindow alloc]
901 initWithContentRect:rect
902 styleMask:(NSTitledWindowMask |
903 NSClosableWindowMask |
904 NSMiniaturizableWindowMask |
905 NSResizableWindowMask)
906 backing:NSBackingStoreBuffered
909 [win setMinSize:[win frameRectForContentRect:rect].size];
910 [[win contentView] addSubview: (pbox ? (NSView *) pbox : (NSView *) sv)];
912 [win makeKeyAndOrderFront:win];
914 [sv startAnimation]; // this is the dummy saver
921 # endif // !USE_IPHONE
924 - (void)applicationDidFinishLaunching:
926 (NSNotification *) notif
928 (UIApplication *) application
929 # endif // USE_IPHONE
931 [self listSaverBundleNames];
934 int window_count = ([saverNames count] <= 1 ? 1 : 2);
935 NSMutableArray *a = [[NSMutableArray arrayWithCapacity: window_count+1]
940 // Create either one window (for standalone, e.g. Phosphor.app)
941 // or two windows for SaverTester.app.
942 for (i = 0; i < window_count; i++) {
943 NSWindow *win = [self makeWindow];
944 // Get the last-saved window position out of preferences.
945 [win setFrameAutosaveName:
946 [NSString stringWithFormat:@"XScreenSaverWindow%d", i]];
947 [win setFrameUsingName:[win frameAutosaveName]];
953 ya_rand_init (0); // Now's a good time.
955 rootViewController = [[[RotateyViewController alloc] init] retain];
956 [window setRootViewController: rootViewController];
957 [rootViewController pushViewController:[self makeMenu]
960 [window makeKeyAndVisible];
961 [window setAutoresizesSubviews:YES];
962 [window setAutoresizingMask:
963 (UIViewAutoresizingFlexibleWidth |
964 UIViewAutoresizingFlexibleHeight)];
966 // Has to be after the list window is up, or we get black.
967 [self saveScreenshot];
969 # endif // USE_IPHONE
971 NSString *forced = 0;
972 /* In the XCode project, each .saver scheme sets this env var when
973 launching SaverTester.app so that it knows which one we are
974 currently debugging. If this is set, it overrides the default
975 selection in the popup menu. If unset, that menu persists to
976 whatever it was last time.
978 const char *f = getenv ("SELECTED_SAVER");
980 forced = [NSString stringWithCString:(char *)f
981 encoding:NSUTF8StringEncoding];
983 if (forced && ![saverNames containsObject:forced]) {
984 NSLog(@"forced saver \"%@\" does not exist", forced);
988 // If there's only one saver, run that.
989 if (!forced && [saverNames count] == 1)
990 forced = [saverNames objectAtIndex:0];
992 NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
995 NSString *prev = [prefs stringForKey:@"selectedSaverName"];
999 // If nothing was selected (e.g., this is the first launch)
1000 // then scroll randomly instead of starting up at "A".
1003 prev = [saverNames objectAtIndex: (random() % [saverNames count])];
1006 [listController scrollTo: prev];
1007 # endif // USE_IPHONE
1010 [prefs setObject:forced forKey:@"selectedSaverName"];
1013 /* Don't auto-launch the saver unless it was running last time.
1014 XScreenSaverView manages this, on crash_timer.
1016 if (! [prefs boolForKey:@"wasRunning"])
1020 [self selectedSaverDidChange:nil];
1026 /* When the window closes, exit (even if prefs still open.)
1028 - (BOOL) applicationShouldTerminateAfterLastWindowClosed: (NSApplication *) n
1033 # else // USE_IPHONE
1035 - (void)applicationWillResignActive:(UIApplication *)app
1037 [(XScreenSaverView *)view setScreenLocked:YES];
1040 - (void)applicationDidBecomeActive:(UIApplication *)app
1042 [(XScreenSaverView *)view setScreenLocked:NO];
1045 - (void)applicationDidEnterBackground:(UIApplication *)application
1047 [(XScreenSaverView *)view setScreenLocked:YES];
1050 #endif // USE_IPHONE