1 /* xscreensaver, Copyright (c) 2006-2014 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(BACKBUFFER_CALAYER)
430 [self setLayer: [CALayer layer]];
431 self.layer.delegate = self;
432 self.layer.opaque = YES;
433 [self setWantsLayer: YES];
434 # endif // !USE_IPHONE && BACKBUFFER_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");
448 NSAssert(!xdpy, @"xdpy not yet freed");
450 # ifdef USE_BACKBUFFER
452 CGContextRelease (backbuffer);
455 CGColorSpaceRelease (colorspace);
457 # ifdef BACKBUFFER_CGCONTEXT
459 CGContextRelease (window_ctx);
460 # endif // BACKBUFFER_CGCONTEXT
462 # endif // USE_BACKBUFFER
464 [prefsReader release];
472 - (PrefsReader *) prefsReader
479 - (void) lockFocus { }
480 - (void) unlockFocus { }
486 /* A few seconds after the saver launches, we store the "wasRunning"
487 preference. This is so that if the saver is crashing at startup,
488 we don't launch it again next time, getting stuck in a crash loop.
490 - (void) allSystemsGo: (NSTimer *) timer
492 NSAssert (timer == crash_timer, @"crash timer screwed up");
495 NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
496 [prefs setBool:YES forKey:@"wasRunning"];
502 - (void) startAnimation
504 NSAssert(![self isAnimating], @"already animating");
505 NSAssert(!initted_p && !xdata, @"already initialized");
507 // See comment in render_x11() for why this value is important:
508 [self setAnimationTimeInterval: 1.0 / 120.0];
510 [super startAnimation];
511 /* We can't draw on the window from this method, so we actually do the
512 initialization of the screen saver (xsft->init_cb) in the first call
513 to animateOneFrame() instead.
518 [crash_timer invalidate];
520 NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
521 [prefs removeObjectForKey:@"wasRunning"];
524 crash_timer = [NSTimer scheduledTimerWithTimeInterval: 5
526 selector:@selector(allSystemsGo:)
530 # endif // USE_IPHONE
532 // Never automatically turn the screen off if we are docked,
533 // and an animation is running.
536 [UIApplication sharedApplication].idleTimerDisabled =
537 ([UIDevice currentDevice].batteryState != UIDeviceBatteryStateUnplugged);
538 [[UIApplication sharedApplication]
539 setStatusBarHidden:YES withAnimation:UIStatusBarAnimationNone];
544 - (void)stopAnimation
546 NSAssert([self isAnimating], @"not animating");
550 [self lockFocus]; // in case something tries to draw from here
551 [self prepareContext];
553 /* I considered just not even calling the free callback at all...
554 But webcollage-cocoa needs it, to kill the inferior webcollage
555 processes (since the screen saver framework never generates a
556 SIGPIPE for them...) Instead, I turned off the free call in
557 xlockmore.c, which is where all of the bogus calls are anyway.
559 xsft->free_cb (xdpy, xwindow, xdata);
562 // xdpy must be freed before dealloc is called, because xdpy owns a
563 // circular reference to the parent XScreenSaverView.
564 jwxyz_free_display (xdpy);
568 // setup_p = NO; // #### wait, do we need this?
575 [crash_timer invalidate];
577 NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
578 [prefs removeObjectForKey:@"wasRunning"];
580 # endif // USE_IPHONE
582 [super stopAnimation];
584 // When an animation is no longer running (e.g., looking at the list)
585 // then it's ok to power off the screen when docked.
588 [UIApplication sharedApplication].idleTimerDisabled = NO;
589 [[UIApplication sharedApplication]
590 setStatusBarHidden:NO withAnimation:UIStatusBarAnimationNone];
595 /* Hook for the XScreenSaverGLView subclass
597 - (void) prepareContext
601 /* Hook for the XScreenSaverGLView subclass
603 - (void) resizeContext
609 screenhack_do_fps (Display *dpy, Window w, fps_state *fpst, void *closure)
611 fps_compute (fpst, 0, -1);
618 /* On iPhones with Retina displays, we can draw the savers in "real"
619 pixels, and that works great. The 320x480 "point" screen is really
620 a 640x960 *pixel* screen. However, Retina iPads have 768x1024
621 point screens which are 1536x2048 pixels, and apparently that's
622 enough pixels that copying those bits to the screen is slow. Like,
623 drops us from 15fps to 7fps. So, on Retina iPads, we don't draw in
624 real pixels. This will probably make the savers look better
625 anyway, since that's a higher resolution than most desktop monitors
626 have even today. (This is only true for X11 programs, not GL
627 programs. Those are fine at full rez.)
629 This method is overridden in XScreenSaverGLView, since this kludge
630 isn't necessary for GL programs, being resolution independent by
633 - (CGFloat) hackedContentScaleFactor
635 GLfloat s = [self contentScaleFactor];
636 if (initial_bounds.width >= 1024 ||
637 initial_bounds.height >= 1024)
643 static GLfloat _global_rot_current_angle_kludge;
645 double current_device_rotation (void)
647 return -_global_rot_current_angle_kludge;
651 - (void) hackRotation
653 if (rotation_ratio >= 0) { // in the midst of a rotation animation
655 # define CLAMP180(N) while (N < 0) N += 360; while (N > 180) N -= 360
656 GLfloat f = angle_from;
657 GLfloat t = angle_to;
660 GLfloat dist = -(t-f);
663 // Intermediate angle.
664 rot_current_angle = f - rotation_ratio * dist;
666 // Intermediate frame size.
667 rot_current_size.width = rot_from.width +
668 rotation_ratio * (rot_to.width - rot_from.width);
669 rot_current_size.height = rot_from.height +
670 rotation_ratio * (rot_to.height - rot_from.height);
672 // Tick animation. Complete rotation in 1/6th sec.
673 double now = double_time();
674 double duration = 1/6.0;
675 rotation_ratio = 1 - ((rot_start_time + duration - now) / duration);
677 if (rotation_ratio > 1) { // Done animating.
678 orientation = new_orientation;
679 rot_current_angle = angle_to;
680 rot_current_size = rot_to;
683 // Check orientation again in case we rotated again while rotating:
684 // this is a no-op if nothing has changed.
685 [self didRotate:nil];
687 } else { // Not animating a rotation.
688 rot_current_angle = angle_to;
689 rot_current_size = rot_to;
692 CLAMP180(rot_current_angle);
693 _global_rot_current_angle_kludge = rot_current_angle;
697 double s = [self hackedContentScaleFactor];
698 if (!ignore_rotation_p &&
699 /* rotation_ratio && */
700 ((int) backbuffer_size.width != (int) (s * rot_current_size.width) ||
701 (int) backbuffer_size.height != (int) (s * rot_current_size.height)))
706 - (void)alertView:(UIAlertView *)av clickedButtonAtIndex:(NSInteger)i
708 if (i == 0) exit (-1); // Cancel
709 [self stopAndClose:NO]; // Keep going
712 - (void) handleException: (NSException *)e
714 NSLog (@"Caught exception: %@", e);
715 [[[UIAlertView alloc] initWithTitle:
716 [NSString stringWithFormat: @"%s crashed!",
719 [NSString stringWithFormat:
720 @"The error message was:"
722 "If it keeps crashing, try "
723 "resetting its options.",
726 cancelButtonTitle: @"Exit"
727 otherButtonTitles: @"Keep going", nil]
729 [self stopAnimation];
735 #ifdef USE_BACKBUFFER
737 /* Create a bitmap context into which we render everything.
738 If the desired size has changed, re-created it.
740 - (void) createBackbuffer
743 double s = [self hackedContentScaleFactor];
744 CGSize rotsize = ignore_rotation_p ? initial_bounds : rot_current_size;
745 int new_w = s * rotsize.width;
746 int new_h = s * rotsize.height;
748 int new_w = [self bounds].size.width;
749 int new_h = [self bounds].size.height;
752 // Colorspaces and CGContexts only happen with non-GL hacks.
754 CGColorSpaceRelease (colorspace);
755 # ifdef BACKBUFFER_CGCONTEXT
757 CGContextRelease (window_ctx);
760 NSWindow *window = [self window];
762 if (window && xdpy) {
765 # if defined(BACKBUFFER_CGCONTEXT)
766 // TODO: This was borrowed from jwxyz_window_resized, and should
767 // probably be refactored.
769 // Figure out which screen the window is currently on.
770 CGDirectDisplayID cgdpy = 0;
774 // TODO: XTranslateCoordinates is returning (0,1200) on my system.
776 // In any case, those weren't valid coordinates for CGGetDisplaysWithPoint.
777 // XTranslateCoordinates (xdpy, xwindow, NULL, 0, 0, &wx, &wy, NULL);
782 p0 = [window convertBaseToScreen:p0];
783 CGPoint p = {p0.x, p0.y};
785 CGGetDisplaysWithPoint (p, 1, &cgdpy, &n);
786 NSAssert (cgdpy, @"unable to find CGDisplay");
790 // Figure out this screen's colorspace, and use that for every CGImage.
792 CMProfileRef profile = 0;
794 // CMGetProfileByAVID is deprecated as of OS X 10.6, but there's no
795 // documented replacement as of OS X 10.9.
796 // http://lists.apple.com/archives/colorsync-dev/2012/Nov/msg00001.html
797 CMGetProfileByAVID ((CMDisplayIDType) cgdpy, &profile);
798 NSAssert (profile, @"unable to find colorspace profile");
799 colorspace = CGColorSpaceCreateWithPlatformColorSpace (profile);
800 NSAssert (colorspace, @"unable to find colorspace");
802 # elif defined(BACKBUFFER_CALAYER)
803 // Was apparently faster until 10.9.
804 colorspace = CGColorSpaceCreateDeviceRGB ();
805 # endif // BACKBUFFER_CALAYER
807 # ifdef BACKBUFFER_CGCONTEXT
808 window_ctx = [[window graphicsContext] graphicsPort];
809 CGContextRetain (window_ctx);
810 # endif // BACKBUFFER_CGCONTEXT
814 # ifdef BACKBUFFER_CGCONTEXT
816 # endif // BACKBUFFER_CGCONTEXT
817 colorspace = CGColorSpaceCreateDeviceRGB();
821 backbuffer_size.width == new_w &&
822 backbuffer_size.height == new_h)
825 CGSize osize = backbuffer_size;
826 CGContextRef ob = backbuffer;
828 backbuffer_size.width = new_w;
829 backbuffer_size.height = new_h;
831 backbuffer = CGBitmapContextCreate (NULL,
832 backbuffer_size.width,
833 backbuffer_size.height,
835 backbuffer_size.width * 4,
837 // kCGImageAlphaPremultipliedLast
838 (kCGImageAlphaNoneSkipFirst |
839 kCGBitmapByteOrder32Host)
841 NSAssert (backbuffer, @"unable to allocate back buffer");
845 r.origin.x = r.origin.y = 0;
846 r.size = backbuffer_size;
847 CGContextSetGrayFillColor (backbuffer, 0, 1);
848 CGContextFillRect (backbuffer, r);
851 // Restore old bits, as much as possible, to the X11 upper left origin.
854 rect.origin.y = (backbuffer_size.height - osize.height);
856 CGImageRef img = CGBitmapContextCreateImage (ob);
857 CGContextDrawImage (backbuffer, rect, img);
858 CGImageRelease (img);
859 CGContextRelease (ob);
863 #endif // USE_BACKBUFFER
866 /* Inform X11 that the size of our window has changed.
870 if (!xwindow) return; // early
872 # ifdef USE_BACKBUFFER
873 [self createBackbuffer];
874 jwxyz_window_resized (xdpy, xwindow,
876 backbuffer_size.width, backbuffer_size.height,
878 # else // !USE_BACKBUFFER
879 NSRect r = [self frame]; // ignoring rotation is closer
880 r.size = [self bounds].size; // to what XGetGeometry expects.
881 jwxyz_window_resized (xdpy, xwindow,
882 r.origin.x, r.origin.y,
883 r.size.width, r.size.height,
885 # endif // !USE_BACKBUFFER
887 // Next time render_x11 is called, run the saver's reshape_cb.
897 if (orientation == UIDeviceOrientationUnknown)
898 [self didRotate:nil];
905 # ifdef USE_BACKBUFFER
906 NSAssert (backbuffer, @"no back buffer");
907 xdpy = jwxyz_make_display (self, backbuffer);
909 xdpy = jwxyz_make_display (self, 0);
911 xwindow = XRootWindow (xdpy, 0);
914 /* Some X11 hacks (fluidballs) want to ignore all rotation events. */
916 get_boolean_resource (xdpy, "ignoreRotation", "IgnoreRotation");
917 # endif // USE_IPHONE
925 xsft->setup_cb (xsft, xsft->setup_arg);
929 NSAssert(!xdata, @"xdata already initialized");
935 XSetWindowBackground (xdpy, xwindow,
936 get_pixel_resource (xdpy, 0,
937 "background", "Background"));
938 XClearWindow (xdpy, xwindow);
941 [[self window] setAcceptsMouseMovedEvents:YES];
944 /* In MacOS 10.5, this enables "QuartzGL", meaning that the Quartz
945 drawing primitives will run on the GPU instead of the CPU.
946 It seems like it might make things worse rather than better,
947 though... Plus it makes us binary-incompatible with 10.4.
949 # if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
950 [[self window] setPreferredBackingLocation:
951 NSWindowBackingLocationVideoMemory];
955 /* Kludge: even though the init_cb functions are declared to take 2 args,
956 actually call them with 3, for the benefit of xlockmore_init() and
959 void *(*init_cb) (Display *, Window, void *) =
960 (void *(*) (Display *, Window, void *)) xsft->init_cb;
962 xdata = init_cb (xdpy, xwindow, xsft->setup_arg);
963 // NSAssert(xdata, @"no xdata from init");
964 if (! xdata) abort();
966 if (get_boolean_resource (xdpy, "doFPS", "DoFPS")) {
967 fpst = fps_init (xdpy, xwindow);
968 if (! xsft->fps_cb) xsft->fps_cb = screenhack_do_fps;
973 [self checkForUpdates];
977 /* I don't understand why we have to do this *every frame*, but we do,
978 or else the cursor comes back on.
981 if (![self isPreview])
982 [NSCursor setHiddenUntilMouseMoves:YES];
988 /* This is just a guess, but the -fps code wants to know how long
989 we were sleeping between frames.
991 long usecs = 1000000 * [self animationTimeInterval];
992 usecs -= 200; // caller apparently sleeps for slightly less sometimes...
993 if (usecs < 0) usecs = 0;
994 fps_slept (fpst, usecs);
998 /* It turns out that on some systems (possibly only 10.5 and older?)
999 [ScreenSaverView setAnimationTimeInterval] does nothing. This means
1000 that we cannot rely on it.
1002 Some of the screen hacks want to delay for long periods, and letting the
1003 framework run the update function at 30 FPS when it really wanted half a
1004 minute between frames would be bad. So instead, we assume that the
1005 framework's animation timer might fire whenever, but we only invoke the
1006 screen hack's "draw frame" method when enough time has expired.
1008 This means two extra calls to gettimeofday() per frame. For fast-cycling
1009 screen savers, that might actually slow them down. Oh well.
1011 A side-effect of this is that it's not possible for a saver to request
1012 an animation interval that is faster than animationTimeInterval.
1014 HOWEVER! On modern systems where setAnimationTimeInterval is *not*
1015 ignored, it's important that it be faster than 30 FPS. 120 FPS is good.
1017 An NSTimer won't fire if the timer is already running the invocation
1018 function from a previous firing. So, if we use a 30 FPS
1019 animationTimeInterval (33333 µs) and a screenhack takes 40000 µs for a
1020 frame, there will be a 26666 µs delay until the next frame, 66666 µs
1021 after the beginning of the current frame. In other words, 25 FPS
1024 Frame rates tend to snap to values of 30/N, where N is a positive
1025 integer, i.e. 30 FPS, 15 FPS, 10, 7.5, 6. And the 'snapped' frame rate
1026 is rounded down from what it would normally be.
1028 So if we set animationTimeInterval to 1/120 instead of 1/30, frame rates
1029 become values of 60/N, 120/N, or 240/N, with coarser or finer frame rate
1030 steps for higher or lower animation time intervals respectively.
1033 gettimeofday (&tv, 0);
1034 double now = tv.tv_sec + (tv.tv_usec / 1000000.0);
1035 if (now < next_frame_time) return;
1037 [self prepareContext];
1040 // We do this here instead of in setFrame so that all the
1041 // Xlib drawing takes place under the animation timer.
1042 [self resizeContext];
1044 # ifndef USE_BACKBUFFER
1046 # else // USE_BACKBUFFER
1049 r.size.width = backbuffer_size.width;
1050 r.size.height = backbuffer_size.height;
1051 # endif // USE_BACKBUFFER
1053 xsft->reshape_cb (xdpy, xwindow, xdata, r.size.width, r.size.height);
1057 // Run any XtAppAddInput callbacks now.
1058 // (Note that XtAppAddTimeOut callbacks have already been run by
1059 // the Cocoa event loop.)
1061 jwxyz_sources_run (display_sources_data (xdpy));
1067 NSDisableScreenUpdates();
1069 // NSAssert(xdata, @"no xdata when drawing");
1070 if (! xdata) abort();
1071 unsigned long delay = xsft->draw_cb (xdpy, xwindow, xdata);
1072 if (fpst) xsft->fps_cb (xdpy, xwindow, fpst, xdata);
1074 NSEnableScreenUpdates();
1077 gettimeofday (&tv, 0);
1078 now = tv.tv_sec + (tv.tv_usec / 1000000.0);
1079 next_frame_time = now + (delay / 1000000.0);
1081 # ifdef USE_IPHONE // Allow savers on the iPhone to run full-tilt.
1082 if (delay < [self animationTimeInterval])
1083 [self setAnimationTimeInterval:(delay / 1000000.0)];
1086 # ifdef DO_GC_HACKERY
1087 /* Current theory is that the 10.6 garbage collector sucks in the
1090 It only does a collection when a threshold of outstanding
1091 collectable allocations has been surpassed. However, CoreGraphics
1092 creates lots of small collectable allocations that contain pointers
1093 to very large non-collectable allocations: a small CG object that's
1094 collectable referencing large malloc'd allocations (non-collectable)
1095 containing bitmap data. So the large allocation doesn't get freed
1096 until GC collects the small allocation, which triggers its finalizer
1097 to run which frees the large allocation. So GC is deciding that it
1098 doesn't really need to run, even though the process has gotten
1099 enormous. GC eventually runs once pageouts have happened, but by
1100 then it's too late, and the machine's resident set has been
1103 So, we force an exhaustive garbage collection in this process
1104 approximately every 5 seconds whether the system thinks it needs
1108 static int tick = 0;
1109 if (++tick > 5*30) {
1111 objc_collect (OBJC_EXHAUSTIVE_COLLECTION);
1114 # endif // DO_GC_HACKERY
1118 @catch (NSException *e) {
1119 [self handleException: e];
1121 # endif // USE_IPHONE
1125 /* drawRect always does nothing, and animateOneFrame renders bits to the
1126 screen. This is (now) true of both X11 and GL on both MacOS and iOS.
1129 - (void)drawRect:(NSRect)rect
1131 if (xwindow) // clear to the X window's bg color, not necessarily black.
1132 XClearWindow (xdpy, xwindow);
1134 [super drawRect:rect]; // early: black.
1138 #ifndef USE_BACKBUFFER
1140 - (void) animateOneFrame
1143 jwxyz_flush_context(xdpy);
1146 #else // USE_BACKBUFFER
1148 - (void) animateOneFrame
1150 // Render X11 into the backing store bitmap...
1152 NSAssert (backbuffer, @"no back buffer");
1155 UIGraphicsPushContext (backbuffer);
1161 UIGraphicsPopContext();
1165 // Then compute the transformations for rotation.
1166 double hs = [self hackedContentScaleFactor];
1167 double s = [self contentScaleFactor];
1169 // The rotation origin for layer.affineTransform is in the center already.
1170 CGAffineTransform t = ignore_rotation_p ?
1171 CGAffineTransformIdentity :
1172 CGAffineTransformMakeRotation (rot_current_angle / (180.0 / M_PI));
1175 self.layer.affineTransform = CGAffineTransformScale(t, f, f);
1178 bounds.origin.x = 0;
1179 bounds.origin.y = 0;
1180 bounds.size.width = backbuffer_size.width / s;
1181 bounds.size.height = backbuffer_size.height / s;
1182 self.layer.bounds = bounds;
1183 # endif // USE_IPHONE
1185 # if defined(BACKBUFFER_CALAYER)
1186 [self.layer setNeedsDisplay];
1187 # elif defined(BACKBUFFER_CGCONTEXT)
1189 w = CGBitmapContextGetWidth (backbuffer),
1190 h = CGBitmapContextGetHeight (backbuffer);
1192 size_t bpl = CGBitmapContextGetBytesPerRow (backbuffer);
1193 CGDataProviderRef prov = CGDataProviderCreateWithData (NULL,
1194 CGBitmapContextGetData(backbuffer),
1199 CGImageRef img = CGImageCreate (w, h,
1201 CGBitmapContextGetBytesPerRow(backbuffer),
1203 CGBitmapContextGetBitmapInfo(backbuffer),
1205 kCGRenderingIntentDefault);
1207 CGDataProviderRelease (prov);
1212 rect.size = backbuffer_size;
1213 CGContextDrawImage (window_ctx, rect, img);
1215 CGImageRelease (img);
1217 CGContextFlush (window_ctx);
1218 # endif // BACKBUFFER_CGCONTEXT
1221 # ifdef BACKBUFFER_CALAYER
1223 - (void) drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
1225 // This "isn't safe" if NULL is passed to CGBitmapCreateContext before iOS 4.
1226 char *dest_data = (char *)CGBitmapContextGetData (ctx);
1228 // The CGContext here is normally upside-down on iOS.
1230 CGBitmapContextGetBitmapInfo (ctx) ==
1231 (kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host)
1233 && CGContextGetCTM (ctx).d < 0
1234 # endif // USE_IPHONE
1237 size_t dest_height = CGBitmapContextGetHeight (ctx);
1238 size_t dest_bpr = CGBitmapContextGetBytesPerRow (ctx);
1239 size_t src_height = CGBitmapContextGetHeight (backbuffer);
1240 size_t src_bpr = CGBitmapContextGetBytesPerRow (backbuffer);
1241 char *src_data = (char *)CGBitmapContextGetData (backbuffer);
1243 size_t height = src_height < dest_height ? src_height : dest_height;
1245 if (src_bpr == dest_bpr) {
1246 // iPad 1: 4.0 ms, iPad 2: 6.7 ms
1247 memcpy (dest_data, src_data, src_bpr * height);
1249 // iPad 1: 4.6 ms, iPad 2: 7.2 ms
1250 size_t bpr = src_bpr < dest_bpr ? src_bpr : dest_bpr;
1252 memcpy (dest_data, src_data, bpr);
1254 src_data += src_bpr;
1255 dest_data += dest_bpr;
1260 // iPad 1: 9.6 ms, iPad 2: 12.1 ms
1263 CGContextScaleCTM (ctx, 1, -1);
1264 CGFloat s = [self contentScaleFactor];
1265 CGFloat hs = [self hackedContentScaleFactor];
1266 CGContextTranslateCTM (ctx, 0, -backbuffer_size.height * hs / s);
1267 # endif // USE_IPHONE
1269 CGImageRef img = CGBitmapContextCreateImage (backbuffer);
1270 CGContextDrawImage (ctx, self.layer.bounds, img);
1271 CGImageRelease (img);
1274 # endif // BACKBUFFER_CALAYER
1276 #endif // USE_BACKBUFFER
1280 - (void) setFrame:(NSRect) newRect
1282 [super setFrame:newRect];
1284 if (xwindow) // inform Xlib that the window has changed now.
1289 # ifndef USE_IPHONE // Doesn't exist on iOS
1290 - (void) setFrameSize:(NSSize) newSize
1292 [super setFrameSize:newSize];
1296 # endif // !USE_IPHONE
1299 +(BOOL) performGammaFade
1304 - (BOOL) hasConfigureSheet
1309 + (NSString *) decompressXML: (NSData *)data
1311 if (! data) return 0;
1312 BOOL compressed_p = !!strncmp ((const char *) data.bytes, "<?xml", 5);
1314 // If it's not already XML, decompress it.
1315 NSAssert (compressed_p, @"xml isn't compressed");
1317 NSMutableData *data2 = 0;
1320 memset (&zs, 0, sizeof(zs));
1321 ret = inflateInit2 (&zs, 16 + MAX_WBITS);
1323 UInt32 usize = * (UInt32 *) (data.bytes + data.length - 4);
1324 data2 = [NSMutableData dataWithLength: usize];
1325 zs.next_in = (Bytef *) data.bytes;
1326 zs.avail_in = (uint) data.length;
1327 zs.next_out = (Bytef *) data2.bytes;
1328 zs.avail_out = (uint) data2.length;
1329 ret = inflate (&zs, Z_FINISH);
1332 if (ret == Z_OK || ret == Z_STREAM_END)
1335 NSAssert2 (0, @"gunzip error: %d: %s",
1336 ret, (zs.msg ? zs.msg : "<null>"));
1339 return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
1344 - (NSWindow *) configureSheet
1346 - (UIViewController *) configureView
1349 NSBundle *bundle = [NSBundle bundleForClass:[self class]];
1350 NSString *file = [NSString stringWithCString:xsft->progclass
1351 encoding:NSISOLatin1StringEncoding];
1352 file = [file lowercaseString];
1353 NSString *path = [bundle pathForResource:file ofType:@"xml"];
1355 NSLog (@"%@.xml does not exist in the application bundle: %@/",
1356 file, [bundle resourcePath]);
1361 UIViewController *sheet;
1362 # else // !USE_IPHONE
1364 # endif // !USE_IPHONE
1366 NSData *xmld = [NSData dataWithContentsOfFile:path];
1367 NSString *xml = [[self class] decompressXML: xmld];
1368 sheet = [[XScreenSaverConfigSheet alloc]
1369 initWithXML:[xml dataUsingEncoding:NSUTF8StringEncoding]
1370 options:xsft->options
1371 controller:[prefsReader userDefaultsController]
1372 globalController:[prefsReader globalDefaultsController]
1373 defaults:[prefsReader defaultOptions]];
1375 // #### am I expected to retain this, or not? wtf.
1376 // I thought not, but if I don't do this, we (sometimes) crash.
1377 // #### Analyze says "potential leak of an object stored into sheet"
1384 - (NSUserDefaultsController *) userDefaultsController
1386 return [prefsReader userDefaultsController];
1390 /* Announce our willingness to accept keyboard input.
1392 - (BOOL)acceptsFirstResponder
1400 /* Convert an NSEvent into an XEvent, and pass it along.
1401 Returns YES if it was handled.
1403 - (BOOL) doEvent: (NSEvent *) e
1406 if (![self isPreview] || // no event handling if actually screen-saving!
1407 ![self isAnimating] ||
1412 memset (&xe, 0, sizeof(xe));
1416 int flags = [e modifierFlags];
1417 if (flags & NSAlphaShiftKeyMask) state |= LockMask;
1418 if (flags & NSShiftKeyMask) state |= ShiftMask;
1419 if (flags & NSControlKeyMask) state |= ControlMask;
1420 if (flags & NSAlternateKeyMask) state |= Mod1Mask;
1421 if (flags & NSCommandKeyMask) state |= Mod2Mask;
1423 NSPoint p = [[[e window] contentView] convertPoint:[e locationInWindow]
1426 double s = [self hackedContentScaleFactor];
1431 int y = s * ([self bounds].size.height - p.y);
1433 xe.xany.type = type;
1439 xe.xbutton.state = state;
1440 if ([e type] == NSScrollWheel)
1441 xe.xbutton.button = ([e deltaY] > 0 ? Button4 :
1442 [e deltaY] < 0 ? Button5 :
1443 [e deltaX] > 0 ? Button6 :
1444 [e deltaX] < 0 ? Button7 :
1447 xe.xbutton.button = [e buttonNumber] + 1;
1452 xe.xmotion.state = state;
1457 NSString *ns = (([e type] == NSFlagsChanged) ? 0 :
1458 [e charactersIgnoringModifiers]);
1461 if (!ns || [ns length] == 0) // dead key
1463 // Cocoa hides the difference between left and right keys.
1464 // Also we only get KeyPress events for these, no KeyRelease
1465 // (unless we hack the mod state manually. Bleh.)
1467 if (flags & NSAlphaShiftKeyMask) k = XK_Caps_Lock;
1468 else if (flags & NSShiftKeyMask) k = XK_Shift_L;
1469 else if (flags & NSControlKeyMask) k = XK_Control_L;
1470 else if (flags & NSAlternateKeyMask) k = XK_Alt_L;
1471 else if (flags & NSCommandKeyMask) k = XK_Meta_L;
1473 else if ([ns length] == 1) // real key
1475 switch ([ns characterAtIndex:0]) {
1476 case NSLeftArrowFunctionKey: k = XK_Left; break;
1477 case NSRightArrowFunctionKey: k = XK_Right; break;
1478 case NSUpArrowFunctionKey: k = XK_Up; break;
1479 case NSDownArrowFunctionKey: k = XK_Down; break;
1480 case NSPageUpFunctionKey: k = XK_Page_Up; break;
1481 case NSPageDownFunctionKey: k = XK_Page_Down; break;
1482 case NSHomeFunctionKey: k = XK_Home; break;
1483 case NSPrevFunctionKey: k = XK_Prior; break;
1484 case NSNextFunctionKey: k = XK_Next; break;
1485 case NSBeginFunctionKey: k = XK_Begin; break;
1486 case NSEndFunctionKey: k = XK_End; break;
1490 [ns cStringUsingEncoding:NSISOLatin1StringEncoding];
1491 k = (s && *s ? *s : 0);
1497 if (! k) return YES; // E.g., "KeyRelease XK_Shift_L"
1499 xe.xkey.keycode = k;
1500 xe.xkey.state = state;
1504 NSAssert1 (0, @"unknown X11 event type: %d", type);
1509 [self prepareContext];
1510 BOOL result = xsft->event_cb (xdpy, xwindow, xdata, &xe);
1516 - (void) mouseDown: (NSEvent *) e
1518 if (! [self doEvent:e type:ButtonPress])
1519 [super mouseDown:e];
1522 - (void) mouseUp: (NSEvent *) e
1524 if (! [self doEvent:e type:ButtonRelease])
1528 - (void) otherMouseDown: (NSEvent *) e
1530 if (! [self doEvent:e type:ButtonPress])
1531 [super otherMouseDown:e];
1534 - (void) otherMouseUp: (NSEvent *) e
1536 if (! [self doEvent:e type:ButtonRelease])
1537 [super otherMouseUp:e];
1540 - (void) mouseMoved: (NSEvent *) e
1542 if (! [self doEvent:e type:MotionNotify])
1543 [super mouseMoved:e];
1546 - (void) mouseDragged: (NSEvent *) e
1548 if (! [self doEvent:e type:MotionNotify])
1549 [super mouseDragged:e];
1552 - (void) otherMouseDragged: (NSEvent *) e
1554 if (! [self doEvent:e type:MotionNotify])
1555 [super otherMouseDragged:e];
1558 - (void) scrollWheel: (NSEvent *) e
1560 if (! [self doEvent:e type:ButtonPress])
1561 [super scrollWheel:e];
1564 - (void) keyDown: (NSEvent *) e
1566 if (! [self doEvent:e type:KeyPress])
1570 - (void) keyUp: (NSEvent *) e
1572 if (! [self doEvent:e type:KeyRelease])
1576 - (void) flagsChanged: (NSEvent *) e
1578 if (! [self doEvent:e type:KeyPress])
1579 [super flagsChanged:e];
1585 - (void) stopAndClose:(Bool)relaunch_p
1587 if ([self isAnimating])
1588 [self stopAnimation];
1590 /* Need to make the SaverListController be the firstResponder again
1591 so that it can continue to receive its own shake events. I
1592 suppose that this abstraction-breakage means that I'm adding
1593 XScreenSaverView to the UINavigationController wrong...
1595 UIViewController *v = [[self window] rootViewController];
1596 if ([v isKindOfClass: [UINavigationController class]]) {
1597 UINavigationController *n = (UINavigationController *) v;
1598 [[n topViewController] becomeFirstResponder];
1601 UIView *fader = [self superview]; // the "backgroundView" view is our parent
1603 if (relaunch_p) { // Fake a shake on the SaverListController.
1604 // Why is [self window] sometimes null here?
1605 UIWindow *w = [[UIApplication sharedApplication] keyWindow];
1606 UIViewController *v = [w rootViewController];
1607 if ([v isKindOfClass: [UINavigationController class]]) {
1608 UINavigationController *n = (UINavigationController *) v;
1609 [[n topViewController] motionEnded: UIEventSubtypeMotionShake
1612 } else { // Not launching another, animate our return to the list.
1613 [UIView animateWithDuration: 0.5
1614 animations:^{ fader.alpha = 0.0; }
1615 completion:^(BOOL finished) {
1616 [fader removeFromSuperview];
1623 /* Called after the device's orientation has changed.
1625 Note: we could include a subclass of UIViewController which
1626 contains a shouldAutorotateToInterfaceOrientation method that
1627 returns YES, in which case Core Animation would auto-rotate our
1628 View for us in response to rotation events... but, that interacts
1629 badly with the EAGLContext -- if you introduce Core Animation into
1630 the path, the OpenGL pipeline probably falls back on software
1631 rendering and performance goes to hell. Also, the scaling and
1632 rotation that Core Animation does interacts incorrectly with the GL
1635 So, we have to hack the rotation animation manually, in the GL world.
1637 Possibly XScreenSaverView should use Core Animation, and
1638 XScreenSaverGLView should override that.
1640 - (void)didRotate:(NSNotification *)notification
1642 UIDeviceOrientation current = [[UIDevice currentDevice] orientation];
1644 /* If the simulator starts up in the rotated position, sometimes
1645 the UIDevice says we're in Portrait when we're not -- but it
1646 turns out that the UINavigationController knows what's up!
1647 So get it from there.
1649 if (current == UIDeviceOrientationUnknown) {
1650 switch ([[[self window] rootViewController] interfaceOrientation]) {
1651 case UIInterfaceOrientationPortrait:
1652 current = UIDeviceOrientationPortrait;
1654 case UIInterfaceOrientationPortraitUpsideDown:
1655 current = UIDeviceOrientationPortraitUpsideDown;
1657 case UIInterfaceOrientationLandscapeLeft: // It's opposite day
1658 current = UIDeviceOrientationLandscapeRight;
1660 case UIInterfaceOrientationLandscapeRight:
1661 current = UIDeviceOrientationLandscapeLeft;
1668 /* On the iPad (but not iPhone 3GS, or the simulator) sometimes we get
1669 an orientation change event with an unknown orientation. Those seem
1670 to always be immediately followed by another orientation change with
1671 a *real* orientation change, so let's try just ignoring those bogus
1672 ones and hoping that the real one comes in shortly...
1674 if (current == UIDeviceOrientationUnknown)
1677 if (rotation_ratio >= 0) return; // in the midst of rotation animation
1678 if (orientation == current) return; // no change
1680 // When transitioning to FaceUp or FaceDown, pretend there was no change.
1681 if (current == UIDeviceOrientationFaceUp ||
1682 current == UIDeviceOrientationFaceDown)
1685 new_orientation = current; // current animation target
1686 rotation_ratio = 0; // start animating
1687 rot_start_time = double_time();
1689 switch (orientation) {
1690 case UIDeviceOrientationLandscapeLeft: angle_from = 90; break;
1691 case UIDeviceOrientationLandscapeRight: angle_from = 270; break;
1692 case UIDeviceOrientationPortraitUpsideDown: angle_from = 180; break;
1693 default: angle_from = 0; break;
1696 switch (new_orientation) {
1697 case UIDeviceOrientationLandscapeLeft: angle_to = 90; break;
1698 case UIDeviceOrientationLandscapeRight: angle_to = 270; break;
1699 case UIDeviceOrientationPortraitUpsideDown: angle_to = 180; break;
1700 default: angle_to = 0; break;
1703 switch (orientation) {
1704 case UIDeviceOrientationLandscapeRight: // from landscape
1705 case UIDeviceOrientationLandscapeLeft:
1706 rot_from.width = initial_bounds.height;
1707 rot_from.height = initial_bounds.width;
1709 default: // from portrait
1710 rot_from.width = initial_bounds.width;
1711 rot_from.height = initial_bounds.height;
1715 switch (new_orientation) {
1716 case UIDeviceOrientationLandscapeRight: // to landscape
1717 case UIDeviceOrientationLandscapeLeft:
1718 rot_to.width = initial_bounds.height;
1719 rot_to.height = initial_bounds.width;
1721 default: // to portrait
1722 rot_to.width = initial_bounds.width;
1723 rot_to.height = initial_bounds.height;
1728 // If we've done a rotation but the saver hasn't been initialized yet,
1729 // don't bother going through an X11 resize, but just do it now.
1730 rot_start_time = 0; // dawn of time
1731 [self hackRotation];
1736 /* I believe we can't use UIGestureRecognizer for tracking touches
1737 because UIPanGestureRecognizer doesn't give us enough detail in its
1740 Currently we don't handle multi-touches (just the first touch) but
1741 I'm leaving this comment here for future reference:
1743 In the simulator, multi-touch sequences look like this:
1745 touchesBegan [touchA, touchB]
1746 touchesEnd [touchA, touchB]
1748 But on real devices, sometimes you get that, but sometimes you get:
1750 touchesBegan [touchA, touchB]
1756 touchesBegan [touchA]
1757 touchesBegan [touchB]
1761 So the only way to properly detect a "pinch" gesture is to remember
1762 the start-point of each touch as it comes in; and the end-point of
1763 each touch as those come in; and only process the gesture once the
1764 number of touchEnds matches the number of touchBegins.
1767 - (void) rotateMouse:(int)rot x:(int*)x y:(int *)y w:(int)w h:(int)h
1769 // This is a no-op unless contentScaleFactor != hackedContentScaleFactor.
1770 // Currently, this is the iPad Retina only.
1771 CGRect frame = [self bounds]; // Scale.
1772 double s = [self hackedContentScaleFactor];
1773 *x *= (backbuffer_size.width / frame.size.width) / s;
1774 *y *= (backbuffer_size.height / frame.size.height) / s;
1778 #if 0 // AudioToolbox/AudioToolbox.h
1781 // There's no way to play a standard system alert sound!
1782 // We'd have to include our own WAV for that. Eh, fuck it.
1783 AudioServicesPlaySystemSound (kSystemSoundID_Vibrate);
1784 # if TARGET_IPHONE_SIMULATOR
1785 NSLog(@"BEEP"); // The sim doesn't vibrate.
1791 /* We distinguish between taps and drags.
1792 - Drags (down, motion, up) are sent to the saver to handle.
1793 - Single-taps exit the saver.
1794 This means a saver cannot respond to a single-tap. Only a few try to.
1797 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
1799 // If they are trying to pinch, just do nothing.
1800 if ([[event allTouches] count] > 1)
1805 if (xsft->event_cb && xwindow) {
1806 double s = [self hackedContentScaleFactor];
1808 memset (&xe, 0, sizeof(xe));
1810 // #### 'frame' here or 'bounds'?
1811 int w = s * [self frame].size.width;
1812 int h = s * [self frame].size.height;
1813 for (UITouch *touch in touches) {
1814 CGPoint p = [touch locationInView:self];
1815 xe.xany.type = ButtonPress;
1816 xe.xbutton.button = i + 1;
1817 xe.xbutton.button = i + 1;
1818 xe.xbutton.x = s * p.x;
1819 xe.xbutton.y = s * p.y;
1820 [self rotateMouse: rot_current_angle
1821 x: &xe.xbutton.x y: &xe.xbutton.y w: w h: h];
1822 jwxyz_mouse_moved (xdpy, xwindow, xe.xbutton.x, xe.xbutton.y);
1824 // Ignore return code: don't care whether the hack handled it.
1825 xsft->event_cb (xdpy, xwindow, xdata, &xe);
1827 // Remember when/where this was, to determine tap versus drag or hold.
1828 tap_time = double_time();
1832 break; // No pinches: only look at the first touch.
1838 - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
1840 // If they are trying to pinch, just do nothing.
1841 if ([[event allTouches] count] > 1)
1844 if (xsft->event_cb && xwindow) {
1845 double s = [self hackedContentScaleFactor];
1847 memset (&xe, 0, sizeof(xe));
1849 // #### 'frame' here or 'bounds'?
1850 int w = s * [self frame].size.width;
1851 int h = s * [self frame].size.height;
1852 for (UITouch *touch in touches) {
1853 CGPoint p = [touch locationInView:self];
1855 // If the ButtonRelease came less than half a second after ButtonPress,
1856 // and didn't move far, then this was a tap, not a drag or a hold.
1857 // Interpret it as "exit".
1859 double dist = sqrt (((p.x - tap_point.x) * (p.x - tap_point.x)) +
1860 ((p.y - tap_point.y) * (p.y - tap_point.y)));
1861 if (tap_time + 0.5 >= double_time() && dist < 20) {
1862 [self stopAndClose:NO];
1866 xe.xany.type = ButtonRelease;
1867 xe.xbutton.button = i + 1;
1868 xe.xbutton.x = s * p.x;
1869 xe.xbutton.y = s * p.y;
1870 [self rotateMouse: rot_current_angle
1871 x: &xe.xbutton.x y: &xe.xbutton.y w: w h: h];
1872 jwxyz_mouse_moved (xdpy, xwindow, xe.xbutton.x, xe.xbutton.y);
1873 xsft->event_cb (xdpy, xwindow, xdata, &xe);
1875 break; // No pinches: only look at the first touch.
1881 - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
1883 // If they are trying to pinch, just do nothing.
1884 if ([[event allTouches] count] > 1)
1887 if (xsft->event_cb && xwindow) {
1888 double s = [self hackedContentScaleFactor];
1890 memset (&xe, 0, sizeof(xe));
1892 // #### 'frame' here or 'bounds'?
1893 int w = s * [self frame].size.width;
1894 int h = s * [self frame].size.height;
1895 for (UITouch *touch in touches) {
1896 CGPoint p = [touch locationInView:self];
1897 xe.xany.type = MotionNotify;
1898 xe.xmotion.x = s * p.x;
1899 xe.xmotion.y = s * p.y;
1900 [self rotateMouse: rot_current_angle
1901 x: &xe.xbutton.x y: &xe.xbutton.y w: w h: h];
1902 jwxyz_mouse_moved (xdpy, xwindow, xe.xmotion.x, xe.xmotion.y);
1903 xsft->event_cb (xdpy, xwindow, xdata, &xe);
1905 break; // No pinches: only look at the first touch.
1911 /* We need this to respond to "shake" gestures
1913 - (BOOL)canBecomeFirstResponder
1918 - (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
1923 - (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event
1927 /* Shake means exit and launch a new saver.
1929 - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
1931 [self stopAndClose:YES];
1935 - (void)setScreenLocked:(BOOL)locked
1937 if (screenLocked == locked) return;
1938 screenLocked = locked;
1940 if ([self isAnimating])
1941 [self stopAnimation];
1943 if (! [self isAnimating])
1944 [self startAnimation];
1948 #endif // USE_IPHONE
1951 - (void) checkForUpdates
1954 // We only check once at startup, even if there are multiple screens,
1955 // and even if this saver is running for many days.
1956 // (Uh, except this doesn't work because this static isn't shared,
1957 // even if we make it an exported global. Not sure why. Oh well.)
1958 static BOOL checked_p = NO;
1959 if (checked_p) return;
1962 // If it's off, don't bother running the updater. Otherwise, the
1963 // updater will decide if it's time to hit the network.
1964 if (! get_boolean_resource (xdpy,
1965 SUSUEnableAutomaticChecksKey,
1966 SUSUEnableAutomaticChecksKey))
1969 NSString *updater = @"XScreenSaverUpdater.app";
1971 // There may be multiple copies of the updater: e.g., one in /Applications
1972 // and one in the mounted installer DMG! It's important that we run the
1973 // one from the disk and not the DMG, so search for the right one.
1975 NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
1976 NSBundle *bundle = [NSBundle bundleForClass:[self class]];
1978 @[[[bundle bundlePath] stringByDeletingLastPathComponent],
1979 [@"~/Library/Screen Savers" stringByExpandingTildeInPath],
1980 @"/Library/Screen Savers",
1981 @"/System/Library/Screen Savers",
1983 @"/Applications/Utilities"];
1984 NSString *app_path = nil;
1985 for (NSString *dir in search) {
1986 NSString *p = [dir stringByAppendingPathComponent:updater];
1987 if ([[NSFileManager defaultManager] fileExistsAtPath:p]) {
1994 app_path = [workspace fullPathForApplication:updater];
1996 if (app_path && [app_path hasPrefix:@"/Volumes/XScreenSaver "])
1997 app_path = 0; // The DMG version will not do.
2000 NSLog(@"Unable to find %@", updater);
2005 if (! [workspace launchApplicationAtURL:[NSURL fileURLWithPath:app_path]
2006 options:(NSWorkspaceLaunchWithoutAddingToRecents |
2007 NSWorkspaceLaunchWithoutActivation |
2008 NSWorkspaceLaunchAndHide)
2011 NSLog(@"Unable to launch %@: %@", app_path, err);
2014 # endif // !USE_IPHONE
2020 /* Utility functions...
2023 static PrefsReader *
2024 get_prefsReader (Display *dpy)
2026 XScreenSaverView *view = jwxyz_window_view (XRootWindow (dpy, 0));
2027 if (!view) return 0;
2028 return [view prefsReader];
2033 get_string_resource (Display *dpy, char *name, char *class)
2035 return [get_prefsReader(dpy) getStringResource:name];
2039 get_boolean_resource (Display *dpy, char *name, char *class)
2041 return [get_prefsReader(dpy) getBooleanResource:name];
2045 get_integer_resource (Display *dpy, char *name, char *class)
2047 return [get_prefsReader(dpy) getIntegerResource:name];
2051 get_float_resource (Display *dpy, char *name, char *class)
2053 return [get_prefsReader(dpy) getFloatResource:name];