1 /* xscreensaver, Copyright (c) 2006-2012 Jamie Zawinski <jwz@jwz.org>
3 * Permission to use, copy, modify, distribute, and sell this software and its
4 * documentation for any purpose is hereby granted without fee, provided that
5 * the above copyright notice appear in all copies and that both that
6 * copyright notice and this permission notice appear in supporting
7 * documentation. No representations are made about the suitability of this
8 * software for any purpose. It is provided "as is" without express or
12 /* This 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>
19 #import "XScreenSaverView.h"
20 #import "XScreenSaverConfigSheet.h"
21 #import "screenhackI.h"
22 #import "xlockmoreI.h"
23 #import "jwxyz-timers.h"
25 /* Garbage collection only exists if we are being compiled against the
26 10.6 SDK or newer, not if we are building against the 10.4 SDK.
28 #ifndef MAC_OS_X_VERSION_10_6
29 # define MAC_OS_X_VERSION_10_6 1060 /* undefined in 10.4 SDK, grr */
31 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 /* 10.6 SDK */
32 # import <objc/objc-auto.h>
33 # define DO_GC_HACKERY
36 extern struct xscreensaver_function_table *xscreensaver_function_table;
38 /* Global variables used by the screen savers
41 const char *progclass;
47 /* Stub definition of the superclass, for iPhone.
49 @implementation ScreenSaverView
51 NSTimeInterval anim_interval;
56 - (id)initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview {
57 self = [super initWithFrame:frame];
59 anim_interval = 1.0/30;
62 - (NSTimeInterval)animationTimeInterval { return anim_interval; }
63 - (void)setAnimationTimeInterval:(NSTimeInterval)i { anim_interval = i; }
64 - (BOOL)hasConfigureSheet { return NO; }
65 - (NSWindow *)configureSheet { return nil; }
66 - (NSView *)configureView { return nil; }
67 - (BOOL)isPreview { return NO; }
68 - (BOOL)isAnimating { return animating_p; }
69 - (void)animateOneFrame { }
71 - (void)startAnimation {
72 if (animating_p) return;
74 anim_timer = [NSTimer scheduledTimerWithTimeInterval: anim_interval
76 selector:@selector(animateOneFrame)
81 - (void)stopAnimation {
83 [anim_timer invalidate];
90 # endif // !USE_IPHONE
94 @implementation XScreenSaverView
96 // Given a lower-cased saver name, returns the function table for it.
97 // If no name, guess the name from the class's bundle name.
99 - (struct xscreensaver_function_table *) findFunctionTable:(NSString *)name
101 NSBundle *nsb = [NSBundle bundleForClass:[self class]];
102 NSAssert1 (nsb, @"no bundle for class %@", [self class]);
104 NSString *path = [nsb bundlePath];
105 CFURLRef url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
107 kCFURLPOSIXPathStyle,
109 CFBundleRef cfb = CFBundleCreate (kCFAllocatorDefault, url);
111 NSAssert1 (cfb, @"no CFBundle for \"%@\"", path);
114 name = [[path lastPathComponent] stringByDeletingPathExtension];
116 NSString *table_name = [[name lowercaseString]
117 stringByAppendingString:
118 @"_xscreensaver_function_table"];
119 void *addr = CFBundleGetDataPointerForName (cfb, (CFStringRef) table_name);
123 NSLog (@"no symbol \"%@\" for \"%@\"", table_name, path);
125 return (struct xscreensaver_function_table *) addr;
129 // Add the "Contents/Resources/" subdirectory of this screen saver's .bundle
130 // to $PATH for the benefit of savers that include helper shell scripts.
132 - (void) setShellPath
134 NSBundle *nsb = [NSBundle bundleForClass:[self class]];
135 NSAssert1 (nsb, @"no bundle for class %@", [self class]);
137 NSString *nsdir = [nsb resourcePath];
138 NSAssert1 (nsdir, @"no resourcePath for class %@", [self class]);
139 const char *dir = [nsdir cStringUsingEncoding:NSUTF8StringEncoding];
140 const char *opath = getenv ("PATH");
141 if (!opath) opath = "/bin"; // $PATH is unset when running under Shark!
142 char *npath = (char *) malloc (strlen (opath) + strlen (dir) + 30);
143 strcpy (npath, "PATH=");
146 strcat (npath, opath);
147 if (putenv (npath)) {
152 /* Don't free (npath) -- MacOS's putenv() does not copy it. */
156 // set an $XSCREENSAVER_CLASSPATH variable so that included shell scripts
157 // (e.g., "xscreensaver-text") know how to look up resources.
159 - (void) setResourcesEnv:(NSString *) name
161 NSBundle *nsb = [NSBundle bundleForClass:[self class]];
162 NSAssert1 (nsb, @"no bundle for class %@", [self class]);
164 const char *s = [name cStringUsingEncoding:NSUTF8StringEncoding];
165 char *env = (char *) malloc (strlen (s) + 40);
166 strcpy (env, "XSCREENSAVER_CLASSPATH=");
172 /* Don't free (env) -- MacOS's putenv() does not copy it. */
177 add_default_options (const XrmOptionDescRec *opts,
178 const char * const *defs,
179 XrmOptionDescRec **opts_ret,
180 const char ***defs_ret)
182 /* These aren't "real" command-line options (there are no actual command-line
183 options in the Cocoa version); but this is the somewhat kludgey way that
184 the <xscreensaver-text /> and <xscreensaver-image /> tags in the
185 ../hacks/config/\*.xml files communicate with the preferences database.
187 static const XrmOptionDescRec default_options [] = {
188 { "-text-mode", ".textMode", XrmoptionSepArg, 0 },
189 { "-text-literal", ".textLiteral", XrmoptionSepArg, 0 },
190 { "-text-file", ".textFile", XrmoptionSepArg, 0 },
191 { "-text-url", ".textURL", XrmoptionSepArg, 0 },
192 { "-text-program", ".textProgram", XrmoptionSepArg, 0 },
193 { "-grab-desktop", ".grabDesktopImages", XrmoptionNoArg, "True" },
194 { "-no-grab-desktop", ".grabDesktopImages", XrmoptionNoArg, "False"},
195 { "-choose-random-images", ".chooseRandomImages",XrmoptionNoArg, "True" },
196 { "-no-choose-random-images",".chooseRandomImages",XrmoptionNoArg, "False"},
197 { "-image-directory", ".imageDirectory", XrmoptionSepArg, 0 },
198 { "-fps", ".doFPS", XrmoptionNoArg, "True" },
199 { "-no-fps", ".doFPS", XrmoptionNoArg, "False"},
202 static const char *default_defaults [] = {
204 ".doubleBuffer: True",
205 ".multiSample: False",
213 ".textURL: http://twitter.com/statuses/public_timeline.atom",
215 ".grabDesktopImages: yes",
216 ".chooseRandomImages: no",
217 ".imageDirectory: ~/Pictures",
223 for (i = 0; default_options[i].option; i++)
225 for (i = 0; opts[i].option; i++)
228 XrmOptionDescRec *opts2 = (XrmOptionDescRec *)
229 calloc (count + 1, sizeof (*opts2));
233 while (default_options[j].option) {
234 opts2[i] = default_options[j];
238 while (opts[j].option) {
249 for (i = 0; default_defaults[i]; i++)
251 for (i = 0; defs[i]; i++)
254 const char **defs2 = (const char **) calloc (count + 1, sizeof (*defs2));
258 while (default_defaults[j]) {
259 defs2[i] = default_defaults[j];
273 /* Returns the current time in seconds as a double.
279 # ifdef GETTIMEOFDAY_TWO_ARGS
281 gettimeofday(&now, &tzp);
286 return (now.tv_sec + ((double) now.tv_usec * 0.000001));
291 - (id) initWithFrame:(NSRect)frame
292 saverName:(NSString *)saverName
293 isPreview:(BOOL)isPreview
296 rot_current_size = frame.size; // needs to be early, because
297 rot_from = rot_current_size; // [self setFrame] is called by
298 rot_to = rot_current_size; // [super initWithFrame].
302 if (! (self = [super initWithFrame:frame isPreview:isPreview]))
305 xsft = [self findFunctionTable: saverName];
314 [self setMultipleTouchEnabled:YES];
315 orientation = UIDeviceOrientationUnknown;
316 [self didRotate:nil];
317 # endif // USE_IPHONE
321 xsft->setup_cb (xsft, xsft->setup_arg);
323 /* The plist files for these preferences show up in
324 $HOME/Library/Preferences/ByHost/ in a file named like
325 "org.jwz.xscreensaver.<SAVERNAME>.<NUMBERS>.plist"
327 NSString *name = [NSString stringWithCString:xsft->progclass
328 encoding:NSUTF8StringEncoding];
329 name = [@"org.jwz.xscreensaver." stringByAppendingString:name];
330 [self setResourcesEnv:name];
333 XrmOptionDescRec *opts = 0;
334 const char **defs = 0;
335 add_default_options (xsft->options, xsft->defaults, &opts, &defs);
336 prefsReader = [[PrefsReader alloc]
337 initWithName:name xrmKeys:opts defaults:defs];
339 // free (opts); // bah, we need these! #### leak!
340 xsft->options = opts;
342 progname = progclass = xsft->progclass;
347 [self createBackbuffer];
349 // So we can tell when we're docked.
350 [UIDevice currentDevice].batteryMonitoringEnabled = YES;
351 # endif // USE_IPHONE
356 - (id) initWithFrame:(NSRect)frame isPreview:(BOOL)p
358 return [self initWithFrame:frame saverName:0 isPreview:p];
364 NSAssert(![self isAnimating], @"still animating");
365 NSAssert(!xdata, @"xdata not yet freed");
367 jwxyz_free_display (xdpy);
371 CGContextRelease (backbuffer);
374 [prefsReader release];
383 - (PrefsReader *) prefsReader
390 - (void) lockFocus { }
391 - (void) unlockFocus { }
397 /* A few seconds after the saver launches, we store the "wasRunning"
398 preference. This is so that if the saver is crashing at startup,
399 we don't launch it again next time, getting stuck in a crash loop.
401 - (void) allSystemsGo: (NSTimer *) timer
403 NSAssert (timer == crash_timer, @"crash timer screwed up");
406 NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
407 [prefs setBool:YES forKey:@"wasRunning"];
413 - (void) startAnimation
415 NSAssert(![self isAnimating], @"already animating");
416 NSAssert(!initted_p && !xdata, @"already initialized");
417 [super startAnimation];
418 /* We can't draw on the window from this method, so we actually do the
419 initialization of the screen saver (xsft->init_cb) in the first call
420 to animateOneFrame() instead.
425 [crash_timer invalidate];
427 NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
428 [prefs removeObjectForKey:@"wasRunning"];
431 crash_timer = [NSTimer scheduledTimerWithTimeInterval: 5
433 selector:@selector(allSystemsGo:)
436 # endif // USE_IPHONE
438 // Never automatically turn the screen off if we are docked,
439 // and an animation is running.
442 [UIApplication sharedApplication].idleTimerDisabled =
443 ([UIDevice currentDevice].batteryState != UIDeviceBatteryStateUnplugged);
448 - (void)stopAnimation
450 NSAssert([self isAnimating], @"not animating");
454 [self lockFocus]; // in case something tries to draw from here
455 [self prepareContext];
457 /* I considered just not even calling the free callback at all...
458 But webcollage-cocoa needs it, to kill the inferior webcollage
459 processes (since the screen saver framework never generates a
460 SIGPIPE for them...) Instead, I turned off the free call in
461 xlockmore.c, which is where all of the bogus calls are anyway.
463 xsft->free_cb (xdpy, xwindow, xdata);
466 // setup_p = NO; // #### wait, do we need this?
473 [crash_timer invalidate];
475 NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
476 [prefs removeObjectForKey:@"wasRunning"];
478 # endif // USE_IPHONE
480 [super stopAnimation];
482 // When an animation is no longer running (e.g., looking at the list)
483 // then it's ok to power off the screen when docked.
486 [UIApplication sharedApplication].idleTimerDisabled = NO;
491 /* Hook for the XScreenSaverGLView subclass
493 - (void) prepareContext
497 /* Hook for the XScreenSaverGLView subclass
499 - (void) resizeContext
505 screenhack_do_fps (Display *dpy, Window w, fps_state *fpst, void *closure)
507 fps_compute (fpst, 0, -1);
513 /* Create a bitmap context into which we render everything.
515 - (void) createBackbuffer
517 CGContextRef ob = backbuffer;
518 NSSize osize = backbuffer_size;
520 CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB();
521 double s = self.contentScaleFactor;
522 backbuffer_size.width = (int) (s * rot_current_size.width);
523 backbuffer_size.height = (int) (s * rot_current_size.height);
524 backbuffer = CGBitmapContextCreate (NULL,
525 backbuffer_size.width,
526 backbuffer_size.height,
528 backbuffer_size.width * 4,
530 kCGImageAlphaPremultipliedLast);
531 NSAssert (backbuffer, @"unable to allocate back buffer");
532 CGColorSpaceRelease (cs);
535 CGContextSetGrayFillColor (backbuffer, 0, 1);
536 CGRect r = CGRectZero;
537 r.size = backbuffer_size;
538 CGContextFillRect (backbuffer, r);
541 // Restore old bits, as much as possible, to the X11 upper left origin.
544 rect.origin.y = (backbuffer_size.height - osize.height);
546 CGImageRef img = CGBitmapContextCreateImage (ob);
547 CGContextDrawImage (backbuffer, rect, img);
548 CGImageRelease (img);
549 CGContextRelease (ob);
553 static GLfloat _global_rot_current_angle_kludge;
555 double current_device_rotation (void)
557 return -_global_rot_current_angle_kludge;
561 - (void) hackRotation
563 if (rotation_ratio >= 0) { // in the midst of a rotation animation
565 # define CLAMP180(N) while (N < 0) N += 360; while (N > 180) N -= 360
566 GLfloat f = angle_from;
567 GLfloat t = angle_to;
570 GLfloat dist = -(t-f);
573 // Intermediate angle.
574 rot_current_angle = f - rotation_ratio * dist;
576 // Intermediate frame size.
577 rot_current_size.width = rot_from.width +
578 rotation_ratio * (rot_to.width - rot_from.width);
579 rot_current_size.height = rot_from.height +
580 rotation_ratio * (rot_to.height - rot_from.height);
582 // Tick animation. Complete rotation in 1/6th sec.
583 double now = double_time();
584 double duration = 1/6.0;
585 rotation_ratio = 1 - ((rot_start_time + duration - now) / duration);
587 if (rotation_ratio > 1) { // Done animating.
588 orientation = new_orientation;
589 rot_current_angle = angle_to;
590 rot_current_size = rot_to;
593 // Check orientation again in case we rotated again while rotating:
594 // this is a no-op if nothing has changed.
595 [self didRotate:nil];
597 } else { // Not animating a rotation.
598 rot_current_angle = angle_to;
599 rot_current_size = rot_to;
602 CLAMP180(rot_current_angle);
603 _global_rot_current_angle_kludge = rot_current_angle;
607 double s = self.contentScaleFactor;
608 if (((int) backbuffer_size.width != (int) (s * rot_current_size.width) ||
609 (int) backbuffer_size.height != (int) (s * rot_current_size.height))
610 /* && rotation_ratio == -1*/)
611 [self setFrame:[self frame]];
620 if (orientation == UIDeviceOrientationUnknown)
621 [self didRotate:nil];
629 NSAssert (backbuffer, @"no back buffer");
630 xdpy = jwxyz_make_display (self, backbuffer);
632 xdpy = jwxyz_make_display (self, 0);
634 xwindow = XRootWindow (xdpy, 0);
637 jwxyz_window_resized (xdpy, xwindow,
639 backbuffer_size.width, backbuffer_size.height,
642 NSRect r = [self frame];
643 jwxyz_window_resized (xdpy, xwindow,
644 r.origin.x, r.origin.y,
645 r.size.width, r.size.height,
653 xsft->setup_cb (xsft, xsft->setup_arg);
657 NSAssert(!xdata, @"xdata already initialized");
662 XSetWindowBackground (xdpy, xwindow,
663 get_pixel_resource (xdpy, 0,
664 "background", "Background"));
665 XClearWindow (xdpy, xwindow);
668 [[self window] setAcceptsMouseMovedEvents:YES];
671 /* In MacOS 10.5, this enables "QuartzGL", meaning that the Quartz
672 drawing primitives will run on the GPU instead of the CPU.
673 It seems like it might make things worse rather than better,
674 though... Plus it makes us binary-incompatible with 10.4.
676 # if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
677 [[self window] setPreferredBackingLocation:
678 NSWindowBackingLocationVideoMemory];
682 /* Kludge: even though the init_cb functions are declared to take 2 args,
683 actually call them with 3, for the benefit of xlockmore_init() and
686 void *(*init_cb) (Display *, Window, void *) =
687 (void *(*) (Display *, Window, void *)) xsft->init_cb;
689 xdata = init_cb (xdpy, xwindow, xsft->setup_arg);
691 if (get_boolean_resource (xdpy, "doFPS", "DoFPS")) {
692 fpst = fps_init (xdpy, xwindow);
693 if (! xsft->fps_cb) xsft->fps_cb = screenhack_do_fps;
698 /* I don't understand why we have to do this *every frame*, but we do,
699 or else the cursor comes back on.
702 if (![self isPreview])
703 [NSCursor setHiddenUntilMouseMoves:YES];
709 /* This is just a guess, but the -fps code wants to know how long
710 we were sleeping between frames.
712 long usecs = 1000000 * [self animationTimeInterval];
713 usecs -= 200; // caller apparently sleeps for slightly less sometimes...
714 if (usecs < 0) usecs = 0;
715 fps_slept (fpst, usecs);
719 /* It turns out that [ScreenSaverView setAnimationTimeInterval] does nothing.
720 This is bad, because some of the screen hacks want to delay for long
721 periods (like 5 seconds or a minute!) between frames, and running them
722 all at 60 FPS is no good.
724 So, we don't use setAnimationTimeInterval, and just let the framework call
725 us whenever. But, we only invoke the screen hack's "draw frame" method
726 when enough time has expired.
728 This means two extra calls to gettimeofday() per frame. For fast-cycling
729 screen savers, that might actually slow them down. Oh well.
731 #### Also, we do not run the draw callback faster than the system's
732 animationTimeInterval, so if any savers are pickier about timing
733 than that, this may slow them down too much. If that's a problem,
734 then we could call draw_cb in a loop here (with usleep) until the
735 next call would put us past animationTimeInterval... But a better
736 approach would probably be to just change the saver to not do that.
739 gettimeofday (&tv, 0);
740 double now = tv.tv_sec + (tv.tv_usec / 1000000.0);
741 if (now < next_frame_time) return;
743 [self prepareContext];
746 // We do this here instead of in setFrame so that all the
747 // Xlib drawing takes place under the animation timer.
748 [self resizeContext];
755 r.size.width = backbuffer_size.width;
756 r.size.height = backbuffer_size.height;
757 # endif // USE_IPHONE
759 xsft->reshape_cb (xdpy, xwindow, xdata, r.size.width, r.size.height);
764 // Run any XtAppAddInput callbacks now.
765 // (Note that XtAppAddTimeOut callbacks have already been run by
766 // the Cocoa event loop.)
768 jwxyz_sources_run (display_sources_data (xdpy));
774 NSDisableScreenUpdates();
776 unsigned long delay = xsft->draw_cb (xdpy, xwindow, xdata);
777 if (fpst) xsft->fps_cb (xdpy, xwindow, fpst, xdata);
779 NSEnableScreenUpdates();
782 gettimeofday (&tv, 0);
783 now = tv.tv_sec + (tv.tv_usec / 1000000.0);
784 next_frame_time = now + (delay / 1000000.0);
786 # ifdef USE_IPHONE // Allow savers on the iPhone to run full-tilt.
787 if (delay < [self animationTimeInterval])
788 [self setAnimationTimeInterval:(delay / 1000000.0)];
791 # ifdef DO_GC_HACKERY
792 /* Current theory is that the 10.6 garbage collector sucks in the
795 It only does a collection when a threshold of outstanding
796 collectable allocations has been surpassed. However, CoreGraphics
797 creates lots of small collectable allocations that contain pointers
798 to very large non-collectable allocations: a small CG object that's
799 collectable referencing large malloc'd allocations (non-collectable)
800 containing bitmap data. So the large allocation doesn't get freed
801 until GC collects the small allocation, which triggers its finalizer
802 to run which frees the large allocation. So GC is deciding that it
803 doesn't really need to run, even though the process has gotten
804 enormous. GC eventually runs once pageouts have happened, but by
805 then it's too late, and the machine's resident set has been
808 So, we force an exhaustive garbage collection in this process
809 approximately every 5 seconds whether the system thinks it needs
816 objc_collect (OBJC_EXHAUSTIVE_COLLECTION);
819 # endif // DO_GC_HACKERY
823 /* On MacOS: drawRect does nothing, and animateOneFrame renders.
824 On iOS GL: drawRect does nothing, and animateOneFrame renders.
825 On iOS X11: drawRect renders, and animateOneFrame marks the view dirty.
829 - (void)drawRect:(NSRect)rect
831 if (xwindow) // clear to the X window's bg color, not necessarily black.
832 XClearWindow (xdpy, xwindow);
834 [super drawRect:rect]; // early: black.
837 - (void) animateOneFrame
844 - (void)drawRect:(NSRect)rect
846 // Render X11 into the backing store bitmap...
848 NSAssert (backbuffer, @"no back buffer");
849 UIGraphicsPushContext (backbuffer);
851 UIGraphicsPopContext();
853 // Then copy that bitmap to the screen.
855 CGContextRef cgc = UIGraphicsGetCurrentContext();
857 // Mask it to only update the parts that are exposed.
858 // CGContextClipToRect (cgc, rect);
860 double s = self.contentScaleFactor;
861 CGRect frame = [self frame];
864 target.size.width = backbuffer_size.width;
865 target.size.height = backbuffer_size.height;
866 target.origin.x = (s * frame.size.width - target.size.width) / 2;
867 target.origin.y = (s * frame.size.height - target.size.height) / 2;
869 target.origin.x /= s;
870 target.origin.y /= s;
871 target.size.width /= s;
872 target.size.height /= s;
874 CGAffineTransform t = CGAffineTransformIdentity;
876 // Rotate around center
877 float cx = frame.size.width / 2;
878 float cy = frame.size.height / 2;
879 t = CGAffineTransformTranslate (t, cx, cy);
880 t = CGAffineTransformRotate (t, -rot_current_angle / (180.0 / M_PI));
881 t = CGAffineTransformTranslate (t, -cx, -cy);
884 t = CGAffineTransformConcat (t,
885 CGAffineTransformMake ( 1, 0, 0,
886 -1, 0, frame.size.height));
888 // Clear background (visible in corners of screen during rotation)
889 if (rotation_ratio != -1) {
890 CGContextSetGrayFillColor (cgc, 0, 1);
891 CGContextFillRect (cgc, frame);
894 CGContextConcatCTM (cgc, t);
896 // Copy the backbuffer to the screen.
897 // Note that CGContextDrawImage measures in "points", not "pixels".
898 CGImageRef img = CGBitmapContextCreateImage (backbuffer);
899 CGContextDrawImage (cgc, target, img);
900 CGImageRelease (img);
903 - (void) animateOneFrame
905 [self setNeedsDisplay];
908 #endif // !USE_IPHONE
912 - (void) setFrame:(NSRect) newRect
914 [super setFrame:newRect];
917 [self createBackbuffer];
920 resized_p = YES; // The reshape_cb runs in render_x11
921 if (xwindow) { // inform Xlib that the window has changed now.
923 NSAssert (backbuffer, @"no back buffer");
924 // The backbuffer is the rotated size, and so is the xwindow.
925 jwxyz_window_resized (xdpy, xwindow,
927 backbuffer_size.width, backbuffer_size.height,
930 jwxyz_window_resized (xdpy, xwindow,
931 newRect.origin.x, newRect.origin.y,
932 newRect.size.width, newRect.size.height,
939 # ifndef USE_IPHONE // Doesn't exist on iOS
940 - (void) setFrameSize:(NSSize) newSize
942 [super setFrameSize:newSize];
945 jwxyz_window_resized (xdpy, xwindow,
946 [self frame].origin.x,
947 [self frame].origin.y,
948 newSize.width, newSize.height,
949 0); // backbuffer only on iPhone
951 # endif // !USE_IPHONE
954 +(BOOL) performGammaFade
959 - (BOOL) hasConfigureSheet
965 - (NSWindow *) configureSheet
967 - (UIViewController *) configureView
970 NSBundle *bundle = [NSBundle bundleForClass:[self class]];
971 NSString *file = [NSString stringWithCString:xsft->progclass
972 encoding:NSUTF8StringEncoding];
973 file = [file lowercaseString];
974 NSString *path = [bundle pathForResource:file ofType:@"xml"];
976 NSLog (@"%@.xml does not exist in the application bundle: %@/",
977 file, [bundle resourcePath]);
982 UIViewController *sheet;
983 # else // !USE_IPHONE
985 # endif // !USE_IPHONE
987 sheet = [[XScreenSaverConfigSheet alloc]
989 options:xsft->options
990 controller:[prefsReader userDefaultsController]
991 defaults:[prefsReader defaultOptions]];
993 // #### am I expected to retain this, or not? wtf.
994 // I thought not, but if I don't do this, we (sometimes) crash.
1001 - (NSUserDefaultsController *) userDefaultsController
1003 return [prefsReader userDefaultsController];
1007 /* Announce our willingness to accept keyboard input.
1009 - (BOOL)acceptsFirstResponder
1017 /* Convert an NSEvent into an XEvent, and pass it along.
1018 Returns YES if it was handled.
1020 - (BOOL) doEvent: (NSEvent *) e
1023 if (![self isPreview] || // no event handling if actually screen-saving!
1024 ![self isAnimating] ||
1029 memset (&xe, 0, sizeof(xe));
1033 int flags = [e modifierFlags];
1034 if (flags & NSAlphaShiftKeyMask) state |= LockMask;
1035 if (flags & NSShiftKeyMask) state |= ShiftMask;
1036 if (flags & NSControlKeyMask) state |= ControlMask;
1037 if (flags & NSAlternateKeyMask) state |= Mod1Mask;
1038 if (flags & NSCommandKeyMask) state |= Mod2Mask;
1040 NSPoint p = [[[e window] contentView] convertPoint:[e locationInWindow]
1043 double s = self.contentScaleFactor;
1048 int y = s * ([self frame].size.height - p.y);
1050 xe.xany.type = type;
1056 xe.xbutton.state = state;
1057 if ([e type] == NSScrollWheel)
1058 xe.xbutton.button = ([e deltaY] > 0 ? Button4 :
1059 [e deltaY] < 0 ? Button5 :
1060 [e deltaX] > 0 ? Button6 :
1061 [e deltaX] < 0 ? Button7 :
1064 xe.xbutton.button = [e buttonNumber] + 1;
1069 xe.xmotion.state = state;
1074 NSString *ns = (([e type] == NSFlagsChanged) ? 0 :
1075 [e charactersIgnoringModifiers]);
1078 if (!ns || [ns length] == 0) // dead key
1080 // Cocoa hides the difference between left and right keys.
1081 // Also we only get KeyPress events for these, no KeyRelease
1082 // (unless we hack the mod state manually. Bleh.)
1084 if (flags & NSAlphaShiftKeyMask) k = XK_Caps_Lock;
1085 else if (flags & NSShiftKeyMask) k = XK_Shift_L;
1086 else if (flags & NSControlKeyMask) k = XK_Control_L;
1087 else if (flags & NSAlternateKeyMask) k = XK_Alt_L;
1088 else if (flags & NSCommandKeyMask) k = XK_Meta_L;
1090 else if ([ns length] == 1) // real key
1092 switch ([ns characterAtIndex:0]) {
1093 case NSLeftArrowFunctionKey: k = XK_Left; break;
1094 case NSRightArrowFunctionKey: k = XK_Right; break;
1095 case NSUpArrowFunctionKey: k = XK_Up; break;
1096 case NSDownArrowFunctionKey: k = XK_Down; break;
1097 case NSPageUpFunctionKey: k = XK_Page_Up; break;
1098 case NSPageDownFunctionKey: k = XK_Page_Down; break;
1099 case NSHomeFunctionKey: k = XK_Home; break;
1100 case NSPrevFunctionKey: k = XK_Prior; break;
1101 case NSNextFunctionKey: k = XK_Next; break;
1102 case NSBeginFunctionKey: k = XK_Begin; break;
1103 case NSEndFunctionKey: k = XK_End; break;
1107 [ns cStringUsingEncoding:NSISOLatin1StringEncoding];
1108 k = (s && *s ? *s : 0);
1114 xe.xkey.keycode = k;
1115 xe.xkey.state = state;
1123 [self prepareContext];
1124 BOOL result = xsft->event_cb (xdpy, xwindow, xdata, &xe);
1130 - (void) mouseDown: (NSEvent *) e
1132 if (! [self doEvent:e type:ButtonPress])
1133 [super mouseDown:e];
1136 - (void) mouseUp: (NSEvent *) e
1138 if (! [self doEvent:e type:ButtonRelease])
1142 - (void) otherMouseDown: (NSEvent *) e
1144 if (! [self doEvent:e type:ButtonPress])
1145 [super otherMouseDown:e];
1148 - (void) otherMouseUp: (NSEvent *) e
1150 if (! [self doEvent:e type:ButtonRelease])
1151 [super otherMouseUp:e];
1154 - (void) mouseMoved: (NSEvent *) e
1156 if (! [self doEvent:e type:MotionNotify])
1157 [super mouseMoved:e];
1160 - (void) mouseDragged: (NSEvent *) e
1162 if (! [self doEvent:e type:MotionNotify])
1163 [super mouseDragged:e];
1166 - (void) otherMouseDragged: (NSEvent *) e
1168 if (! [self doEvent:e type:MotionNotify])
1169 [super otherMouseDragged:e];
1172 - (void) scrollWheel: (NSEvent *) e
1174 if (! [self doEvent:e type:ButtonPress])
1175 [super scrollWheel:e];
1178 - (void) keyDown: (NSEvent *) e
1180 if (! [self doEvent:e type:KeyPress])
1184 - (void) keyUp: (NSEvent *) e
1186 if (! [self doEvent:e type:KeyRelease])
1190 - (void) flagsChanged: (NSEvent *) e
1192 if (! [self doEvent:e type:KeyPress])
1193 [super flagsChanged:e];
1199 /* Called after the device's orientation has changed.
1201 Note: we could include a subclass of UIViewController which
1202 contains a shouldAutorotateToInterfaceOrientation method that
1203 returns YES, in which case Core Animation would auto-rotate our
1204 View for us in response to rotation events... but, that interacts
1205 badly with the EAGLContext -- if you introduce Core Animation into
1206 the path, the OpenGL pipeline probably falls back on software
1207 rendering and performance goes to hell. Also, the scaling and
1208 rotation that Core Animation does interacts incorrectly with the GL
1211 So, we have to hack the rotation animation manually, in the GL world.
1213 Possibly XScreenSaverView should use Core Animation, and
1214 XScreenSaverGLView should override that.
1216 - (void)didRotate:(NSNotification *)notification
1218 UIDeviceOrientation current = [[UIDevice currentDevice] orientation];
1220 /* If the simulator starts up in the rotated position, sometimes
1221 the UIDevice says we're in Portrait when we're not -- but it
1222 turns out that the UINavigationController knows what's up!
1223 So get it from there.
1225 if (current == UIDeviceOrientationUnknown) {
1226 switch ([[[self window] rootViewController] interfaceOrientation]) {
1227 case UIInterfaceOrientationPortrait:
1228 current = UIDeviceOrientationPortrait;
1230 case UIInterfaceOrientationPortraitUpsideDown:
1231 current = UIDeviceOrientationPortraitUpsideDown;
1233 case UIInterfaceOrientationLandscapeLeft: // It's opposite day
1234 current = UIDeviceOrientationLandscapeRight;
1236 case UIInterfaceOrientationLandscapeRight:
1237 current = UIDeviceOrientationLandscapeLeft;
1244 /* On the iPad (but not iPhone 3GS, or the simulator) sometimes we get
1245 an orientation change event with an unknown orientation. Those seem
1246 to always be immediately followed by another orientation change with
1247 a *real* orientation change, so let's try just ignoring those bogus
1248 ones and hoping that the real one comes in shortly...
1250 if (current == UIDeviceOrientationUnknown)
1253 if (rotation_ratio >= 0) return; // in the midst of rotation animation
1254 if (orientation == current) return; // no change
1256 // When transitioning to FaceUp or FaceDown, pretend there was no change.
1257 if (current == UIDeviceOrientationFaceUp ||
1258 current == UIDeviceOrientationFaceDown)
1261 new_orientation = current; // current animation target
1262 rotation_ratio = 0; // start animating
1263 rot_start_time = double_time();
1265 switch (orientation) {
1266 case UIDeviceOrientationLandscapeLeft: angle_from = 90; break;
1267 case UIDeviceOrientationLandscapeRight: angle_from = 270; break;
1268 case UIDeviceOrientationPortraitUpsideDown: angle_from = 180; break;
1269 default: angle_from = 0; break;
1272 switch (new_orientation) {
1273 case UIDeviceOrientationLandscapeLeft: angle_to = 90; break;
1274 case UIDeviceOrientationLandscapeRight: angle_to = 270; break;
1275 case UIDeviceOrientationPortraitUpsideDown: angle_to = 180; break;
1276 default: angle_to = 0; break;
1279 NSRect ff = [self frame];
1281 switch (orientation) {
1282 case UIDeviceOrientationLandscapeRight: // from landscape
1283 case UIDeviceOrientationLandscapeLeft:
1284 rot_from.width = ff.size.height;
1285 rot_from.height = ff.size.width;
1287 default: // from portrait
1288 rot_from.width = ff.size.width;
1289 rot_from.height = ff.size.height;
1293 switch (new_orientation) {
1294 case UIDeviceOrientationLandscapeRight: // to landscape
1295 case UIDeviceOrientationLandscapeLeft:
1296 rot_to.width = ff.size.height;
1297 rot_to.height = ff.size.width;
1299 default: // to portrait
1300 rot_to.width = ff.size.width;
1301 rot_to.height = ff.size.height;
1306 // If we've done a rotation but the saver hasn't been initialized yet,
1307 // don't bother going through an X11 resize, but just do it now.
1308 rot_start_time = 0; // dawn of time
1309 [self hackRotation];
1314 /* In the simulator, multi-touch sequences look like this:
1316 touchesBegan [touchA, touchB]
1317 touchesEnd [touchA, touchB]
1319 But on real devices, sometimes you get that, but sometimes you get:
1321 touchesBegan [touchA, touchB]
1327 touchesBegan [touchA]
1328 touchesBegan [touchB]
1332 So the only way to properly detect a "pinch" gesture is to remember
1333 the start-point of each touch as it comes in; and the end-point of
1334 each touch as those come in; and only process the gesture once the
1335 number of touchEnds matches the number of touchBegins.
1339 rotate_mouse (int *x, int *y, int w, int h, int rot)
1341 int ox = *x, oy = *y;
1342 if (rot > 45 && rot < 135) { *x = oy; *y = w-ox; }
1343 else if (rot < -45 && rot > -135) { *x = h-oy; *y = ox; }
1344 else if (rot > 135 || rot < -135) { *x = w-ox; *y = h-oy; }
1348 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
1350 if (xsft->event_cb && xwindow) {
1351 double s = self.contentScaleFactor;
1353 memset (&xe, 0, sizeof(xe));
1355 int w = s * [self frame].size.width;
1356 int h = s * [self frame].size.height;
1357 for (UITouch *touch in touches) {
1358 CGPoint p = [touch locationInView:self];
1359 xe.xany.type = ButtonPress;
1360 xe.xbutton.button = i + 1;
1361 xe.xbutton.button = i + 1;
1362 xe.xbutton.x = s * p.x;
1363 xe.xbutton.y = s * p.y;
1364 rotate_mouse (&xe.xbutton.x, &xe.xbutton.y, w, h, rot_current_angle);
1365 jwxyz_mouse_moved (xdpy, xwindow, xe.xbutton.x, xe.xbutton.y);
1366 xsft->event_cb (xdpy, xwindow, xdata, &xe);
1373 - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
1376 // Double-tap means "exit" and return to selection menu.
1378 for (UITouch *touch in touches) {
1379 if ([touch tapCount] >= 2) {
1380 if ([self isAnimating])
1381 [self stopAnimation];
1382 [self removeFromSuperview];
1387 if (xsft->event_cb && xwindow) {
1388 double s = self.contentScaleFactor;
1390 memset (&xe, 0, sizeof(xe));
1392 int w = s * [self frame].size.width;
1393 int h = s * [self frame].size.height;
1394 for (UITouch *touch in touches) {
1395 CGPoint p = [touch locationInView:self];
1396 xe.xany.type = ButtonRelease;
1397 xe.xbutton.button = i + 1;
1398 xe.xbutton.x = s * p.x;
1399 xe.xbutton.y = s * p.y;
1400 rotate_mouse (&xe.xbutton.x, &xe.xbutton.y, w, h, rot_current_angle);
1401 jwxyz_mouse_moved (xdpy, xwindow, xe.xbutton.x, xe.xbutton.y);
1402 xsft->event_cb (xdpy, xwindow, xdata, &xe);
1409 - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
1411 if (xsft->event_cb && xwindow) {
1412 double s = self.contentScaleFactor;
1414 memset (&xe, 0, sizeof(xe));
1416 int w = s * [self frame].size.width;
1417 int h = s * [self frame].size.height;
1418 for (UITouch *touch in touches) {
1419 CGPoint p = [touch locationInView:self];
1420 xe.xany.type = MotionNotify;
1421 xe.xmotion.x = s * p.x;
1422 xe.xmotion.y = s * p.y;
1423 rotate_mouse (&xe.xbutton.x, &xe.xbutton.y, w, h, rot_current_angle);
1424 jwxyz_mouse_moved (xdpy, xwindow, xe.xmotion.x, xe.xmotion.y);
1425 xsft->event_cb (xdpy, xwindow, xdata, &xe);
1432 /* We need this to respond to "shake" gestures
1434 - (BOOL)canBecomeFirstResponder {
1439 - (void)setScreenLocked:(BOOL)locked
1441 if (screenLocked == locked) return;
1442 screenLocked = locked;
1444 if ([self isAnimating])
1445 [self stopAnimation];
1447 if (! [self isAnimating])
1448 [self startAnimation];
1453 #endif // USE_IPHONE
1458 /* Utility functions...
1461 static PrefsReader *
1462 get_prefsReader (Display *dpy)
1464 XScreenSaverView *view = jwxyz_window_view (XRootWindow (dpy, 0));
1466 return [view prefsReader];
1471 get_string_resource (Display *dpy, char *name, char *class)
1473 return [get_prefsReader(dpy) getStringResource:name];
1477 get_boolean_resource (Display *dpy, char *name, char *class)
1479 return [get_prefsReader(dpy) getBooleanResource:name];
1483 get_integer_resource (Display *dpy, char *name, char *class)
1485 return [get_prefsReader(dpy) getIntegerResource:name];
1489 get_float_resource (Display *dpy, char *name, char *class)
1491 return [get_prefsReader(dpy) getFloatResource:name];