1 /* xscreensaver, Copyright (c) 2006-2013 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 is a subclass of Apple's ScreenSaverView that knows how to run
13 xscreensaver programs without X11 via the dark magic of the "jwxyz"
14 library. In xscreensaver terminology, this is the replacement for
15 the "screenhack.c" module.
18 #import <QuartzCore/QuartzCore.h>
20 #import "XScreenSaverView.h"
21 #import "XScreenSaverConfigSheet.h"
22 #import "screenhackI.h"
23 #import "xlockmoreI.h"
24 #import "jwxyz-timers.h"
27 /* Garbage collection only exists if we are being compiled against the
28 10.6 SDK or newer, not if we are building against the 10.4 SDK.
30 #ifndef MAC_OS_X_VERSION_10_6
31 # define MAC_OS_X_VERSION_10_6 1060 /* undefined in 10.4 SDK, grr */
33 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 /* 10.6 SDK */
34 # import <objc/objc-auto.h>
35 # define DO_GC_HACKERY
38 extern struct xscreensaver_function_table *xscreensaver_function_table;
40 /* Global variables used by the screen savers
43 const char *progclass;
49 extern NSDictionary *make_function_table_dict(void); // ios-function-table.m
51 /* Stub definition of the superclass, for iPhone.
53 @implementation ScreenSaverView
55 NSTimeInterval anim_interval;
60 - (id)initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview {
61 self = [super initWithFrame:frame];
63 anim_interval = 1.0/30;
66 - (NSTimeInterval)animationTimeInterval { return anim_interval; }
67 - (void)setAnimationTimeInterval:(NSTimeInterval)i { anim_interval = i; }
68 - (BOOL)hasConfigureSheet { return NO; }
69 - (NSWindow *)configureSheet { return nil; }
70 - (NSView *)configureView { return nil; }
71 - (BOOL)isPreview { return NO; }
72 - (BOOL)isAnimating { return animating_p; }
73 - (void)animateOneFrame { }
75 - (void)startAnimation {
76 if (animating_p) return;
78 anim_timer = [NSTimer scheduledTimerWithTimeInterval: anim_interval
80 selector:@selector(animateOneFrame)
85 - (void)stopAnimation {
87 [anim_timer invalidate];
94 # endif // !USE_IPHONE
98 @interface XScreenSaverView (Private)
99 - (void) stopAndClose:(Bool)relaunch;
102 @implementation XScreenSaverView
104 // Given a lower-cased saver name, returns the function table for it.
105 // If no name, guess the name from the class's bundle name.
107 - (struct xscreensaver_function_table *) findFunctionTable:(NSString *)name
109 NSBundle *nsb = [NSBundle bundleForClass:[self class]];
110 NSAssert1 (nsb, @"no bundle for class %@", [self class]);
112 NSString *path = [nsb bundlePath];
113 CFURLRef url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
115 kCFURLPOSIXPathStyle,
117 CFBundleRef cfb = CFBundleCreate (kCFAllocatorDefault, url);
119 NSAssert1 (cfb, @"no CFBundle for \"%@\"", path);
120 // #### Analyze says "Potential leak of an object stored into cfb"
123 name = [[path lastPathComponent] stringByDeletingPathExtension];
125 name = [[name lowercaseString]
126 stringByReplacingOccurrencesOfString:@" "
130 // CFBundleGetDataPointerForName doesn't work in "Archive" builds.
131 // I'm guessing that symbol-stripping is mandatory. Fuck.
132 NSString *table_name = [name stringByAppendingString:
133 @"_xscreensaver_function_table"];
134 void *addr = CFBundleGetDataPointerForName (cfb, (CFStringRef) table_name);
138 NSLog (@"no symbol \"%@\" for \"%@\"", table_name, path);
141 // Remember: any time you add a new saver to the iOS app,
142 // manually run "make ios-function-table.m"!
143 if (! function_tables)
144 function_tables = [make_function_table_dict() retain];
145 NSValue *v = [function_tables objectForKey: name];
146 void *addr = v ? [v pointerValue] : 0;
147 # endif // USE_IPHONE
149 return (struct xscreensaver_function_table *) addr;
153 // Add the "Contents/Resources/" subdirectory of this screen saver's .bundle
154 // to $PATH for the benefit of savers that include helper shell scripts.
156 - (void) setShellPath
158 NSBundle *nsb = [NSBundle bundleForClass:[self class]];
159 NSAssert1 (nsb, @"no bundle for class %@", [self class]);
161 NSString *nsdir = [nsb resourcePath];
162 NSAssert1 (nsdir, @"no resourcePath for class %@", [self class]);
163 const char *dir = [nsdir cStringUsingEncoding:NSUTF8StringEncoding];
164 const char *opath = getenv ("PATH");
165 if (!opath) opath = "/bin"; // $PATH is unset when running under Shark!
166 char *npath = (char *) malloc (strlen (opath) + strlen (dir) + 30);
167 strcpy (npath, "PATH=");
170 strcat (npath, opath);
171 if (putenv (npath)) {
173 NSAssert1 (0, @"putenv \"%s\" failed", npath);
176 /* Don't free (npath) -- MacOS's putenv() does not copy it. */
180 // set an $XSCREENSAVER_CLASSPATH variable so that included shell scripts
181 // (e.g., "xscreensaver-text") know how to look up resources.
183 - (void) setResourcesEnv:(NSString *) name
185 NSBundle *nsb = [NSBundle bundleForClass:[self class]];
186 NSAssert1 (nsb, @"no bundle for class %@", [self class]);
188 const char *s = [name cStringUsingEncoding:NSUTF8StringEncoding];
189 char *env = (char *) malloc (strlen (s) + 40);
190 strcpy (env, "XSCREENSAVER_CLASSPATH=");
194 NSAssert1 (0, @"putenv \"%s\" failed", env);
196 /* Don't free (env) -- MacOS's putenv() does not copy it. */
201 add_default_options (const XrmOptionDescRec *opts,
202 const char * const *defs,
203 XrmOptionDescRec **opts_ret,
204 const char ***defs_ret)
206 /* These aren't "real" command-line options (there are no actual command-line
207 options in the Cocoa version); but this is the somewhat kludgey way that
208 the <xscreensaver-text /> and <xscreensaver-image /> tags in the
209 ../hacks/config/\*.xml files communicate with the preferences database.
211 static const XrmOptionDescRec default_options [] = {
212 { "-text-mode", ".textMode", XrmoptionSepArg, 0 },
213 { "-text-literal", ".textLiteral", XrmoptionSepArg, 0 },
214 { "-text-file", ".textFile", XrmoptionSepArg, 0 },
215 { "-text-url", ".textURL", XrmoptionSepArg, 0 },
216 { "-text-program", ".textProgram", XrmoptionSepArg, 0 },
217 { "-grab-desktop", ".grabDesktopImages", XrmoptionNoArg, "True" },
218 { "-no-grab-desktop", ".grabDesktopImages", XrmoptionNoArg, "False"},
219 { "-choose-random-images", ".chooseRandomImages",XrmoptionNoArg, "True" },
220 { "-no-choose-random-images",".chooseRandomImages",XrmoptionNoArg, "False"},
221 { "-image-directory", ".imageDirectory", XrmoptionSepArg, 0 },
222 { "-fps", ".doFPS", XrmoptionNoArg, "True" },
223 { "-no-fps", ".doFPS", XrmoptionNoArg, "False"},
224 { "-foreground", ".foreground", XrmoptionSepArg, 0 },
225 { "-fg", ".foreground", XrmoptionSepArg, 0 },
226 { "-background", ".background", XrmoptionSepArg, 0 },
227 { "-bg", ".background", XrmoptionSepArg, 0 },
230 static const char *default_defaults [] = {
232 ".doubleBuffer: True",
233 ".multiSample: False",
241 ".textURL: http://en.wikipedia.org/w/index.php?title=Special:NewPages&feed=rss",
243 ".grabDesktopImages: yes",
245 ".chooseRandomImages: no",
247 ".chooseRandomImages: yes",
249 ".imageDirectory: ~/Pictures",
255 for (i = 0; default_options[i].option; i++)
257 for (i = 0; opts[i].option; i++)
260 XrmOptionDescRec *opts2 = (XrmOptionDescRec *)
261 calloc (count + 1, sizeof (*opts2));
265 while (default_options[j].option) {
266 opts2[i] = default_options[j];
270 while (opts[j].option) {
281 for (i = 0; default_defaults[i]; i++)
283 for (i = 0; defs[i]; i++)
286 const char **defs2 = (const char **) calloc (count + 1, sizeof (*defs2));
290 while (default_defaults[j]) {
291 defs2[i] = default_defaults[j];
305 /* Returns the current time in seconds as a double.
311 # ifdef GETTIMEOFDAY_TWO_ARGS
313 gettimeofday(&now, &tzp);
318 return (now.tv_sec + ((double) now.tv_usec * 0.000001));
323 - (id) initWithFrame:(NSRect)frame
324 saverName:(NSString *)saverName
325 isPreview:(BOOL)isPreview
328 initial_bounds = frame.size;
329 rot_current_size = frame.size; // needs to be early, because
330 rot_from = rot_current_size; // [self setFrame] is called by
331 rot_to = rot_current_size; // [super initWithFrame].
335 if (! (self = [super initWithFrame:frame isPreview:isPreview]))
338 xsft = [self findFunctionTable: saverName];
347 [self setMultipleTouchEnabled:YES];
348 orientation = UIDeviceOrientationUnknown;
349 [self didRotate:nil];
350 # endif // USE_IPHONE
354 xsft->setup_cb (xsft, xsft->setup_arg);
356 /* The plist files for these preferences show up in
357 $HOME/Library/Preferences/ByHost/ in a file named like
358 "org.jwz.xscreensaver.<SAVERNAME>.<NUMBERS>.plist"
360 NSString *name = [NSString stringWithCString:xsft->progclass
361 encoding:NSISOLatin1StringEncoding];
362 name = [@"org.jwz.xscreensaver." stringByAppendingString:name];
363 [self setResourcesEnv:name];
366 XrmOptionDescRec *opts = 0;
367 const char **defs = 0;
368 add_default_options (xsft->options, xsft->defaults, &opts, &defs);
369 prefsReader = [[PrefsReader alloc]
370 initWithName:name xrmKeys:opts defaults:defs];
372 // free (opts); // bah, we need these! #### leak!
373 xsft->options = opts;
375 progname = progclass = xsft->progclass;
379 # ifdef USE_BACKBUFFER
380 [self createBackbuffer];
385 // So we can tell when we're docked.
386 [UIDevice currentDevice].batteryMonitoringEnabled = YES;
387 # endif // USE_IPHONE
395 [self setLayer: [CALayer layer]];
396 self.layer.delegate = self;
397 self.layer.opaque = YES;
398 [self setWantsLayer: YES];
403 - (id) initWithFrame:(NSRect)frame isPreview:(BOOL)p
405 return [self initWithFrame:frame saverName:0 isPreview:p];
411 NSAssert(![self isAnimating], @"still animating");
412 NSAssert(!xdata, @"xdata not yet freed");
414 jwxyz_free_display (xdpy);
416 # ifdef USE_BACKBUFFER
418 CGContextRelease (backbuffer);
421 [prefsReader release];
429 - (PrefsReader *) prefsReader
436 - (void) lockFocus { }
437 - (void) unlockFocus { }
443 /* A few seconds after the saver launches, we store the "wasRunning"
444 preference. This is so that if the saver is crashing at startup,
445 we don't launch it again next time, getting stuck in a crash loop.
447 - (void) allSystemsGo: (NSTimer *) timer
449 NSAssert (timer == crash_timer, @"crash timer screwed up");
452 NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
453 [prefs setBool:YES forKey:@"wasRunning"];
459 - (void) startAnimation
461 NSAssert(![self isAnimating], @"already animating");
462 NSAssert(!initted_p && !xdata, @"already initialized");
463 [super startAnimation];
464 /* We can't draw on the window from this method, so we actually do the
465 initialization of the screen saver (xsft->init_cb) in the first call
466 to animateOneFrame() instead.
471 [crash_timer invalidate];
473 NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
474 [prefs removeObjectForKey:@"wasRunning"];
477 crash_timer = [NSTimer scheduledTimerWithTimeInterval: 5
479 selector:@selector(allSystemsGo:)
483 # endif // USE_IPHONE
485 // Never automatically turn the screen off if we are docked,
486 // and an animation is running.
489 [UIApplication sharedApplication].idleTimerDisabled =
490 ([UIDevice currentDevice].batteryState != UIDeviceBatteryStateUnplugged);
495 - (void)stopAnimation
497 NSAssert([self isAnimating], @"not animating");
501 [self lockFocus]; // in case something tries to draw from here
502 [self prepareContext];
504 /* I considered just not even calling the free callback at all...
505 But webcollage-cocoa needs it, to kill the inferior webcollage
506 processes (since the screen saver framework never generates a
507 SIGPIPE for them...) Instead, I turned off the free call in
508 xlockmore.c, which is where all of the bogus calls are anyway.
510 xsft->free_cb (xdpy, xwindow, xdata);
513 // setup_p = NO; // #### wait, do we need this?
520 [crash_timer invalidate];
522 NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
523 [prefs removeObjectForKey:@"wasRunning"];
525 # endif // USE_IPHONE
527 [super stopAnimation];
529 // When an animation is no longer running (e.g., looking at the list)
530 // then it's ok to power off the screen when docked.
533 [UIApplication sharedApplication].idleTimerDisabled = NO;
538 /* Hook for the XScreenSaverGLView subclass
540 - (void) prepareContext
544 /* Hook for the XScreenSaverGLView subclass
546 - (void) resizeContext
552 screenhack_do_fps (Display *dpy, Window w, fps_state *fpst, void *closure)
554 fps_compute (fpst, 0, -1);
561 /* On iPhones with Retina displays, we can draw the savers in "real"
562 pixels, and that works great. The 320x480 "point" screen is really
563 a 640x960 *pixel* screen. However, Retina iPads have 768x1024
564 point screens which are 1536x2048 pixels, and apparently that's
565 enough pixels that copying those bits to the screen is slow. Like,
566 drops us from 15fps to 7fps. So, on Retina iPads, we don't draw in
567 real pixels. This will probably make the savers look better
568 anyway, since that's a higher resolution than most desktop monitors
569 have even today. (This is only true for X11 programs, not GL
570 programs. Those are fine at full rez.)
572 This method is overridden in XScreenSaverGLView, since this kludge
573 isn't necessary for GL programs, being resolution independent by
576 - (CGFloat) hackedContentScaleFactor
578 GLfloat s = [self contentScaleFactor];
579 if (initial_bounds.width >= 1024 ||
580 initial_bounds.height >= 1024)
586 static GLfloat _global_rot_current_angle_kludge;
588 double current_device_rotation (void)
590 return -_global_rot_current_angle_kludge;
594 - (void) hackRotation
596 if (rotation_ratio >= 0) { // in the midst of a rotation animation
598 # define CLAMP180(N) while (N < 0) N += 360; while (N > 180) N -= 360
599 GLfloat f = angle_from;
600 GLfloat t = angle_to;
603 GLfloat dist = -(t-f);
606 // Intermediate angle.
607 rot_current_angle = f - rotation_ratio * dist;
609 // Intermediate frame size.
610 rot_current_size.width = rot_from.width +
611 rotation_ratio * (rot_to.width - rot_from.width);
612 rot_current_size.height = rot_from.height +
613 rotation_ratio * (rot_to.height - rot_from.height);
615 // Tick animation. Complete rotation in 1/6th sec.
616 double now = double_time();
617 double duration = 1/6.0;
618 rotation_ratio = 1 - ((rot_start_time + duration - now) / duration);
620 if (rotation_ratio > 1) { // Done animating.
621 orientation = new_orientation;
622 rot_current_angle = angle_to;
623 rot_current_size = rot_to;
626 // Check orientation again in case we rotated again while rotating:
627 // this is a no-op if nothing has changed.
628 [self didRotate:nil];
630 } else { // Not animating a rotation.
631 rot_current_angle = angle_to;
632 rot_current_size = rot_to;
635 CLAMP180(rot_current_angle);
636 _global_rot_current_angle_kludge = rot_current_angle;
640 double s = [self hackedContentScaleFactor];
641 if (!ignore_rotation_p &&
642 /* rotation_ratio && */
643 ((int) backbuffer_size.width != (int) (s * rot_current_size.width) ||
644 (int) backbuffer_size.height != (int) (s * rot_current_size.height)))
649 - (void)alertView:(UIAlertView *)av clickedButtonAtIndex:(NSInteger)i
651 if (i == 0) exit (-1); // Cancel
652 [self stopAndClose:NO]; // Keep going
655 - (void) handleException: (NSException *)e
657 NSLog (@"Caught exception: %@", e);
658 [[[UIAlertView alloc] initWithTitle:
659 [NSString stringWithFormat: @"%s crashed!",
662 [NSString stringWithFormat:
663 @"The error message was:"
665 "If it keeps crashing, try "
666 "resetting its options.",
669 cancelButtonTitle: @"Exit"
670 otherButtonTitles: @"Keep going", nil]
672 [self stopAnimation];
678 #ifdef USE_BACKBUFFER
680 /* Create a bitmap context into which we render everything.
681 If the desired size has changed, re-created it.
683 - (void) createBackbuffer
686 double s = [self hackedContentScaleFactor];
687 CGSize rotsize = ignore_rotation_p ? initial_bounds : rot_current_size;
688 int new_w = s * rotsize.width;
689 int new_h = s * rotsize.height;
691 int new_w = [self bounds].size.width;
692 int new_h = [self bounds].size.height;
696 backbuffer_size.width == new_w &&
697 backbuffer_size.height == new_h)
700 CGSize osize = backbuffer_size;
701 CGContextRef ob = backbuffer;
703 backbuffer_size.width = new_w;
704 backbuffer_size.height = new_h;
706 CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB();
707 backbuffer = CGBitmapContextCreate (NULL,
708 backbuffer_size.width,
709 backbuffer_size.height,
711 backbuffer_size.width * 4,
713 // kCGImageAlphaPremultipliedLast
714 (kCGImageAlphaNoneSkipFirst |
715 kCGBitmapByteOrder32Host)
717 CGColorSpaceRelease (cs);
718 NSAssert (backbuffer, @"unable to allocate back buffer");
722 r.origin.x = r.origin.y = 0;
723 r.size = backbuffer_size;
724 CGContextSetGrayFillColor (backbuffer, 0, 1);
725 CGContextFillRect (backbuffer, r);
728 // Restore old bits, as much as possible, to the X11 upper left origin.
731 rect.origin.y = (backbuffer_size.height - osize.height);
733 CGImageRef img = CGBitmapContextCreateImage (ob);
734 CGContextDrawImage (backbuffer, rect, img);
735 CGImageRelease (img);
736 CGContextRelease (ob);
740 #endif // USE_BACKBUFFER
743 /* Inform X11 that the size of our window has changed.
747 if (!xwindow) return; // early
749 # ifdef USE_BACKBUFFER
750 [self createBackbuffer];
751 jwxyz_window_resized (xdpy, xwindow,
753 backbuffer_size.width, backbuffer_size.height,
755 # else // !USE_BACKBUFFER
756 NSRect r = [self frame]; // ignoring rotation is closer
757 r.size = [self bounds].size; // to what XGetGeometry expects.
758 jwxyz_window_resized (xdpy, xwindow,
759 r.origin.x, r.origin.y,
760 r.size.width, r.size.height,
762 # endif // !USE_BACKBUFFER
764 // Next time render_x11 is called, run the saver's reshape_cb.
774 if (orientation == UIDeviceOrientationUnknown)
775 [self didRotate:nil];
782 # ifdef USE_BACKBUFFER
783 NSAssert (backbuffer, @"no back buffer");
784 xdpy = jwxyz_make_display (self, backbuffer);
786 xdpy = jwxyz_make_display (self, 0);
788 xwindow = XRootWindow (xdpy, 0);
791 /* Some X11 hacks (fluidballs) want to ignore all rotation events. */
793 get_boolean_resource (xdpy, "ignoreRotation", "IgnoreRotation");
794 # endif // USE_IPHONE
802 xsft->setup_cb (xsft, xsft->setup_arg);
806 NSAssert(!xdata, @"xdata already initialized");
812 XSetWindowBackground (xdpy, xwindow,
813 get_pixel_resource (xdpy, 0,
814 "background", "Background"));
815 XClearWindow (xdpy, xwindow);
818 [[self window] setAcceptsMouseMovedEvents:YES];
821 /* In MacOS 10.5, this enables "QuartzGL", meaning that the Quartz
822 drawing primitives will run on the GPU instead of the CPU.
823 It seems like it might make things worse rather than better,
824 though... Plus it makes us binary-incompatible with 10.4.
826 # if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
827 [[self window] setPreferredBackingLocation:
828 NSWindowBackingLocationVideoMemory];
832 /* Kludge: even though the init_cb functions are declared to take 2 args,
833 actually call them with 3, for the benefit of xlockmore_init() and
836 void *(*init_cb) (Display *, Window, void *) =
837 (void *(*) (Display *, Window, void *)) xsft->init_cb;
839 xdata = init_cb (xdpy, xwindow, xsft->setup_arg);
841 if (get_boolean_resource (xdpy, "doFPS", "DoFPS")) {
842 fpst = fps_init (xdpy, xwindow);
843 if (! xsft->fps_cb) xsft->fps_cb = screenhack_do_fps;
848 /* I don't understand why we have to do this *every frame*, but we do,
849 or else the cursor comes back on.
852 if (![self isPreview])
853 [NSCursor setHiddenUntilMouseMoves:YES];
859 /* This is just a guess, but the -fps code wants to know how long
860 we were sleeping between frames.
862 long usecs = 1000000 * [self animationTimeInterval];
863 usecs -= 200; // caller apparently sleeps for slightly less sometimes...
864 if (usecs < 0) usecs = 0;
865 fps_slept (fpst, usecs);
869 /* It turns out that [ScreenSaverView setAnimationTimeInterval] does nothing.
870 This is bad, because some of the screen hacks want to delay for long
871 periods (like 5 seconds or a minute!) between frames, and running them
872 all at 60 FPS is no good.
874 So, we don't use setAnimationTimeInterval, and just let the framework call
875 us whenever. But, we only invoke the screen hack's "draw frame" method
876 when enough time has expired.
878 This means two extra calls to gettimeofday() per frame. For fast-cycling
879 screen savers, that might actually slow them down. Oh well.
881 #### Also, we do not run the draw callback faster than the system's
882 animationTimeInterval, so if any savers are pickier about timing
883 than that, this may slow them down too much. If that's a problem,
884 then we could call draw_cb in a loop here (with usleep) until the
885 next call would put us past animationTimeInterval... But a better
886 approach would probably be to just change the saver to not do that.
889 gettimeofday (&tv, 0);
890 double now = tv.tv_sec + (tv.tv_usec / 1000000.0);
891 if (now < next_frame_time) return;
893 [self prepareContext];
896 // We do this here instead of in setFrame so that all the
897 // Xlib drawing takes place under the animation timer.
898 [self resizeContext];
900 # ifndef USE_BACKBUFFER
902 # else // USE_BACKBUFFER
905 r.size.width = backbuffer_size.width;
906 r.size.height = backbuffer_size.height;
907 # endif // USE_BACKBUFFER
909 xsft->reshape_cb (xdpy, xwindow, xdata, r.size.width, r.size.height);
913 // Run any XtAppAddInput callbacks now.
914 // (Note that XtAppAddTimeOut callbacks have already been run by
915 // the Cocoa event loop.)
917 jwxyz_sources_run (display_sources_data (xdpy));
923 NSDisableScreenUpdates();
925 unsigned long delay = xsft->draw_cb (xdpy, xwindow, xdata);
926 if (fpst) xsft->fps_cb (xdpy, xwindow, fpst, xdata);
928 NSEnableScreenUpdates();
931 gettimeofday (&tv, 0);
932 now = tv.tv_sec + (tv.tv_usec / 1000000.0);
933 next_frame_time = now + (delay / 1000000.0);
935 # ifdef USE_IPHONE // Allow savers on the iPhone to run full-tilt.
936 if (delay < [self animationTimeInterval])
937 [self setAnimationTimeInterval:(delay / 1000000.0)];
940 # ifdef DO_GC_HACKERY
941 /* Current theory is that the 10.6 garbage collector sucks in the
944 It only does a collection when a threshold of outstanding
945 collectable allocations has been surpassed. However, CoreGraphics
946 creates lots of small collectable allocations that contain pointers
947 to very large non-collectable allocations: a small CG object that's
948 collectable referencing large malloc'd allocations (non-collectable)
949 containing bitmap data. So the large allocation doesn't get freed
950 until GC collects the small allocation, which triggers its finalizer
951 to run which frees the large allocation. So GC is deciding that it
952 doesn't really need to run, even though the process has gotten
953 enormous. GC eventually runs once pageouts have happened, but by
954 then it's too late, and the machine's resident set has been
957 So, we force an exhaustive garbage collection in this process
958 approximately every 5 seconds whether the system thinks it needs
965 objc_collect (OBJC_EXHAUSTIVE_COLLECTION);
968 # endif // DO_GC_HACKERY
972 @catch (NSException *e) {
973 [self handleException: e];
975 # endif // USE_IPHONE
979 /* drawRect always does nothing, and animateOneFrame renders bits to the
980 screen. This is (now) true of both X11 and GL on both MacOS and iOS.
983 - (void)drawRect:(NSRect)rect
985 if (xwindow) // clear to the X window's bg color, not necessarily black.
986 XClearWindow (xdpy, xwindow);
988 [super drawRect:rect]; // early: black.
992 #ifndef USE_BACKBUFFER
994 - (void) animateOneFrame
999 #else // USE_BACKBUFFER
1001 - (void) animateOneFrame
1003 // Render X11 into the backing store bitmap...
1005 NSAssert (backbuffer, @"no back buffer");
1008 UIGraphicsPushContext (backbuffer);
1014 UIGraphicsPopContext();
1018 // Then compute the transformations for rotation.
1019 double hs = [self hackedContentScaleFactor];
1020 double s = [self contentScaleFactor];
1022 // The rotation origin for layer.affineTransform is in the center already.
1023 CGAffineTransform t = ignore_rotation_p ?
1024 CGAffineTransformIdentity :
1025 CGAffineTransformMakeRotation (rot_current_angle / (180.0 / M_PI));
1028 self.layer.affineTransform = CGAffineTransformScale(t, f, f);
1031 bounds.origin.x = 0;
1032 bounds.origin.y = 0;
1033 bounds.size.width = backbuffer_size.width / s;
1034 bounds.size.height = backbuffer_size.height / s;
1035 self.layer.bounds = bounds;
1036 # endif // USE_IPHONE
1038 [self.layer setNeedsDisplay];
1041 - (void) drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
1043 // This "isn't safe" if NULL is passed to CGBitmapCreateContext before iOS 4.
1044 char *dest_data = (char *)CGBitmapContextGetData (ctx);
1046 // The CGContext here is normally upside-down on iOS.
1048 CGBitmapContextGetBitmapInfo (ctx) ==
1049 (kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host)
1051 && CGContextGetCTM (ctx).d < 0
1055 size_t dest_height = CGBitmapContextGetHeight (ctx);
1056 size_t dest_bpr = CGBitmapContextGetBytesPerRow (ctx);
1057 size_t src_height = CGBitmapContextGetHeight (backbuffer);
1058 size_t src_bpr = CGBitmapContextGetBytesPerRow (backbuffer);
1059 char *src_data = (char *)CGBitmapContextGetData (backbuffer);
1061 size_t height = src_height < dest_height ? src_height : dest_height;
1063 if (src_bpr == dest_bpr) {
1064 // iPad 1: 4.0 ms, iPad 2: 6.7 ms
1065 memcpy (dest_data, src_data, src_bpr * height);
1067 // iPad 1: 4.6 ms, iPad 2: 7.2 ms
1068 size_t bpr = src_bpr < dest_bpr ? src_bpr : dest_bpr;
1070 memcpy (dest_data, src_data, bpr);
1072 src_data += src_bpr;
1073 dest_data += dest_bpr;
1078 // iPad 1: 9.6 ms, iPad 2: 12.1 ms
1081 CGContextScaleCTM (ctx, 1, -1);
1082 CGFloat s = [self contentScaleFactor];
1083 CGFloat hs = [self hackedContentScaleFactor];
1084 CGContextTranslateCTM (ctx, 0, -backbuffer_size.height * hs / s);
1087 CGImageRef img = CGBitmapContextCreateImage (backbuffer);
1088 CGContextDrawImage (ctx, self.layer.bounds, img);
1089 CGImageRelease (img);
1093 #endif // !USE_BACKBUFFER
1097 - (void) setFrame:(NSRect) newRect
1099 [super setFrame:newRect];
1101 if (xwindow) // inform Xlib that the window has changed now.
1106 # ifndef USE_IPHONE // Doesn't exist on iOS
1107 - (void) setFrameSize:(NSSize) newSize
1109 [super setFrameSize:newSize];
1113 # endif // !USE_IPHONE
1116 +(BOOL) performGammaFade
1121 - (BOOL) hasConfigureSheet
1126 + (NSString *) decompressXML: (NSData *)data
1128 if (! data) return 0;
1129 BOOL compressed_p = !!strncmp ((const char *) data.bytes, "<?xml", 5);
1131 // If it's not already XML, decompress it.
1132 NSAssert (compressed_p, @"xml isn't compressed");
1134 NSMutableData *data2 = 0;
1137 memset (&zs, 0, sizeof(zs));
1138 ret = inflateInit2 (&zs, 16 + MAX_WBITS);
1140 UInt32 usize = * (UInt32 *) (data.bytes + data.length - 4);
1141 data2 = [NSMutableData dataWithLength: usize];
1142 zs.next_in = (Bytef *) data.bytes;
1143 zs.avail_in = data.length;
1144 zs.next_out = (Bytef *) data2.bytes;
1145 zs.avail_out = data2.length;
1146 ret = inflate (&zs, Z_FINISH);
1149 if (ret == Z_OK || ret == Z_STREAM_END)
1152 NSAssert2 (0, @"gunzip error: %d: %s",
1153 ret, (zs.msg ? zs.msg : "<null>"));
1156 return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
1161 - (NSWindow *) configureSheet
1163 - (UIViewController *) configureView
1166 NSBundle *bundle = [NSBundle bundleForClass:[self class]];
1167 NSString *file = [NSString stringWithCString:xsft->progclass
1168 encoding:NSISOLatin1StringEncoding];
1169 file = [file lowercaseString];
1170 NSString *path = [bundle pathForResource:file ofType:@"xml"];
1172 NSLog (@"%@.xml does not exist in the application bundle: %@/",
1173 file, [bundle resourcePath]);
1178 UIViewController *sheet;
1179 # else // !USE_IPHONE
1181 # endif // !USE_IPHONE
1183 NSData *xmld = [NSData dataWithContentsOfFile:path];
1184 NSString *xml = [[self class] decompressXML: xmld];
1185 sheet = [[XScreenSaverConfigSheet alloc]
1186 initWithXML:[xml dataUsingEncoding:NSUTF8StringEncoding]
1187 options:xsft->options
1188 controller:[prefsReader userDefaultsController]
1189 defaults:[prefsReader defaultOptions]];
1191 // #### am I expected to retain this, or not? wtf.
1192 // I thought not, but if I don't do this, we (sometimes) crash.
1193 // #### Analyze says "potential leak of an object stored into sheet"
1200 - (NSUserDefaultsController *) userDefaultsController
1202 return [prefsReader userDefaultsController];
1206 /* Announce our willingness to accept keyboard input.
1208 - (BOOL)acceptsFirstResponder
1216 /* Convert an NSEvent into an XEvent, and pass it along.
1217 Returns YES if it was handled.
1219 - (BOOL) doEvent: (NSEvent *) e
1222 if (![self isPreview] || // no event handling if actually screen-saving!
1223 ![self isAnimating] ||
1228 memset (&xe, 0, sizeof(xe));
1232 int flags = [e modifierFlags];
1233 if (flags & NSAlphaShiftKeyMask) state |= LockMask;
1234 if (flags & NSShiftKeyMask) state |= ShiftMask;
1235 if (flags & NSControlKeyMask) state |= ControlMask;
1236 if (flags & NSAlternateKeyMask) state |= Mod1Mask;
1237 if (flags & NSCommandKeyMask) state |= Mod2Mask;
1239 NSPoint p = [[[e window] contentView] convertPoint:[e locationInWindow]
1242 double s = [self hackedContentScaleFactor];
1247 int y = s * ([self bounds].size.height - p.y);
1249 xe.xany.type = type;
1255 xe.xbutton.state = state;
1256 if ([e type] == NSScrollWheel)
1257 xe.xbutton.button = ([e deltaY] > 0 ? Button4 :
1258 [e deltaY] < 0 ? Button5 :
1259 [e deltaX] > 0 ? Button6 :
1260 [e deltaX] < 0 ? Button7 :
1263 xe.xbutton.button = [e buttonNumber] + 1;
1268 xe.xmotion.state = state;
1273 NSString *ns = (([e type] == NSFlagsChanged) ? 0 :
1274 [e charactersIgnoringModifiers]);
1277 if (!ns || [ns length] == 0) // dead key
1279 // Cocoa hides the difference between left and right keys.
1280 // Also we only get KeyPress events for these, no KeyRelease
1281 // (unless we hack the mod state manually. Bleh.)
1283 if (flags & NSAlphaShiftKeyMask) k = XK_Caps_Lock;
1284 else if (flags & NSShiftKeyMask) k = XK_Shift_L;
1285 else if (flags & NSControlKeyMask) k = XK_Control_L;
1286 else if (flags & NSAlternateKeyMask) k = XK_Alt_L;
1287 else if (flags & NSCommandKeyMask) k = XK_Meta_L;
1289 else if ([ns length] == 1) // real key
1291 switch ([ns characterAtIndex:0]) {
1292 case NSLeftArrowFunctionKey: k = XK_Left; break;
1293 case NSRightArrowFunctionKey: k = XK_Right; break;
1294 case NSUpArrowFunctionKey: k = XK_Up; break;
1295 case NSDownArrowFunctionKey: k = XK_Down; break;
1296 case NSPageUpFunctionKey: k = XK_Page_Up; break;
1297 case NSPageDownFunctionKey: k = XK_Page_Down; break;
1298 case NSHomeFunctionKey: k = XK_Home; break;
1299 case NSPrevFunctionKey: k = XK_Prior; break;
1300 case NSNextFunctionKey: k = XK_Next; break;
1301 case NSBeginFunctionKey: k = XK_Begin; break;
1302 case NSEndFunctionKey: k = XK_End; break;
1306 [ns cStringUsingEncoding:NSISOLatin1StringEncoding];
1307 k = (s && *s ? *s : 0);
1313 if (! k) return YES; // E.g., "KeyRelease XK_Shift_L"
1315 xe.xkey.keycode = k;
1316 xe.xkey.state = state;
1320 NSAssert1 (0, @"unknown X11 event type: %d", type);
1325 [self prepareContext];
1326 BOOL result = xsft->event_cb (xdpy, xwindow, xdata, &xe);
1332 - (void) mouseDown: (NSEvent *) e
1334 if (! [self doEvent:e type:ButtonPress])
1335 [super mouseDown:e];
1338 - (void) mouseUp: (NSEvent *) e
1340 if (! [self doEvent:e type:ButtonRelease])
1344 - (void) otherMouseDown: (NSEvent *) e
1346 if (! [self doEvent:e type:ButtonPress])
1347 [super otherMouseDown:e];
1350 - (void) otherMouseUp: (NSEvent *) e
1352 if (! [self doEvent:e type:ButtonRelease])
1353 [super otherMouseUp:e];
1356 - (void) mouseMoved: (NSEvent *) e
1358 if (! [self doEvent:e type:MotionNotify])
1359 [super mouseMoved:e];
1362 - (void) mouseDragged: (NSEvent *) e
1364 if (! [self doEvent:e type:MotionNotify])
1365 [super mouseDragged:e];
1368 - (void) otherMouseDragged: (NSEvent *) e
1370 if (! [self doEvent:e type:MotionNotify])
1371 [super otherMouseDragged:e];
1374 - (void) scrollWheel: (NSEvent *) e
1376 if (! [self doEvent:e type:ButtonPress])
1377 [super scrollWheel:e];
1380 - (void) keyDown: (NSEvent *) e
1382 if (! [self doEvent:e type:KeyPress])
1386 - (void) keyUp: (NSEvent *) e
1388 if (! [self doEvent:e type:KeyRelease])
1392 - (void) flagsChanged: (NSEvent *) e
1394 if (! [self doEvent:e type:KeyPress])
1395 [super flagsChanged:e];
1401 - (void) stopAndClose:(Bool)relaunch_p
1403 if ([self isAnimating])
1404 [self stopAnimation];
1406 /* Need to make the SaverListController be the firstResponder again
1407 so that it can continue to receive its own shake events. I
1408 suppose that this abstraction-breakage means that I'm adding
1409 XScreenSaverView to the UINavigationController wrong...
1411 UIViewController *v = [[self window] rootViewController];
1412 if ([v isKindOfClass: [UINavigationController class]]) {
1413 UINavigationController *n = (UINavigationController *) v;
1414 [[n topViewController] becomeFirstResponder];
1417 UIView *fader = [self superview]; // the "backgroundView" view is our parent
1419 if (relaunch_p) { // Fake a shake on the SaverListController.
1420 // Why is [self window] sometimes null here?
1421 UIWindow *w = [[UIApplication sharedApplication] keyWindow];
1422 UIViewController *v = [w rootViewController];
1423 if ([v isKindOfClass: [UINavigationController class]]) {
1424 UINavigationController *n = (UINavigationController *) v;
1425 [[n topViewController] motionEnded: UIEventSubtypeMotionShake
1428 } else { // Not launching another, animate our return to the list.
1429 [UIView animateWithDuration: 0.5
1430 animations:^{ fader.alpha = 0.0; }
1431 completion:^(BOOL finished) {
1432 [fader removeFromSuperview];
1439 /* Called after the device's orientation has changed.
1441 Note: we could include a subclass of UIViewController which
1442 contains a shouldAutorotateToInterfaceOrientation method that
1443 returns YES, in which case Core Animation would auto-rotate our
1444 View for us in response to rotation events... but, that interacts
1445 badly with the EAGLContext -- if you introduce Core Animation into
1446 the path, the OpenGL pipeline probably falls back on software
1447 rendering and performance goes to hell. Also, the scaling and
1448 rotation that Core Animation does interacts incorrectly with the GL
1451 So, we have to hack the rotation animation manually, in the GL world.
1453 Possibly XScreenSaverView should use Core Animation, and
1454 XScreenSaverGLView should override that.
1456 - (void)didRotate:(NSNotification *)notification
1458 UIDeviceOrientation current = [[UIDevice currentDevice] orientation];
1460 /* If the simulator starts up in the rotated position, sometimes
1461 the UIDevice says we're in Portrait when we're not -- but it
1462 turns out that the UINavigationController knows what's up!
1463 So get it from there.
1465 if (current == UIDeviceOrientationUnknown) {
1466 switch ([[[self window] rootViewController] interfaceOrientation]) {
1467 case UIInterfaceOrientationPortrait:
1468 current = UIDeviceOrientationPortrait;
1470 case UIInterfaceOrientationPortraitUpsideDown:
1471 current = UIDeviceOrientationPortraitUpsideDown;
1473 case UIInterfaceOrientationLandscapeLeft: // It's opposite day
1474 current = UIDeviceOrientationLandscapeRight;
1476 case UIInterfaceOrientationLandscapeRight:
1477 current = UIDeviceOrientationLandscapeLeft;
1484 /* On the iPad (but not iPhone 3GS, or the simulator) sometimes we get
1485 an orientation change event with an unknown orientation. Those seem
1486 to always be immediately followed by another orientation change with
1487 a *real* orientation change, so let's try just ignoring those bogus
1488 ones and hoping that the real one comes in shortly...
1490 if (current == UIDeviceOrientationUnknown)
1493 if (rotation_ratio >= 0) return; // in the midst of rotation animation
1494 if (orientation == current) return; // no change
1496 // When transitioning to FaceUp or FaceDown, pretend there was no change.
1497 if (current == UIDeviceOrientationFaceUp ||
1498 current == UIDeviceOrientationFaceDown)
1501 new_orientation = current; // current animation target
1502 rotation_ratio = 0; // start animating
1503 rot_start_time = double_time();
1505 switch (orientation) {
1506 case UIDeviceOrientationLandscapeLeft: angle_from = 90; break;
1507 case UIDeviceOrientationLandscapeRight: angle_from = 270; break;
1508 case UIDeviceOrientationPortraitUpsideDown: angle_from = 180; break;
1509 default: angle_from = 0; break;
1512 switch (new_orientation) {
1513 case UIDeviceOrientationLandscapeLeft: angle_to = 90; break;
1514 case UIDeviceOrientationLandscapeRight: angle_to = 270; break;
1515 case UIDeviceOrientationPortraitUpsideDown: angle_to = 180; break;
1516 default: angle_to = 0; break;
1519 switch (orientation) {
1520 case UIDeviceOrientationLandscapeRight: // from landscape
1521 case UIDeviceOrientationLandscapeLeft:
1522 rot_from.width = initial_bounds.height;
1523 rot_from.height = initial_bounds.width;
1525 default: // from portrait
1526 rot_from.width = initial_bounds.width;
1527 rot_from.height = initial_bounds.height;
1531 switch (new_orientation) {
1532 case UIDeviceOrientationLandscapeRight: // to landscape
1533 case UIDeviceOrientationLandscapeLeft:
1534 rot_to.width = initial_bounds.height;
1535 rot_to.height = initial_bounds.width;
1537 default: // to portrait
1538 rot_to.width = initial_bounds.width;
1539 rot_to.height = initial_bounds.height;
1544 // If we've done a rotation but the saver hasn't been initialized yet,
1545 // don't bother going through an X11 resize, but just do it now.
1546 rot_start_time = 0; // dawn of time
1547 [self hackRotation];
1552 /* I believe we can't use UIGestureRecognizer for tracking touches
1553 because UIPanGestureRecognizer doesn't give us enough detail in its
1556 Currently we don't handle multi-touches (just the first touch) but
1557 I'm leaving this comment here for future reference:
1559 In the simulator, multi-touch sequences look like this:
1561 touchesBegan [touchA, touchB]
1562 touchesEnd [touchA, touchB]
1564 But on real devices, sometimes you get that, but sometimes you get:
1566 touchesBegan [touchA, touchB]
1572 touchesBegan [touchA]
1573 touchesBegan [touchB]
1577 So the only way to properly detect a "pinch" gesture is to remember
1578 the start-point of each touch as it comes in; and the end-point of
1579 each touch as those come in; and only process the gesture once the
1580 number of touchEnds matches the number of touchBegins.
1583 - (void) rotateMouse:(int)rot x:(int*)x y:(int *)y w:(int)w h:(int)h
1585 // This is a no-op unless contentScaleFactor != hackedContentScaleFactor.
1586 // Currently, this is the iPad Retina only.
1587 CGRect frame = [self bounds]; // Scale.
1588 double s = [self hackedContentScaleFactor];
1589 *x *= (backbuffer_size.width / frame.size.width) / s;
1590 *y *= (backbuffer_size.height / frame.size.height) / s;
1594 #if 0 // AudioToolbox/AudioToolbox.h
1597 // There's no way to play a standard system alert sound!
1598 // We'd have to include our own WAV for that. Eh, fuck it.
1599 AudioServicesPlaySystemSound (kSystemSoundID_Vibrate);
1600 # if TARGET_IPHONE_SIMULATOR
1601 NSLog(@"BEEP"); // The sim doesn't vibrate.
1607 /* We distinguish between taps and drags.
1608 - Drags (down, motion, up) are sent to the saver to handle.
1609 - Single-taps exit the saver.
1610 This means a saver cannot respond to a single-tap. Only a few try to.
1613 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
1615 // If they are trying to pinch, just do nothing.
1616 if ([[event allTouches] count] > 1)
1621 if (xsft->event_cb && xwindow) {
1622 double s = [self hackedContentScaleFactor];
1624 memset (&xe, 0, sizeof(xe));
1626 // #### 'frame' here or 'bounds'?
1627 int w = s * [self frame].size.width;
1628 int h = s * [self frame].size.height;
1629 for (UITouch *touch in touches) {
1630 CGPoint p = [touch locationInView:self];
1631 xe.xany.type = ButtonPress;
1632 xe.xbutton.button = i + 1;
1633 xe.xbutton.button = i + 1;
1634 xe.xbutton.x = s * p.x;
1635 xe.xbutton.y = s * p.y;
1636 [self rotateMouse: rot_current_angle
1637 x: &xe.xbutton.x y: &xe.xbutton.y w: w h: h];
1638 jwxyz_mouse_moved (xdpy, xwindow, xe.xbutton.x, xe.xbutton.y);
1640 // Ignore return code: don't care whether the hack handled it.
1641 xsft->event_cb (xdpy, xwindow, xdata, &xe);
1643 // Remember when/where this was, to determine tap versus drag or hold.
1644 tap_time = double_time();
1648 break; // No pinches: only look at the first touch.
1654 - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
1656 // If they are trying to pinch, just do nothing.
1657 if ([[event allTouches] count] > 1)
1660 if (xsft->event_cb && xwindow) {
1661 double s = [self hackedContentScaleFactor];
1663 memset (&xe, 0, sizeof(xe));
1665 // #### 'frame' here or 'bounds'?
1666 int w = s * [self frame].size.width;
1667 int h = s * [self frame].size.height;
1668 for (UITouch *touch in touches) {
1669 CGPoint p = [touch locationInView:self];
1671 // If the ButtonRelease came less than half a second after ButtonPress,
1672 // and didn't move far, then this was a tap, not a drag or a hold.
1673 // Interpret it as "exit".
1675 double dist = sqrt (((p.x - tap_point.x) * (p.x - tap_point.x)) +
1676 ((p.y - tap_point.y) * (p.y - tap_point.y)));
1677 if (tap_time + 0.5 >= double_time() && dist < 20) {
1678 [self stopAndClose:NO];
1682 xe.xany.type = ButtonRelease;
1683 xe.xbutton.button = i + 1;
1684 xe.xbutton.x = s * p.x;
1685 xe.xbutton.y = s * p.y;
1686 [self rotateMouse: rot_current_angle
1687 x: &xe.xbutton.x y: &xe.xbutton.y w: w h: h];
1688 jwxyz_mouse_moved (xdpy, xwindow, xe.xbutton.x, xe.xbutton.y);
1689 xsft->event_cb (xdpy, xwindow, xdata, &xe);
1691 break; // No pinches: only look at the first touch.
1697 - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
1699 // If they are trying to pinch, just do nothing.
1700 if ([[event allTouches] count] > 1)
1703 if (xsft->event_cb && xwindow) {
1704 double s = [self hackedContentScaleFactor];
1706 memset (&xe, 0, sizeof(xe));
1708 // #### 'frame' here or 'bounds'?
1709 int w = s * [self frame].size.width;
1710 int h = s * [self frame].size.height;
1711 for (UITouch *touch in touches) {
1712 CGPoint p = [touch locationInView:self];
1713 xe.xany.type = MotionNotify;
1714 xe.xmotion.x = s * p.x;
1715 xe.xmotion.y = s * p.y;
1716 [self rotateMouse: rot_current_angle
1717 x: &xe.xbutton.x y: &xe.xbutton.y w: w h: h];
1718 jwxyz_mouse_moved (xdpy, xwindow, xe.xmotion.x, xe.xmotion.y);
1719 xsft->event_cb (xdpy, xwindow, xdata, &xe);
1721 break; // No pinches: only look at the first touch.
1727 /* We need this to respond to "shake" gestures
1729 - (BOOL)canBecomeFirstResponder
1734 - (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
1739 - (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event
1743 /* Shake means exit and launch a new saver.
1745 - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
1747 [self stopAndClose:YES];
1751 - (void)setScreenLocked:(BOOL)locked
1753 if (screenLocked == locked) return;
1754 screenLocked = locked;
1756 if ([self isAnimating])
1757 [self stopAnimation];
1759 if (! [self isAnimating])
1760 [self startAnimation];
1765 #endif // USE_IPHONE
1770 /* Utility functions...
1773 static PrefsReader *
1774 get_prefsReader (Display *dpy)
1776 XScreenSaverView *view = jwxyz_window_view (XRootWindow (dpy, 0));
1777 if (!view) return 0;
1778 return [view prefsReader];
1783 get_string_resource (Display *dpy, char *name, char *class)
1785 return [get_prefsReader(dpy) getStringResource:name];
1789 get_boolean_resource (Display *dpy, char *name, char *class)
1791 return [get_prefsReader(dpy) getBooleanResource:name];
1795 get_integer_resource (Display *dpy, char *name, char *class)
1797 return [get_prefsReader(dpy) getIntegerResource:name];
1801 get_float_resource (Display *dpy, char *name, char *class)
1803 return [get_prefsReader(dpy) getFloatResource:name];