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"
23 #import "screenhackI.h"
24 #import "xlockmoreI.h"
25 #import "jwxyz-timers.h"
28 /* Garbage collection only exists if we are being compiled against the
29 10.6 SDK or newer, not if we are building against the 10.4 SDK.
31 #ifndef MAC_OS_X_VERSION_10_6
32 # define MAC_OS_X_VERSION_10_6 1060 /* undefined in 10.4 SDK, grr */
34 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 /* 10.6 SDK */
35 # import <objc/objc-auto.h>
36 # define DO_GC_HACKERY
39 extern struct xscreensaver_function_table *xscreensaver_function_table;
41 /* Global variables used by the screen savers
44 const char *progclass;
50 extern NSDictionary *make_function_table_dict(void); // ios-function-table.m
52 /* Stub definition of the superclass, for iPhone.
54 @implementation ScreenSaverView
56 NSTimeInterval anim_interval;
61 - (id)initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview {
62 self = [super initWithFrame:frame];
64 anim_interval = 1.0/30;
67 - (NSTimeInterval)animationTimeInterval { return anim_interval; }
68 - (void)setAnimationTimeInterval:(NSTimeInterval)i { anim_interval = i; }
69 - (BOOL)hasConfigureSheet { return NO; }
70 - (NSWindow *)configureSheet { return nil; }
71 - (NSView *)configureView { return nil; }
72 - (BOOL)isPreview { return NO; }
73 - (BOOL)isAnimating { return animating_p; }
74 - (void)animateOneFrame { }
76 - (void)startAnimation {
77 if (animating_p) return;
79 anim_timer = [NSTimer scheduledTimerWithTimeInterval: anim_interval
81 selector:@selector(animateOneFrame)
86 - (void)stopAnimation {
88 [anim_timer invalidate];
95 # endif // !USE_IPHONE
99 @interface XScreenSaverView (Private)
100 - (void) stopAndClose:(Bool)relaunch;
103 @implementation XScreenSaverView
105 // Given a lower-cased saver name, returns the function table for it.
106 // If no name, guess the name from the class's bundle name.
108 - (struct xscreensaver_function_table *) findFunctionTable:(NSString *)name
110 NSBundle *nsb = [NSBundle bundleForClass:[self class]];
111 NSAssert1 (nsb, @"no bundle for class %@", [self class]);
113 NSString *path = [nsb bundlePath];
114 CFURLRef url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
116 kCFURLPOSIXPathStyle,
118 CFBundleRef cfb = CFBundleCreate (kCFAllocatorDefault, url);
120 NSAssert1 (cfb, @"no CFBundle for \"%@\"", path);
121 // #### Analyze says "Potential leak of an object stored into cfb"
124 name = [[path lastPathComponent] stringByDeletingPathExtension];
126 name = [[name lowercaseString]
127 stringByReplacingOccurrencesOfString:@" "
131 // CFBundleGetDataPointerForName doesn't work in "Archive" builds.
132 // I'm guessing that symbol-stripping is mandatory. Fuck.
133 NSString *table_name = [name stringByAppendingString:
134 @"_xscreensaver_function_table"];
135 void *addr = CFBundleGetDataPointerForName (cfb, (CFStringRef) table_name);
139 NSLog (@"no symbol \"%@\" for \"%@\"", table_name, path);
142 // Remember: any time you add a new saver to the iOS app,
143 // manually run "make ios-function-table.m"!
144 if (! function_tables)
145 function_tables = [make_function_table_dict() retain];
146 NSValue *v = [function_tables objectForKey: name];
147 void *addr = v ? [v pointerValue] : 0;
148 # endif // USE_IPHONE
150 return (struct xscreensaver_function_table *) addr;
154 // Add the "Contents/Resources/" subdirectory of this screen saver's .bundle
155 // to $PATH for the benefit of savers that include helper shell scripts.
157 - (void) setShellPath
159 NSBundle *nsb = [NSBundle bundleForClass:[self class]];
160 NSAssert1 (nsb, @"no bundle for class %@", [self class]);
162 NSString *nsdir = [nsb resourcePath];
163 NSAssert1 (nsdir, @"no resourcePath for class %@", [self class]);
164 const char *dir = [nsdir cStringUsingEncoding:NSUTF8StringEncoding];
165 const char *opath = getenv ("PATH");
166 if (!opath) opath = "/bin"; // $PATH is unset when running under Shark!
167 char *npath = (char *) malloc (strlen (opath) + strlen (dir) + 30);
168 strcpy (npath, "PATH=");
171 strcat (npath, opath);
172 if (putenv (npath)) {
174 NSAssert1 (0, @"putenv \"%s\" failed", npath);
177 /* Don't free (npath) -- MacOS's putenv() does not copy it. */
181 // set an $XSCREENSAVER_CLASSPATH variable so that included shell scripts
182 // (e.g., "xscreensaver-text") know how to look up resources.
184 - (void) setResourcesEnv:(NSString *) name
186 NSBundle *nsb = [NSBundle bundleForClass:[self class]];
187 NSAssert1 (nsb, @"no bundle for class %@", [self class]);
189 const char *s = [name cStringUsingEncoding:NSUTF8StringEncoding];
190 char *env = (char *) malloc (strlen (s) + 40);
191 strcpy (env, "XSCREENSAVER_CLASSPATH=");
195 NSAssert1 (0, @"putenv \"%s\" failed", env);
197 /* Don't free (env) -- MacOS's putenv() does not copy it. */
202 add_default_options (const XrmOptionDescRec *opts,
203 const char * const *defs,
204 XrmOptionDescRec **opts_ret,
205 const char ***defs_ret)
207 /* These aren't "real" command-line options (there are no actual command-line
208 options in the Cocoa version); but this is the somewhat kludgey way that
209 the <xscreensaver-text /> and <xscreensaver-image /> tags in the
210 ../hacks/config/\*.xml files communicate with the preferences database.
212 static const XrmOptionDescRec default_options [] = {
213 { "-text-mode", ".textMode", XrmoptionSepArg, 0 },
214 { "-text-literal", ".textLiteral", XrmoptionSepArg, 0 },
215 { "-text-file", ".textFile", XrmoptionSepArg, 0 },
216 { "-text-url", ".textURL", XrmoptionSepArg, 0 },
217 { "-text-program", ".textProgram", XrmoptionSepArg, 0 },
218 { "-grab-desktop", ".grabDesktopImages", XrmoptionNoArg, "True" },
219 { "-no-grab-desktop", ".grabDesktopImages", XrmoptionNoArg, "False"},
220 { "-choose-random-images", ".chooseRandomImages",XrmoptionNoArg, "True" },
221 { "-no-choose-random-images",".chooseRandomImages",XrmoptionNoArg, "False"},
222 { "-image-directory", ".imageDirectory", XrmoptionSepArg, 0 },
223 { "-fps", ".doFPS", XrmoptionNoArg, "True" },
224 { "-no-fps", ".doFPS", XrmoptionNoArg, "False"},
225 { "-foreground", ".foreground", XrmoptionSepArg, 0 },
226 { "-fg", ".foreground", XrmoptionSepArg, 0 },
227 { "-background", ".background", XrmoptionSepArg, 0 },
228 { "-bg", ".background", XrmoptionSepArg, 0 },
231 // <xscreensaver-updater />
232 { "-" SUSUEnableAutomaticChecksKey,
233 "." SUSUEnableAutomaticChecksKey, XrmoptionNoArg, "True" },
234 { "-no-" SUSUEnableAutomaticChecksKey,
235 "." SUSUEnableAutomaticChecksKey, XrmoptionNoArg, "False" },
236 { "-" SUAutomaticallyUpdateKey,
237 "." SUAutomaticallyUpdateKey, XrmoptionNoArg, "True" },
238 { "-no-" SUAutomaticallyUpdateKey,
239 "." SUAutomaticallyUpdateKey, XrmoptionNoArg, "False" },
240 { "-" SUSendProfileInfoKey,
241 "." SUSendProfileInfoKey, XrmoptionNoArg,"True" },
242 { "-no-" SUSendProfileInfoKey,
243 "." SUSendProfileInfoKey, XrmoptionNoArg,"False"},
244 { "-" SUScheduledCheckIntervalKey,
245 "." SUScheduledCheckIntervalKey, XrmoptionSepArg, 0 },
246 # endif // !USE_IPHONE
250 static const char *default_defaults [] = {
252 ".doubleBuffer: True",
253 ".multiSample: False",
261 ".textURL: http://en.wikipedia.org/w/index.php?title=Special:NewPages&feed=rss",
263 ".grabDesktopImages: yes",
265 ".chooseRandomImages: no",
267 ".chooseRandomImages: yes",
269 ".imageDirectory: ~/Pictures",
274 # define STR(S) STR1(S)
275 # define __objc_yes Yes
276 # define __objc_no No
277 "." SUSUEnableAutomaticChecksKey ": " STR(SUSUEnableAutomaticChecksDef),
278 "." SUAutomaticallyUpdateKey ": " STR(SUAutomaticallyUpdateDef),
279 "." SUSendProfileInfoKey ": " STR(SUSendProfileInfoDef),
280 "." SUScheduledCheckIntervalKey ": " STR(SUScheduledCheckIntervalDef),
285 # endif // USE_IPHONE
290 for (i = 0; default_options[i].option; i++)
292 for (i = 0; opts[i].option; i++)
295 XrmOptionDescRec *opts2 = (XrmOptionDescRec *)
296 calloc (count + 1, sizeof (*opts2));
300 while (default_options[j].option) {
301 opts2[i] = default_options[j];
305 while (opts[j].option) {
316 for (i = 0; default_defaults[i]; i++)
318 for (i = 0; defs[i]; i++)
321 const char **defs2 = (const char **) calloc (count + 1, sizeof (*defs2));
325 while (default_defaults[j]) {
326 defs2[i] = default_defaults[j];
340 /* Returns the current time in seconds as a double.
346 # ifdef GETTIMEOFDAY_TWO_ARGS
348 gettimeofday(&now, &tzp);
353 return (now.tv_sec + ((double) now.tv_usec * 0.000001));
358 - (id) initWithFrame:(NSRect)frame
359 saverName:(NSString *)saverName
360 isPreview:(BOOL)isPreview
363 initial_bounds = frame.size;
364 rot_current_size = frame.size; // needs to be early, because
365 rot_from = rot_current_size; // [self setFrame] is called by
366 rot_to = rot_current_size; // [super initWithFrame].
370 if (! (self = [super initWithFrame:frame isPreview:isPreview]))
373 xsft = [self findFunctionTable: saverName];
382 [self setMultipleTouchEnabled:YES];
383 orientation = UIDeviceOrientationUnknown;
384 [self didRotate:nil];
385 # endif // USE_IPHONE
389 xsft->setup_cb (xsft, xsft->setup_arg);
391 /* The plist files for these preferences show up in
392 $HOME/Library/Preferences/ByHost/ in a file named like
393 "org.jwz.xscreensaver.<SAVERNAME>.<NUMBERS>.plist"
395 NSString *name = [NSString stringWithCString:xsft->progclass
396 encoding:NSISOLatin1StringEncoding];
397 name = [@"org.jwz.xscreensaver." stringByAppendingString:name];
398 [self setResourcesEnv:name];
401 XrmOptionDescRec *opts = 0;
402 const char **defs = 0;
403 add_default_options (xsft->options, xsft->defaults, &opts, &defs);
404 prefsReader = [[PrefsReader alloc]
405 initWithName:name xrmKeys:opts defaults:defs];
407 // free (opts); // bah, we need these! #### leak!
408 xsft->options = opts;
410 progname = progclass = xsft->progclass;
414 # ifdef USE_BACKBUFFER
415 [self createBackbuffer];
420 // So we can tell when we're docked.
421 [UIDevice currentDevice].batteryMonitoringEnabled = YES;
422 # endif // USE_IPHONE
429 # if !defined(USE_IPHONE) && defined(USE_CALAYER)
430 [self setLayer: [CALayer layer]];
431 self.layer.delegate = self;
432 self.layer.opaque = YES;
433 [self setWantsLayer: YES];
434 # endif // !USE_IPHONE && USE_CALAYER
438 - (id) initWithFrame:(NSRect)frame isPreview:(BOOL)p
440 return [self initWithFrame:frame saverName:0 isPreview:p];
446 NSAssert(![self isAnimating], @"still animating");
447 NSAssert(!xdata, @"xdata not yet freed");
449 jwxyz_free_display (xdpy);
451 # ifdef USE_BACKBUFFER
453 CGContextRelease (backbuffer);
456 CGColorSpaceRelease (colorspace);
460 CGContextRelease (window_ctx);
461 # endif // !USE_CALAYER
463 # endif // USE_BACKBUFFER
465 [prefsReader release];
473 - (PrefsReader *) prefsReader
480 - (void) lockFocus { }
481 - (void) unlockFocus { }
487 /* A few seconds after the saver launches, we store the "wasRunning"
488 preference. This is so that if the saver is crashing at startup,
489 we don't launch it again next time, getting stuck in a crash loop.
491 - (void) allSystemsGo: (NSTimer *) timer
493 NSAssert (timer == crash_timer, @"crash timer screwed up");
496 NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
497 [prefs setBool:YES forKey:@"wasRunning"];
503 - (void) startAnimation
505 NSAssert(![self isAnimating], @"already animating");
506 NSAssert(!initted_p && !xdata, @"already initialized");
507 [super startAnimation];
508 /* We can't draw on the window from this method, so we actually do the
509 initialization of the screen saver (xsft->init_cb) in the first call
510 to animateOneFrame() instead.
515 [crash_timer invalidate];
517 NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
518 [prefs removeObjectForKey:@"wasRunning"];
521 crash_timer = [NSTimer scheduledTimerWithTimeInterval: 5
523 selector:@selector(allSystemsGo:)
527 # endif // USE_IPHONE
529 // Never automatically turn the screen off if we are docked,
530 // and an animation is running.
533 [UIApplication sharedApplication].idleTimerDisabled =
534 ([UIDevice currentDevice].batteryState != UIDeviceBatteryStateUnplugged);
535 [[UIApplication sharedApplication]
536 setStatusBarHidden:YES withAnimation:UIStatusBarAnimationNone];
541 - (void)stopAnimation
543 NSAssert([self isAnimating], @"not animating");
547 [self lockFocus]; // in case something tries to draw from here
548 [self prepareContext];
550 /* I considered just not even calling the free callback at all...
551 But webcollage-cocoa needs it, to kill the inferior webcollage
552 processes (since the screen saver framework never generates a
553 SIGPIPE for them...) Instead, I turned off the free call in
554 xlockmore.c, which is where all of the bogus calls are anyway.
556 xsft->free_cb (xdpy, xwindow, xdata);
559 // setup_p = NO; // #### wait, do we need this?
566 [crash_timer invalidate];
568 NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
569 [prefs removeObjectForKey:@"wasRunning"];
571 # endif // USE_IPHONE
573 [super stopAnimation];
575 // When an animation is no longer running (e.g., looking at the list)
576 // then it's ok to power off the screen when docked.
579 [UIApplication sharedApplication].idleTimerDisabled = NO;
580 [[UIApplication sharedApplication]
581 setStatusBarHidden:NO withAnimation:UIStatusBarAnimationNone];
586 /* Hook for the XScreenSaverGLView subclass
588 - (void) prepareContext
592 /* Hook for the XScreenSaverGLView subclass
594 - (void) resizeContext
600 screenhack_do_fps (Display *dpy, Window w, fps_state *fpst, void *closure)
602 fps_compute (fpst, 0, -1);
609 /* On iPhones with Retina displays, we can draw the savers in "real"
610 pixels, and that works great. The 320x480 "point" screen is really
611 a 640x960 *pixel* screen. However, Retina iPads have 768x1024
612 point screens which are 1536x2048 pixels, and apparently that's
613 enough pixels that copying those bits to the screen is slow. Like,
614 drops us from 15fps to 7fps. So, on Retina iPads, we don't draw in
615 real pixels. This will probably make the savers look better
616 anyway, since that's a higher resolution than most desktop monitors
617 have even today. (This is only true for X11 programs, not GL
618 programs. Those are fine at full rez.)
620 This method is overridden in XScreenSaverGLView, since this kludge
621 isn't necessary for GL programs, being resolution independent by
624 - (CGFloat) hackedContentScaleFactor
626 GLfloat s = [self contentScaleFactor];
627 if (initial_bounds.width >= 1024 ||
628 initial_bounds.height >= 1024)
634 static GLfloat _global_rot_current_angle_kludge;
636 double current_device_rotation (void)
638 return -_global_rot_current_angle_kludge;
642 - (void) hackRotation
644 if (rotation_ratio >= 0) { // in the midst of a rotation animation
646 # define CLAMP180(N) while (N < 0) N += 360; while (N > 180) N -= 360
647 GLfloat f = angle_from;
648 GLfloat t = angle_to;
651 GLfloat dist = -(t-f);
654 // Intermediate angle.
655 rot_current_angle = f - rotation_ratio * dist;
657 // Intermediate frame size.
658 rot_current_size.width = rot_from.width +
659 rotation_ratio * (rot_to.width - rot_from.width);
660 rot_current_size.height = rot_from.height +
661 rotation_ratio * (rot_to.height - rot_from.height);
663 // Tick animation. Complete rotation in 1/6th sec.
664 double now = double_time();
665 double duration = 1/6.0;
666 rotation_ratio = 1 - ((rot_start_time + duration - now) / duration);
668 if (rotation_ratio > 1) { // Done animating.
669 orientation = new_orientation;
670 rot_current_angle = angle_to;
671 rot_current_size = rot_to;
674 // Check orientation again in case we rotated again while rotating:
675 // this is a no-op if nothing has changed.
676 [self didRotate:nil];
678 } else { // Not animating a rotation.
679 rot_current_angle = angle_to;
680 rot_current_size = rot_to;
683 CLAMP180(rot_current_angle);
684 _global_rot_current_angle_kludge = rot_current_angle;
688 double s = [self hackedContentScaleFactor];
689 if (!ignore_rotation_p &&
690 /* rotation_ratio && */
691 ((int) backbuffer_size.width != (int) (s * rot_current_size.width) ||
692 (int) backbuffer_size.height != (int) (s * rot_current_size.height)))
697 - (void)alertView:(UIAlertView *)av clickedButtonAtIndex:(NSInteger)i
699 if (i == 0) exit (-1); // Cancel
700 [self stopAndClose:NO]; // Keep going
703 - (void) handleException: (NSException *)e
705 NSLog (@"Caught exception: %@", e);
706 [[[UIAlertView alloc] initWithTitle:
707 [NSString stringWithFormat: @"%s crashed!",
710 [NSString stringWithFormat:
711 @"The error message was:"
713 "If it keeps crashing, try "
714 "resetting its options.",
717 cancelButtonTitle: @"Exit"
718 otherButtonTitles: @"Keep going", nil]
720 [self stopAnimation];
726 #ifdef USE_BACKBUFFER
728 /* Create a bitmap context into which we render everything.
729 If the desired size has changed, re-created it.
731 - (void) createBackbuffer
734 double s = [self hackedContentScaleFactor];
735 CGSize rotsize = ignore_rotation_p ? initial_bounds : rot_current_size;
736 int new_w = s * rotsize.width;
737 int new_h = s * rotsize.height;
739 int new_w = [self bounds].size.width;
740 int new_h = [self bounds].size.height;
743 // Colorspaces and CGContexts only happen with non-GL hacks.
745 CGColorSpaceRelease (colorspace);
748 CGContextRelease (window_ctx);
751 NSWindow *window = [self window];
753 if (window && xdpy) {
757 // TODO: This was borrowed from jwxyz_window_resized, and should
758 // probably be refactored.
760 // Figure out which screen the window is currently on.
761 CGDirectDisplayID cgdpy = 0;
765 // TODO: XTranslateCoordinates is returning (0,1200) on my system.
767 // In any case, those weren't valid coordinates for CGGetDisplaysWithPoint.
768 // XTranslateCoordinates (xdpy, xwindow, NULL, 0, 0, &wx, &wy, NULL);
773 p0 = [window convertBaseToScreen:p0];
774 CGPoint p = {p0.x, p0.y};
776 CGGetDisplaysWithPoint (p, 1, &cgdpy, &n);
777 NSAssert (cgdpy, @"unable to find CGDisplay");
781 // Figure out this screen's colorspace, and use that for every CGImage.
783 CMProfileRef profile = 0;
785 // CMGetProfileByAVID is deprecated as of OS X 10.6, but there's no
786 // documented replacement as of OS X 10.9.
787 // http://lists.apple.com/archives/colorsync-dev/2012/Nov/msg00001.html
788 CMGetProfileByAVID ((CMDisplayIDType) cgdpy, &profile);
789 NSAssert (profile, @"unable to find colorspace profile");
790 colorspace = CGColorSpaceCreateWithPlatformColorSpace (profile);
791 NSAssert (colorspace, @"unable to find colorspace");
793 # else // USE_CALAYER
794 // Was apparently faster until 10.9.
795 colorspace = CGColorSpaceCreateDeviceRGB ();
796 # endif // USE_CALAYER
799 window_ctx = [[window graphicsContext] graphicsPort];
800 CGContextRetain (window_ctx);
801 # endif // !USE_CALAYER
807 # endif // !USE_CALAYER
808 colorspace = CGColorSpaceCreateDeviceRGB();
812 backbuffer_size.width == new_w &&
813 backbuffer_size.height == new_h)
816 CGSize osize = backbuffer_size;
817 CGContextRef ob = backbuffer;
819 backbuffer_size.width = new_w;
820 backbuffer_size.height = new_h;
822 backbuffer = CGBitmapContextCreate (NULL,
823 backbuffer_size.width,
824 backbuffer_size.height,
826 backbuffer_size.width * 4,
828 // kCGImageAlphaPremultipliedLast
829 (kCGImageAlphaNoneSkipFirst |
830 kCGBitmapByteOrder32Host)
832 NSAssert (backbuffer, @"unable to allocate back buffer");
836 r.origin.x = r.origin.y = 0;
837 r.size = backbuffer_size;
838 CGContextSetGrayFillColor (backbuffer, 0, 1);
839 CGContextFillRect (backbuffer, r);
842 // Restore old bits, as much as possible, to the X11 upper left origin.
845 rect.origin.y = (backbuffer_size.height - osize.height);
847 CGImageRef img = CGBitmapContextCreateImage (ob);
848 CGContextDrawImage (backbuffer, rect, img);
849 CGImageRelease (img);
850 CGContextRelease (ob);
854 #endif // USE_BACKBUFFER
857 /* Inform X11 that the size of our window has changed.
861 if (!xwindow) return; // early
863 # ifdef USE_BACKBUFFER
864 [self createBackbuffer];
865 jwxyz_window_resized (xdpy, xwindow,
867 backbuffer_size.width, backbuffer_size.height,
869 # else // !USE_BACKBUFFER
870 NSRect r = [self frame]; // ignoring rotation is closer
871 r.size = [self bounds].size; // to what XGetGeometry expects.
872 jwxyz_window_resized (xdpy, xwindow,
873 r.origin.x, r.origin.y,
874 r.size.width, r.size.height,
876 # endif // !USE_BACKBUFFER
878 // Next time render_x11 is called, run the saver's reshape_cb.
888 if (orientation == UIDeviceOrientationUnknown)
889 [self didRotate:nil];
896 # ifdef USE_BACKBUFFER
897 NSAssert (backbuffer, @"no back buffer");
898 xdpy = jwxyz_make_display (self, backbuffer);
900 xdpy = jwxyz_make_display (self, 0);
902 xwindow = XRootWindow (xdpy, 0);
905 /* Some X11 hacks (fluidballs) want to ignore all rotation events. */
907 get_boolean_resource (xdpy, "ignoreRotation", "IgnoreRotation");
908 # endif // USE_IPHONE
916 xsft->setup_cb (xsft, xsft->setup_arg);
920 NSAssert(!xdata, @"xdata already initialized");
926 XSetWindowBackground (xdpy, xwindow,
927 get_pixel_resource (xdpy, 0,
928 "background", "Background"));
929 XClearWindow (xdpy, xwindow);
932 [[self window] setAcceptsMouseMovedEvents:YES];
935 /* In MacOS 10.5, this enables "QuartzGL", meaning that the Quartz
936 drawing primitives will run on the GPU instead of the CPU.
937 It seems like it might make things worse rather than better,
938 though... Plus it makes us binary-incompatible with 10.4.
940 # if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
941 [[self window] setPreferredBackingLocation:
942 NSWindowBackingLocationVideoMemory];
946 /* Kludge: even though the init_cb functions are declared to take 2 args,
947 actually call them with 3, for the benefit of xlockmore_init() and
950 void *(*init_cb) (Display *, Window, void *) =
951 (void *(*) (Display *, Window, void *)) xsft->init_cb;
953 xdata = init_cb (xdpy, xwindow, xsft->setup_arg);
955 if (get_boolean_resource (xdpy, "doFPS", "DoFPS")) {
956 fpst = fps_init (xdpy, xwindow);
957 if (! xsft->fps_cb) xsft->fps_cb = screenhack_do_fps;
962 [self checkForUpdates];
966 /* I don't understand why we have to do this *every frame*, but we do,
967 or else the cursor comes back on.
970 if (![self isPreview])
971 [NSCursor setHiddenUntilMouseMoves:YES];
977 /* This is just a guess, but the -fps code wants to know how long
978 we were sleeping between frames.
980 long usecs = 1000000 * [self animationTimeInterval];
981 usecs -= 200; // caller apparently sleeps for slightly less sometimes...
982 if (usecs < 0) usecs = 0;
983 fps_slept (fpst, usecs);
987 /* It turns out that [ScreenSaverView setAnimationTimeInterval] does nothing.
988 This is bad, because some of the screen hacks want to delay for long
989 periods (like 5 seconds or a minute!) between frames, and running them
990 all at 60 FPS is no good.
992 So, we don't use setAnimationTimeInterval, and just let the framework call
993 us whenever. But, we only invoke the screen hack's "draw frame" method
994 when enough time has expired.
996 This means two extra calls to gettimeofday() per frame. For fast-cycling
997 screen savers, that might actually slow them down. Oh well.
999 #### Also, we do not run the draw callback faster than the system's
1000 animationTimeInterval, so if any savers are pickier about timing
1001 than that, this may slow them down too much. If that's a problem,
1002 then we could call draw_cb in a loop here (with usleep) until the
1003 next call would put us past animationTimeInterval... But a better
1004 approach would probably be to just change the saver to not do that.
1007 gettimeofday (&tv, 0);
1008 double now = tv.tv_sec + (tv.tv_usec / 1000000.0);
1009 if (now < next_frame_time) return;
1011 [self prepareContext];
1014 // We do this here instead of in setFrame so that all the
1015 // Xlib drawing takes place under the animation timer.
1016 [self resizeContext];
1018 # ifndef USE_BACKBUFFER
1020 # else // USE_BACKBUFFER
1023 r.size.width = backbuffer_size.width;
1024 r.size.height = backbuffer_size.height;
1025 # endif // USE_BACKBUFFER
1027 xsft->reshape_cb (xdpy, xwindow, xdata, r.size.width, r.size.height);
1031 // Run any XtAppAddInput callbacks now.
1032 // (Note that XtAppAddTimeOut callbacks have already been run by
1033 // the Cocoa event loop.)
1035 jwxyz_sources_run (display_sources_data (xdpy));
1041 NSDisableScreenUpdates();
1043 unsigned long delay = xsft->draw_cb (xdpy, xwindow, xdata);
1044 if (fpst) xsft->fps_cb (xdpy, xwindow, fpst, xdata);
1046 NSEnableScreenUpdates();
1049 gettimeofday (&tv, 0);
1050 now = tv.tv_sec + (tv.tv_usec / 1000000.0);
1051 next_frame_time = now + (delay / 1000000.0);
1053 # ifdef USE_IPHONE // Allow savers on the iPhone to run full-tilt.
1054 if (delay < [self animationTimeInterval])
1055 [self setAnimationTimeInterval:(delay / 1000000.0)];
1058 # ifdef DO_GC_HACKERY
1059 /* Current theory is that the 10.6 garbage collector sucks in the
1062 It only does a collection when a threshold of outstanding
1063 collectable allocations has been surpassed. However, CoreGraphics
1064 creates lots of small collectable allocations that contain pointers
1065 to very large non-collectable allocations: a small CG object that's
1066 collectable referencing large malloc'd allocations (non-collectable)
1067 containing bitmap data. So the large allocation doesn't get freed
1068 until GC collects the small allocation, which triggers its finalizer
1069 to run which frees the large allocation. So GC is deciding that it
1070 doesn't really need to run, even though the process has gotten
1071 enormous. GC eventually runs once pageouts have happened, but by
1072 then it's too late, and the machine's resident set has been
1075 So, we force an exhaustive garbage collection in this process
1076 approximately every 5 seconds whether the system thinks it needs
1080 static int tick = 0;
1081 if (++tick > 5*30) {
1083 objc_collect (OBJC_EXHAUSTIVE_COLLECTION);
1086 # endif // DO_GC_HACKERY
1090 @catch (NSException *e) {
1091 [self handleException: e];
1093 # endif // USE_IPHONE
1097 /* drawRect always does nothing, and animateOneFrame renders bits to the
1098 screen. This is (now) true of both X11 and GL on both MacOS and iOS.
1101 - (void)drawRect:(NSRect)rect
1103 if (xwindow) // clear to the X window's bg color, not necessarily black.
1104 XClearWindow (xdpy, xwindow);
1106 [super drawRect:rect]; // early: black.
1110 #ifndef USE_BACKBUFFER
1112 - (void) animateOneFrame
1115 jwxyz_flush_context(xdpy);
1118 #else // USE_BACKBUFFER
1120 - (void) animateOneFrame
1122 // Render X11 into the backing store bitmap...
1124 NSAssert (backbuffer, @"no back buffer");
1127 UIGraphicsPushContext (backbuffer);
1133 UIGraphicsPopContext();
1137 // Then compute the transformations for rotation.
1138 double hs = [self hackedContentScaleFactor];
1139 double s = [self contentScaleFactor];
1141 // The rotation origin for layer.affineTransform is in the center already.
1142 CGAffineTransform t = ignore_rotation_p ?
1143 CGAffineTransformIdentity :
1144 CGAffineTransformMakeRotation (rot_current_angle / (180.0 / M_PI));
1147 self.layer.affineTransform = CGAffineTransformScale(t, f, f);
1150 bounds.origin.x = 0;
1151 bounds.origin.y = 0;
1152 bounds.size.width = backbuffer_size.width / s;
1153 bounds.size.height = backbuffer_size.height / s;
1154 self.layer.bounds = bounds;
1155 # endif // USE_IPHONE
1158 [self.layer setNeedsDisplay];
1159 # else // !USE_CALAYER
1161 w = CGBitmapContextGetWidth (backbuffer),
1162 h = CGBitmapContextGetHeight (backbuffer);
1164 size_t bpl = CGBitmapContextGetBytesPerRow (backbuffer);
1165 CGDataProviderRef prov = CGDataProviderCreateWithData (NULL,
1166 CGBitmapContextGetData(backbuffer),
1171 CGImageRef img = CGImageCreate (w, h,
1173 CGBitmapContextGetBytesPerRow(backbuffer),
1175 CGBitmapContextGetBitmapInfo(backbuffer),
1177 kCGRenderingIntentDefault);
1179 CGDataProviderRelease (prov);
1184 rect.size = backbuffer_size;
1185 CGContextDrawImage (window_ctx, rect, img);
1187 CGImageRelease (img);
1189 CGContextFlush (window_ctx);
1190 # endif // !USE_CALAYER
1195 - (void) drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
1197 // This "isn't safe" if NULL is passed to CGBitmapCreateContext before iOS 4.
1198 char *dest_data = (char *)CGBitmapContextGetData (ctx);
1200 // The CGContext here is normally upside-down on iOS.
1202 CGBitmapContextGetBitmapInfo (ctx) ==
1203 (kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host)
1205 && CGContextGetCTM (ctx).d < 0
1206 # endif // USE_IPHONE
1209 size_t dest_height = CGBitmapContextGetHeight (ctx);
1210 size_t dest_bpr = CGBitmapContextGetBytesPerRow (ctx);
1211 size_t src_height = CGBitmapContextGetHeight (backbuffer);
1212 size_t src_bpr = CGBitmapContextGetBytesPerRow (backbuffer);
1213 char *src_data = (char *)CGBitmapContextGetData (backbuffer);
1215 size_t height = src_height < dest_height ? src_height : dest_height;
1217 if (src_bpr == dest_bpr) {
1218 // iPad 1: 4.0 ms, iPad 2: 6.7 ms
1219 memcpy (dest_data, src_data, src_bpr * height);
1221 // iPad 1: 4.6 ms, iPad 2: 7.2 ms
1222 size_t bpr = src_bpr < dest_bpr ? src_bpr : dest_bpr;
1224 memcpy (dest_data, src_data, bpr);
1226 src_data += src_bpr;
1227 dest_data += dest_bpr;
1232 // iPad 1: 9.6 ms, iPad 2: 12.1 ms
1235 CGContextScaleCTM (ctx, 1, -1);
1236 CGFloat s = [self contentScaleFactor];
1237 CGFloat hs = [self hackedContentScaleFactor];
1238 CGContextTranslateCTM (ctx, 0, -backbuffer_size.height * hs / s);
1239 # endif // USE_IPHONE
1241 CGImageRef img = CGBitmapContextCreateImage (backbuffer);
1242 CGContextDrawImage (ctx, self.layer.bounds, img);
1243 CGImageRelease (img);
1246 # endif // USE_CALAYER
1248 #endif // USE_BACKBUFFER
1252 - (void) setFrame:(NSRect) newRect
1254 [super setFrame:newRect];
1256 if (xwindow) // inform Xlib that the window has changed now.
1261 # ifndef USE_IPHONE // Doesn't exist on iOS
1262 - (void) setFrameSize:(NSSize) newSize
1264 [super setFrameSize:newSize];
1268 # endif // !USE_IPHONE
1271 +(BOOL) performGammaFade
1276 - (BOOL) hasConfigureSheet
1281 + (NSString *) decompressXML: (NSData *)data
1283 if (! data) return 0;
1284 BOOL compressed_p = !!strncmp ((const char *) data.bytes, "<?xml", 5);
1286 // If it's not already XML, decompress it.
1287 NSAssert (compressed_p, @"xml isn't compressed");
1289 NSMutableData *data2 = 0;
1292 memset (&zs, 0, sizeof(zs));
1293 ret = inflateInit2 (&zs, 16 + MAX_WBITS);
1295 UInt32 usize = * (UInt32 *) (data.bytes + data.length - 4);
1296 data2 = [NSMutableData dataWithLength: usize];
1297 zs.next_in = (Bytef *) data.bytes;
1298 zs.avail_in = data.length;
1299 zs.next_out = (Bytef *) data2.bytes;
1300 zs.avail_out = data2.length;
1301 ret = inflate (&zs, Z_FINISH);
1304 if (ret == Z_OK || ret == Z_STREAM_END)
1307 NSAssert2 (0, @"gunzip error: %d: %s",
1308 ret, (zs.msg ? zs.msg : "<null>"));
1311 return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
1316 - (NSWindow *) configureSheet
1318 - (UIViewController *) configureView
1321 NSBundle *bundle = [NSBundle bundleForClass:[self class]];
1322 NSString *file = [NSString stringWithCString:xsft->progclass
1323 encoding:NSISOLatin1StringEncoding];
1324 file = [file lowercaseString];
1325 NSString *path = [bundle pathForResource:file ofType:@"xml"];
1327 NSLog (@"%@.xml does not exist in the application bundle: %@/",
1328 file, [bundle resourcePath]);
1333 UIViewController *sheet;
1334 # else // !USE_IPHONE
1336 # endif // !USE_IPHONE
1338 NSData *xmld = [NSData dataWithContentsOfFile:path];
1339 NSString *xml = [[self class] decompressXML: xmld];
1340 sheet = [[XScreenSaverConfigSheet alloc]
1341 initWithXML:[xml dataUsingEncoding:NSUTF8StringEncoding]
1342 options:xsft->options
1343 controller:[prefsReader userDefaultsController]
1344 globalController:[prefsReader globalDefaultsController]
1345 defaults:[prefsReader defaultOptions]];
1347 // #### am I expected to retain this, or not? wtf.
1348 // I thought not, but if I don't do this, we (sometimes) crash.
1349 // #### Analyze says "potential leak of an object stored into sheet"
1356 - (NSUserDefaultsController *) userDefaultsController
1358 return [prefsReader userDefaultsController];
1362 /* Announce our willingness to accept keyboard input.
1364 - (BOOL)acceptsFirstResponder
1372 /* Convert an NSEvent into an XEvent, and pass it along.
1373 Returns YES if it was handled.
1375 - (BOOL) doEvent: (NSEvent *) e
1378 if (![self isPreview] || // no event handling if actually screen-saving!
1379 ![self isAnimating] ||
1384 memset (&xe, 0, sizeof(xe));
1388 int flags = [e modifierFlags];
1389 if (flags & NSAlphaShiftKeyMask) state |= LockMask;
1390 if (flags & NSShiftKeyMask) state |= ShiftMask;
1391 if (flags & NSControlKeyMask) state |= ControlMask;
1392 if (flags & NSAlternateKeyMask) state |= Mod1Mask;
1393 if (flags & NSCommandKeyMask) state |= Mod2Mask;
1395 NSPoint p = [[[e window] contentView] convertPoint:[e locationInWindow]
1398 double s = [self hackedContentScaleFactor];
1403 int y = s * ([self bounds].size.height - p.y);
1405 xe.xany.type = type;
1411 xe.xbutton.state = state;
1412 if ([e type] == NSScrollWheel)
1413 xe.xbutton.button = ([e deltaY] > 0 ? Button4 :
1414 [e deltaY] < 0 ? Button5 :
1415 [e deltaX] > 0 ? Button6 :
1416 [e deltaX] < 0 ? Button7 :
1419 xe.xbutton.button = [e buttonNumber] + 1;
1424 xe.xmotion.state = state;
1429 NSString *ns = (([e type] == NSFlagsChanged) ? 0 :
1430 [e charactersIgnoringModifiers]);
1433 if (!ns || [ns length] == 0) // dead key
1435 // Cocoa hides the difference between left and right keys.
1436 // Also we only get KeyPress events for these, no KeyRelease
1437 // (unless we hack the mod state manually. Bleh.)
1439 if (flags & NSAlphaShiftKeyMask) k = XK_Caps_Lock;
1440 else if (flags & NSShiftKeyMask) k = XK_Shift_L;
1441 else if (flags & NSControlKeyMask) k = XK_Control_L;
1442 else if (flags & NSAlternateKeyMask) k = XK_Alt_L;
1443 else if (flags & NSCommandKeyMask) k = XK_Meta_L;
1445 else if ([ns length] == 1) // real key
1447 switch ([ns characterAtIndex:0]) {
1448 case NSLeftArrowFunctionKey: k = XK_Left; break;
1449 case NSRightArrowFunctionKey: k = XK_Right; break;
1450 case NSUpArrowFunctionKey: k = XK_Up; break;
1451 case NSDownArrowFunctionKey: k = XK_Down; break;
1452 case NSPageUpFunctionKey: k = XK_Page_Up; break;
1453 case NSPageDownFunctionKey: k = XK_Page_Down; break;
1454 case NSHomeFunctionKey: k = XK_Home; break;
1455 case NSPrevFunctionKey: k = XK_Prior; break;
1456 case NSNextFunctionKey: k = XK_Next; break;
1457 case NSBeginFunctionKey: k = XK_Begin; break;
1458 case NSEndFunctionKey: k = XK_End; break;
1462 [ns cStringUsingEncoding:NSISOLatin1StringEncoding];
1463 k = (s && *s ? *s : 0);
1469 if (! k) return YES; // E.g., "KeyRelease XK_Shift_L"
1471 xe.xkey.keycode = k;
1472 xe.xkey.state = state;
1476 NSAssert1 (0, @"unknown X11 event type: %d", type);
1481 [self prepareContext];
1482 BOOL result = xsft->event_cb (xdpy, xwindow, xdata, &xe);
1488 - (void) mouseDown: (NSEvent *) e
1490 if (! [self doEvent:e type:ButtonPress])
1491 [super mouseDown:e];
1494 - (void) mouseUp: (NSEvent *) e
1496 if (! [self doEvent:e type:ButtonRelease])
1500 - (void) otherMouseDown: (NSEvent *) e
1502 if (! [self doEvent:e type:ButtonPress])
1503 [super otherMouseDown:e];
1506 - (void) otherMouseUp: (NSEvent *) e
1508 if (! [self doEvent:e type:ButtonRelease])
1509 [super otherMouseUp:e];
1512 - (void) mouseMoved: (NSEvent *) e
1514 if (! [self doEvent:e type:MotionNotify])
1515 [super mouseMoved:e];
1518 - (void) mouseDragged: (NSEvent *) e
1520 if (! [self doEvent:e type:MotionNotify])
1521 [super mouseDragged:e];
1524 - (void) otherMouseDragged: (NSEvent *) e
1526 if (! [self doEvent:e type:MotionNotify])
1527 [super otherMouseDragged:e];
1530 - (void) scrollWheel: (NSEvent *) e
1532 if (! [self doEvent:e type:ButtonPress])
1533 [super scrollWheel:e];
1536 - (void) keyDown: (NSEvent *) e
1538 if (! [self doEvent:e type:KeyPress])
1542 - (void) keyUp: (NSEvent *) e
1544 if (! [self doEvent:e type:KeyRelease])
1548 - (void) flagsChanged: (NSEvent *) e
1550 if (! [self doEvent:e type:KeyPress])
1551 [super flagsChanged:e];
1557 - (void) stopAndClose:(Bool)relaunch_p
1559 if ([self isAnimating])
1560 [self stopAnimation];
1562 /* Need to make the SaverListController be the firstResponder again
1563 so that it can continue to receive its own shake events. I
1564 suppose that this abstraction-breakage means that I'm adding
1565 XScreenSaverView to the UINavigationController wrong...
1567 UIViewController *v = [[self window] rootViewController];
1568 if ([v isKindOfClass: [UINavigationController class]]) {
1569 UINavigationController *n = (UINavigationController *) v;
1570 [[n topViewController] becomeFirstResponder];
1573 UIView *fader = [self superview]; // the "backgroundView" view is our parent
1575 if (relaunch_p) { // Fake a shake on the SaverListController.
1576 // Why is [self window] sometimes null here?
1577 UIWindow *w = [[UIApplication sharedApplication] keyWindow];
1578 UIViewController *v = [w rootViewController];
1579 if ([v isKindOfClass: [UINavigationController class]]) {
1580 UINavigationController *n = (UINavigationController *) v;
1581 [[n topViewController] motionEnded: UIEventSubtypeMotionShake
1584 } else { // Not launching another, animate our return to the list.
1585 [UIView animateWithDuration: 0.5
1586 animations:^{ fader.alpha = 0.0; }
1587 completion:^(BOOL finished) {
1588 [fader removeFromSuperview];
1595 /* Called after the device's orientation has changed.
1597 Note: we could include a subclass of UIViewController which
1598 contains a shouldAutorotateToInterfaceOrientation method that
1599 returns YES, in which case Core Animation would auto-rotate our
1600 View for us in response to rotation events... but, that interacts
1601 badly with the EAGLContext -- if you introduce Core Animation into
1602 the path, the OpenGL pipeline probably falls back on software
1603 rendering and performance goes to hell. Also, the scaling and
1604 rotation that Core Animation does interacts incorrectly with the GL
1607 So, we have to hack the rotation animation manually, in the GL world.
1609 Possibly XScreenSaverView should use Core Animation, and
1610 XScreenSaverGLView should override that.
1612 - (void)didRotate:(NSNotification *)notification
1614 UIDeviceOrientation current = [[UIDevice currentDevice] orientation];
1616 /* If the simulator starts up in the rotated position, sometimes
1617 the UIDevice says we're in Portrait when we're not -- but it
1618 turns out that the UINavigationController knows what's up!
1619 So get it from there.
1621 if (current == UIDeviceOrientationUnknown) {
1622 switch ([[[self window] rootViewController] interfaceOrientation]) {
1623 case UIInterfaceOrientationPortrait:
1624 current = UIDeviceOrientationPortrait;
1626 case UIInterfaceOrientationPortraitUpsideDown:
1627 current = UIDeviceOrientationPortraitUpsideDown;
1629 case UIInterfaceOrientationLandscapeLeft: // It's opposite day
1630 current = UIDeviceOrientationLandscapeRight;
1632 case UIInterfaceOrientationLandscapeRight:
1633 current = UIDeviceOrientationLandscapeLeft;
1640 /* On the iPad (but not iPhone 3GS, or the simulator) sometimes we get
1641 an orientation change event with an unknown orientation. Those seem
1642 to always be immediately followed by another orientation change with
1643 a *real* orientation change, so let's try just ignoring those bogus
1644 ones and hoping that the real one comes in shortly...
1646 if (current == UIDeviceOrientationUnknown)
1649 if (rotation_ratio >= 0) return; // in the midst of rotation animation
1650 if (orientation == current) return; // no change
1652 // When transitioning to FaceUp or FaceDown, pretend there was no change.
1653 if (current == UIDeviceOrientationFaceUp ||
1654 current == UIDeviceOrientationFaceDown)
1657 new_orientation = current; // current animation target
1658 rotation_ratio = 0; // start animating
1659 rot_start_time = double_time();
1661 switch (orientation) {
1662 case UIDeviceOrientationLandscapeLeft: angle_from = 90; break;
1663 case UIDeviceOrientationLandscapeRight: angle_from = 270; break;
1664 case UIDeviceOrientationPortraitUpsideDown: angle_from = 180; break;
1665 default: angle_from = 0; break;
1668 switch (new_orientation) {
1669 case UIDeviceOrientationLandscapeLeft: angle_to = 90; break;
1670 case UIDeviceOrientationLandscapeRight: angle_to = 270; break;
1671 case UIDeviceOrientationPortraitUpsideDown: angle_to = 180; break;
1672 default: angle_to = 0; break;
1675 switch (orientation) {
1676 case UIDeviceOrientationLandscapeRight: // from landscape
1677 case UIDeviceOrientationLandscapeLeft:
1678 rot_from.width = initial_bounds.height;
1679 rot_from.height = initial_bounds.width;
1681 default: // from portrait
1682 rot_from.width = initial_bounds.width;
1683 rot_from.height = initial_bounds.height;
1687 switch (new_orientation) {
1688 case UIDeviceOrientationLandscapeRight: // to landscape
1689 case UIDeviceOrientationLandscapeLeft:
1690 rot_to.width = initial_bounds.height;
1691 rot_to.height = initial_bounds.width;
1693 default: // to portrait
1694 rot_to.width = initial_bounds.width;
1695 rot_to.height = initial_bounds.height;
1700 // If we've done a rotation but the saver hasn't been initialized yet,
1701 // don't bother going through an X11 resize, but just do it now.
1702 rot_start_time = 0; // dawn of time
1703 [self hackRotation];
1708 /* I believe we can't use UIGestureRecognizer for tracking touches
1709 because UIPanGestureRecognizer doesn't give us enough detail in its
1712 Currently we don't handle multi-touches (just the first touch) but
1713 I'm leaving this comment here for future reference:
1715 In the simulator, multi-touch sequences look like this:
1717 touchesBegan [touchA, touchB]
1718 touchesEnd [touchA, touchB]
1720 But on real devices, sometimes you get that, but sometimes you get:
1722 touchesBegan [touchA, touchB]
1728 touchesBegan [touchA]
1729 touchesBegan [touchB]
1733 So the only way to properly detect a "pinch" gesture is to remember
1734 the start-point of each touch as it comes in; and the end-point of
1735 each touch as those come in; and only process the gesture once the
1736 number of touchEnds matches the number of touchBegins.
1739 - (void) rotateMouse:(int)rot x:(int*)x y:(int *)y w:(int)w h:(int)h
1741 // This is a no-op unless contentScaleFactor != hackedContentScaleFactor.
1742 // Currently, this is the iPad Retina only.
1743 CGRect frame = [self bounds]; // Scale.
1744 double s = [self hackedContentScaleFactor];
1745 *x *= (backbuffer_size.width / frame.size.width) / s;
1746 *y *= (backbuffer_size.height / frame.size.height) / s;
1750 #if 0 // AudioToolbox/AudioToolbox.h
1753 // There's no way to play a standard system alert sound!
1754 // We'd have to include our own WAV for that. Eh, fuck it.
1755 AudioServicesPlaySystemSound (kSystemSoundID_Vibrate);
1756 # if TARGET_IPHONE_SIMULATOR
1757 NSLog(@"BEEP"); // The sim doesn't vibrate.
1763 /* We distinguish between taps and drags.
1764 - Drags (down, motion, up) are sent to the saver to handle.
1765 - Single-taps exit the saver.
1766 This means a saver cannot respond to a single-tap. Only a few try to.
1769 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
1771 // If they are trying to pinch, just do nothing.
1772 if ([[event allTouches] count] > 1)
1777 if (xsft->event_cb && xwindow) {
1778 double s = [self hackedContentScaleFactor];
1780 memset (&xe, 0, sizeof(xe));
1782 // #### 'frame' here or 'bounds'?
1783 int w = s * [self frame].size.width;
1784 int h = s * [self frame].size.height;
1785 for (UITouch *touch in touches) {
1786 CGPoint p = [touch locationInView:self];
1787 xe.xany.type = ButtonPress;
1788 xe.xbutton.button = i + 1;
1789 xe.xbutton.button = i + 1;
1790 xe.xbutton.x = s * p.x;
1791 xe.xbutton.y = s * p.y;
1792 [self rotateMouse: rot_current_angle
1793 x: &xe.xbutton.x y: &xe.xbutton.y w: w h: h];
1794 jwxyz_mouse_moved (xdpy, xwindow, xe.xbutton.x, xe.xbutton.y);
1796 // Ignore return code: don't care whether the hack handled it.
1797 xsft->event_cb (xdpy, xwindow, xdata, &xe);
1799 // Remember when/where this was, to determine tap versus drag or hold.
1800 tap_time = double_time();
1804 break; // No pinches: only look at the first touch.
1810 - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
1812 // If they are trying to pinch, just do nothing.
1813 if ([[event allTouches] count] > 1)
1816 if (xsft->event_cb && xwindow) {
1817 double s = [self hackedContentScaleFactor];
1819 memset (&xe, 0, sizeof(xe));
1821 // #### 'frame' here or 'bounds'?
1822 int w = s * [self frame].size.width;
1823 int h = s * [self frame].size.height;
1824 for (UITouch *touch in touches) {
1825 CGPoint p = [touch locationInView:self];
1827 // If the ButtonRelease came less than half a second after ButtonPress,
1828 // and didn't move far, then this was a tap, not a drag or a hold.
1829 // Interpret it as "exit".
1831 double dist = sqrt (((p.x - tap_point.x) * (p.x - tap_point.x)) +
1832 ((p.y - tap_point.y) * (p.y - tap_point.y)));
1833 if (tap_time + 0.5 >= double_time() && dist < 20) {
1834 [self stopAndClose:NO];
1838 xe.xany.type = ButtonRelease;
1839 xe.xbutton.button = i + 1;
1840 xe.xbutton.x = s * p.x;
1841 xe.xbutton.y = s * p.y;
1842 [self rotateMouse: rot_current_angle
1843 x: &xe.xbutton.x y: &xe.xbutton.y w: w h: h];
1844 jwxyz_mouse_moved (xdpy, xwindow, xe.xbutton.x, xe.xbutton.y);
1845 xsft->event_cb (xdpy, xwindow, xdata, &xe);
1847 break; // No pinches: only look at the first touch.
1853 - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
1855 // If they are trying to pinch, just do nothing.
1856 if ([[event allTouches] count] > 1)
1859 if (xsft->event_cb && xwindow) {
1860 double s = [self hackedContentScaleFactor];
1862 memset (&xe, 0, sizeof(xe));
1864 // #### 'frame' here or 'bounds'?
1865 int w = s * [self frame].size.width;
1866 int h = s * [self frame].size.height;
1867 for (UITouch *touch in touches) {
1868 CGPoint p = [touch locationInView:self];
1869 xe.xany.type = MotionNotify;
1870 xe.xmotion.x = s * p.x;
1871 xe.xmotion.y = s * p.y;
1872 [self rotateMouse: rot_current_angle
1873 x: &xe.xbutton.x y: &xe.xbutton.y w: w h: h];
1874 jwxyz_mouse_moved (xdpy, xwindow, xe.xmotion.x, xe.xmotion.y);
1875 xsft->event_cb (xdpy, xwindow, xdata, &xe);
1877 break; // No pinches: only look at the first touch.
1883 /* We need this to respond to "shake" gestures
1885 - (BOOL)canBecomeFirstResponder
1890 - (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
1895 - (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event
1899 /* Shake means exit and launch a new saver.
1901 - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
1903 [self stopAndClose:YES];
1907 - (void)setScreenLocked:(BOOL)locked
1909 if (screenLocked == locked) return;
1910 screenLocked = locked;
1912 if ([self isAnimating])
1913 [self stopAnimation];
1915 if (! [self isAnimating])
1916 [self startAnimation];
1920 #endif // USE_IPHONE
1923 - (void) checkForUpdates
1926 // We only check once at startup, even if there are multiple screens,
1927 // and even if this saver is running for many days.
1928 // (Uh, except this doesn't work because this static isn't shared,
1929 // even if we make it an exported global. Not sure why. Oh well.)
1930 static BOOL checked_p = NO;
1931 if (checked_p) return;
1933 if (! get_boolean_resource (xdpy,
1934 SUSUEnableAutomaticChecksKey,
1935 SUSUEnableAutomaticChecksKey))
1936 return; // If it's off, don't bother running the updater.
1938 // Otherwise, the updater will decide if it's time to hit the network.
1939 NSString *updater = @"XScreenSaverUpdater";
1940 if (! [[NSWorkspace sharedWorkspace]
1941 launchApplication:updater showIcon:NO autolaunch:NO]) {
1942 NSLog(@"Unable to launch %@", updater);
1944 # endif // USE_IPHONE
1950 /* Utility functions...
1953 static PrefsReader *
1954 get_prefsReader (Display *dpy)
1956 XScreenSaverView *view = jwxyz_window_view (XRootWindow (dpy, 0));
1957 if (!view) return 0;
1958 return [view prefsReader];
1963 get_string_resource (Display *dpy, char *name, char *class)
1965 return [get_prefsReader(dpy) getStringResource:name];
1969 get_boolean_resource (Display *dpy, char *name, char *class)
1971 return [get_prefsReader(dpy) getBooleanResource:name];
1975 get_integer_resource (Display *dpy, char *name, char *class)
1977 return [get_prefsReader(dpy) getIntegerResource:name];
1981 get_float_resource (Display *dpy, char *name, char *class)
1983 return [get_prefsReader(dpy) getFloatResource:name];