1 /* xscreensaver, Copyright (c) 2006-2015 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>
21 #import "XScreenSaverView.h"
22 #import "XScreenSaverConfigSheet.h"
24 #import "screenhackI.h"
25 #import "xlockmoreI.h"
26 #import "jwxyz-timers.h"
29 # import <OpenGL/glu.h>
32 /* Garbage collection only exists if we are being compiled against the
33 10.6 SDK or newer, not if we are building against the 10.4 SDK.
35 #ifndef MAC_OS_X_VERSION_10_6
36 # define MAC_OS_X_VERSION_10_6 1060 /* undefined in 10.4 SDK, grr */
38 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 /* 10.6 SDK */
39 # import <objc/objc-auto.h>
40 # define DO_GC_HACKERY
43 /* Duplicated in xlockmoreI.h and XScreenSaverGLView.m. */
44 extern void clear_gl_error (void);
45 extern void check_gl_error (const char *type);
47 extern struct xscreensaver_function_table *xscreensaver_function_table;
49 /* Global variables used by the screen savers
52 const char *progclass;
58 # define NSSizeToCGSize(x) (x)
60 extern NSDictionary *make_function_table_dict(void); // ios-function-table.m
62 /* Stub definition of the superclass, for iPhone.
64 @implementation ScreenSaverView
66 NSTimeInterval anim_interval;
71 - (id)initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview {
72 self = [super initWithFrame:frame];
74 anim_interval = 1.0/30;
77 - (NSTimeInterval)animationTimeInterval { return anim_interval; }
78 - (void)setAnimationTimeInterval:(NSTimeInterval)i { anim_interval = i; }
79 - (BOOL)hasConfigureSheet { return NO; }
80 - (NSWindow *)configureSheet { return nil; }
81 - (NSView *)configureView { return nil; }
82 - (BOOL)isPreview { return NO; }
83 - (BOOL)isAnimating { return animating_p; }
84 - (void)animateOneFrame { }
86 - (void)startAnimation {
87 if (animating_p) return;
89 anim_timer = [NSTimer scheduledTimerWithTimeInterval: anim_interval
91 selector:@selector(animateOneFrame)
96 - (void)stopAnimation {
98 [anim_timer invalidate];
105 # endif // !USE_IPHONE
109 @interface XScreenSaverView (Private)
110 - (void) stopAndClose:(Bool)relaunch;
113 @implementation XScreenSaverView
115 // Given a lower-cased saver name, returns the function table for it.
116 // If no name, guess the name from the class's bundle name.
118 - (struct xscreensaver_function_table *) findFunctionTable:(NSString *)name
120 NSBundle *nsb = [NSBundle bundleForClass:[self class]];
121 NSAssert1 (nsb, @"no bundle for class %@", [self class]);
123 NSString *path = [nsb bundlePath];
124 CFURLRef url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
126 kCFURLPOSIXPathStyle,
128 CFBundleRef cfb = CFBundleCreate (kCFAllocatorDefault, url);
130 NSAssert1 (cfb, @"no CFBundle for \"%@\"", path);
131 // #### Analyze says "Potential leak of an object stored into cfb"
134 name = [[path lastPathComponent] stringByDeletingPathExtension];
136 name = [[name lowercaseString]
137 stringByReplacingOccurrencesOfString:@" "
141 // CFBundleGetDataPointerForName doesn't work in "Archive" builds.
142 // I'm guessing that symbol-stripping is mandatory. Fuck.
143 NSString *table_name = [name stringByAppendingString:
144 @"_xscreensaver_function_table"];
145 void *addr = CFBundleGetDataPointerForName (cfb, (CFStringRef) table_name);
149 NSLog (@"no symbol \"%@\" for \"%@\"", table_name, path);
152 // Depends on the auto-generated "ios-function-table.m" being up to date.
153 if (! function_tables)
154 function_tables = [make_function_table_dict() retain];
155 NSValue *v = [function_tables objectForKey: name];
156 void *addr = v ? [v pointerValue] : 0;
157 # endif // USE_IPHONE
159 return (struct xscreensaver_function_table *) addr;
163 // Add the "Contents/Resources/" subdirectory of this screen saver's .bundle
164 // to $PATH for the benefit of savers that include helper shell scripts.
166 - (void) setShellPath
168 NSBundle *nsb = [NSBundle bundleForClass:[self class]];
169 NSAssert1 (nsb, @"no bundle for class %@", [self class]);
171 NSString *nsdir = [nsb resourcePath];
172 NSAssert1 (nsdir, @"no resourcePath for class %@", [self class]);
173 const char *dir = [nsdir cStringUsingEncoding:NSUTF8StringEncoding];
174 const char *opath = getenv ("PATH");
175 if (!opath) opath = "/bin"; // $PATH is unset when running under Shark!
176 char *npath = (char *) malloc (strlen (opath) + strlen (dir) + 30);
177 strcpy (npath, "PATH=");
180 strcat (npath, opath);
181 if (putenv (npath)) {
183 NSAssert1 (0, @"putenv \"%s\" failed", npath);
186 /* Don't free (npath) -- MacOS's putenv() does not copy it. */
190 // set an $XSCREENSAVER_CLASSPATH variable so that included shell scripts
191 // (e.g., "xscreensaver-text") know how to look up resources.
193 - (void) setResourcesEnv:(NSString *) name
195 NSBundle *nsb = [NSBundle bundleForClass:[self class]];
196 NSAssert1 (nsb, @"no bundle for class %@", [self class]);
198 const char *s = [name cStringUsingEncoding:NSUTF8StringEncoding];
199 char *env = (char *) malloc (strlen (s) + 40);
200 strcpy (env, "XSCREENSAVER_CLASSPATH=");
204 NSAssert1 (0, @"putenv \"%s\" failed", env);
206 /* Don't free (env) -- MacOS's putenv() does not copy it. */
211 add_default_options (const XrmOptionDescRec *opts,
212 const char * const *defs,
213 XrmOptionDescRec **opts_ret,
214 const char ***defs_ret)
216 /* These aren't "real" command-line options (there are no actual command-line
217 options in the Cocoa version); but this is the somewhat kludgey way that
218 the <xscreensaver-text /> and <xscreensaver-image /> tags in the
219 ../hacks/config/\*.xml files communicate with the preferences database.
221 static const XrmOptionDescRec default_options [] = {
222 { "-text-mode", ".textMode", XrmoptionSepArg, 0 },
223 { "-text-literal", ".textLiteral", XrmoptionSepArg, 0 },
224 { "-text-file", ".textFile", XrmoptionSepArg, 0 },
225 { "-text-url", ".textURL", XrmoptionSepArg, 0 },
226 { "-text-program", ".textProgram", XrmoptionSepArg, 0 },
227 { "-grab-desktop", ".grabDesktopImages", XrmoptionNoArg, "True" },
228 { "-no-grab-desktop", ".grabDesktopImages", XrmoptionNoArg, "False"},
229 { "-choose-random-images", ".chooseRandomImages",XrmoptionNoArg, "True" },
230 { "-no-choose-random-images",".chooseRandomImages",XrmoptionNoArg, "False"},
231 { "-image-directory", ".imageDirectory", XrmoptionSepArg, 0 },
232 { "-fps", ".doFPS", XrmoptionNoArg, "True" },
233 { "-no-fps", ".doFPS", XrmoptionNoArg, "False"},
234 { "-foreground", ".foreground", XrmoptionSepArg, 0 },
235 { "-fg", ".foreground", XrmoptionSepArg, 0 },
236 { "-background", ".background", XrmoptionSepArg, 0 },
237 { "-bg", ".background", XrmoptionSepArg, 0 },
240 // <xscreensaver-updater />
241 { "-" SUSUEnableAutomaticChecksKey,
242 "." SUSUEnableAutomaticChecksKey, XrmoptionNoArg, "True" },
243 { "-no-" SUSUEnableAutomaticChecksKey,
244 "." SUSUEnableAutomaticChecksKey, XrmoptionNoArg, "False" },
245 { "-" SUAutomaticallyUpdateKey,
246 "." SUAutomaticallyUpdateKey, XrmoptionNoArg, "True" },
247 { "-no-" SUAutomaticallyUpdateKey,
248 "." SUAutomaticallyUpdateKey, XrmoptionNoArg, "False" },
249 { "-" SUSendProfileInfoKey,
250 "." SUSendProfileInfoKey, XrmoptionNoArg,"True" },
251 { "-no-" SUSendProfileInfoKey,
252 "." SUSendProfileInfoKey, XrmoptionNoArg,"False"},
253 { "-" SUScheduledCheckIntervalKey,
254 "." SUScheduledCheckIntervalKey, XrmoptionSepArg, 0 },
255 # endif // !USE_IPHONE
259 static const char *default_defaults [] = {
261 ".doubleBuffer: True",
262 ".multiSample: False",
270 ".textURL: https://en.wikipedia.org/w/index.php?title=Special:NewPages&feed=rss",
272 ".grabDesktopImages: yes",
274 ".chooseRandomImages: no",
276 ".chooseRandomImages: yes",
278 ".imageDirectory: ~/Pictures",
280 ".texFontCacheSize: 30",
284 # define STR(S) STR1(S)
285 # define __objc_yes Yes
286 # define __objc_no No
287 "." SUSUEnableAutomaticChecksKey ": " STR(SUSUEnableAutomaticChecksDef),
288 "." SUAutomaticallyUpdateKey ": " STR(SUAutomaticallyUpdateDef),
289 "." SUSendProfileInfoKey ": " STR(SUSendProfileInfoDef),
290 "." SUScheduledCheckIntervalKey ": " STR(SUScheduledCheckIntervalDef),
295 # endif // USE_IPHONE
300 for (i = 0; default_options[i].option; i++)
302 for (i = 0; opts[i].option; i++)
305 XrmOptionDescRec *opts2 = (XrmOptionDescRec *)
306 calloc (count + 1, sizeof (*opts2));
310 while (default_options[j].option) {
311 opts2[i] = default_options[j];
315 while (opts[j].option) {
326 for (i = 0; default_defaults[i]; i++)
328 for (i = 0; defs[i]; i++)
331 const char **defs2 = (const char **) calloc (count + 1, sizeof (*defs2));
335 while (default_defaults[j]) {
336 defs2[i] = default_defaults[j];
350 /* Returns the current time in seconds as a double.
356 # ifdef GETTIMEOFDAY_TWO_ARGS
358 gettimeofday(&now, &tzp);
363 return (now.tv_sec + ((double) now.tv_usec * 0.000001));
367 #if TARGET_IPHONE_SIMULATOR
369 orientname(unsigned long o)
372 case UIDeviceOrientationUnknown: return "Unknown";
373 case UIDeviceOrientationPortrait: return "Portrait";
374 case UIDeviceOrientationPortraitUpsideDown: return "PortraitUpsideDown";
375 case UIDeviceOrientationLandscapeLeft: return "LandscapeLeft";
376 case UIDeviceOrientationLandscapeRight: return "LandscapeRight";
377 case UIDeviceOrientationFaceUp: return "FaceUp";
378 case UIDeviceOrientationFaceDown: return "FaceDown";
379 default: return "ERROR";
382 #endif // TARGET_IPHONE_SIMULATOR
385 - (id) initWithFrame:(NSRect)frame
386 saverName:(NSString *)saverName
387 isPreview:(BOOL)isPreview
389 if (! (self = [super initWithFrame:frame isPreview:isPreview]))
392 xsft = [self findFunctionTable: saverName];
402 xsft->setup_cb (xsft, xsft->setup_arg);
404 /* The plist files for these preferences show up in
405 $HOME/Library/Preferences/ByHost/ in a file named like
406 "org.jwz.xscreensaver.<SAVERNAME>.<NUMBERS>.plist"
408 NSString *name = [NSString stringWithCString:xsft->progclass
409 encoding:NSISOLatin1StringEncoding];
410 name = [@"org.jwz.xscreensaver." stringByAppendingString:name];
411 [self setResourcesEnv:name];
414 XrmOptionDescRec *opts = 0;
415 const char **defs = 0;
416 add_default_options (xsft->options, xsft->defaults, &opts, &defs);
417 prefsReader = [[PrefsReader alloc]
418 initWithName:name xrmKeys:opts defaults:defs];
420 // free (opts); // bah, we need these! #### leak!
421 xsft->options = opts;
423 progname = progclass = xsft->progclass;
428 // When the view fills the screen and double buffering is enabled, OS X will
429 // use page flipping for a minor CPU/FPS boost. In windowed mode, double
430 // buffering reduces the frame rate to 1/2 the screen's refresh rate.
431 double_buffered_p = !isPreview;
435 double s = [self hackedContentScaleFactor];
440 CGSize bb_size; // pixels, not points
441 bb_size.width = s * frame.size.width;
442 bb_size.height = s * frame.size.height;
445 initial_bounds = rot_current_size = rot_from = rot_to = bb_size;
448 orientation = UIDeviceOrientationUnknown;
449 [self didRotate:nil];
452 // So we can tell when we're docked.
453 [UIDevice currentDevice].batteryMonitoringEnabled = YES;
455 [self setBackgroundColor:[NSColor blackColor]];
457 [[NSNotificationCenter defaultCenter]
459 selector:@selector(didRotate:)
460 name:UIDeviceOrientationDidChangeNotification object:nil];
461 # endif // USE_IPHONE
470 return [CAEAGLLayer class];
475 - (id) initWithFrame:(NSRect)frame isPreview:(BOOL)p
477 return [self initWithFrame:frame saverName:0 isPreview:p];
483 if ([self isAnimating])
484 [self stopAnimation];
485 NSAssert(!xdata, @"xdata not yet freed");
486 NSAssert(!xdpy, @"xdpy not yet freed");
489 [[NSNotificationCenter defaultCenter] removeObserver:self];
492 # ifdef USE_BACKBUFFER
494 # ifdef BACKBUFFER_OPENGL
496 // Releasing the OpenGL context should also free any OpenGL objects,
497 // including the backbuffer texture and frame/render/depthbuffers.
498 # endif // BACKBUFFER_OPENGL
501 CGColorSpaceRelease (colorspace);
503 # endif // USE_BACKBUFFER
505 [prefsReader release];
513 - (PrefsReader *) prefsReader
520 - (void) lockFocus { }
521 - (void) unlockFocus { }
527 /* A few seconds after the saver launches, we store the "wasRunning"
528 preference. This is so that if the saver is crashing at startup,
529 we don't launch it again next time, getting stuck in a crash loop.
531 - (void) allSystemsGo: (NSTimer *) timer
533 NSAssert (timer == crash_timer, @"crash timer screwed up");
536 NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
537 [prefs setBool:YES forKey:@"wasRunning"];
543 - (void) startAnimation
545 NSAssert(![self isAnimating], @"already animating");
546 NSAssert(!initted_p && !xdata, @"already initialized");
548 // See comment in render_x11() for why this value is important:
549 [self setAnimationTimeInterval: 1.0 / 240.0];
551 [super startAnimation];
552 /* We can't draw on the window from this method, so we actually do the
553 initialization of the screen saver (xsft->init_cb) in the first call
554 to animateOneFrame() instead.
559 CGSize b = self.bounds.size;
560 double s = [self hackedContentScaleFactor];
563 NSAssert (initial_bounds.width == b.width &&
564 initial_bounds.height == b.height,
565 @"bounds changed unexpectedly");
569 [crash_timer invalidate];
571 NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
572 [prefs removeObjectForKey:@"wasRunning"];
575 crash_timer = [NSTimer scheduledTimerWithTimeInterval: 5
577 selector:@selector(allSystemsGo:)
581 # endif // USE_IPHONE
583 // Never automatically turn the screen off if we are docked,
584 // and an animation is running.
587 [UIApplication sharedApplication].idleTimerDisabled =
588 ([UIDevice currentDevice].batteryState != UIDeviceBatteryStateUnplugged);
589 [[UIApplication sharedApplication]
590 setStatusBarHidden:YES withAnimation:UIStatusBarAnimationNone];
593 #ifdef BACKBUFFER_OPENGL
594 CGSize new_backbuffer_size;
600 NSOpenGLPixelFormat *pixfmt = [self getGLPixelFormat];
602 NSAssert (pixfmt, @"unable to create NSOpenGLPixelFormat");
604 [pixfmt retain]; // #### ???
606 // Fun: On OS X 10.7, the second time an OpenGL context is created, after
607 // the preferences dialog is launched in SaverTester, the context only
608 // lasts until the first full GC. Then it turns black. Solution is to
609 // reuse the OpenGL context after this point.
610 ogl_ctx = [[NSOpenGLContext alloc] initWithFormat:pixfmt
613 // Sync refreshes to the vertical blanking interval
615 [ogl_ctx setValues:&r forParameter:NSOpenGLCPSwapInterval];
616 // check_gl_error ("NSOpenGLCPSwapInterval"); // SEGV sometimes. Too early?
619 [ogl_ctx makeCurrentContext];
620 check_gl_error ("makeCurrentContext");
622 // NSOpenGLContext logs an 'invalid drawable' when this is called
623 // from initWithFrame.
624 [ogl_ctx setView:self];
626 // Clear frame buffer ASAP, else there are bits left over from other apps.
627 glClearColor (0, 0, 0, 1);
628 glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
630 // glXSwapBuffers (mi->dpy, mi->window);
633 // Enable multi-threading, if possible. This runs most OpenGL commands
634 // and GPU management on a second CPU.
636 # ifndef kCGLCEMPEngine
637 # define kCGLCEMPEngine 313 // Added in MacOS 10.4.8 + XCode 2.4.
639 CGLContextObj cctx = CGLGetCurrentContext();
640 CGLError err = CGLEnable (cctx, kCGLCEMPEngine);
641 if (err != kCGLNoError) {
642 NSLog (@"enabling multi-threaded OpenGL failed: %d", err);
646 new_backbuffer_size = NSSizeToCGSize ([self bounds].size);
650 CAEAGLLayer *eagl_layer = (CAEAGLLayer *) self.layer;
651 eagl_layer.opaque = TRUE;
652 eagl_layer.drawableProperties = [self getGLProperties];
654 // Without this, the GL frame buffer is half the screen resolution!
655 eagl_layer.contentsScale = [UIScreen mainScreen].scale;
657 ogl_ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
660 [EAGLContext setCurrentContext: ogl_ctx];
662 CGSize screen_size = [[[UIScreen mainScreen] currentMode] size];
663 // iPad, simulator: 768x1024
664 // iPad, physical: 1024x768
665 if (screen_size.width > screen_size.height) {
666 CGFloat w = screen_size.width;
667 screen_size.width = screen_size.height;
668 screen_size.height = w;
671 if (gl_framebuffer) glDeleteFramebuffersOES (1, &gl_framebuffer);
672 if (gl_renderbuffer) glDeleteRenderbuffersOES (1, &gl_renderbuffer);
674 glGenFramebuffersOES (1, &gl_framebuffer);
675 glBindFramebufferOES (GL_FRAMEBUFFER_OES, gl_framebuffer);
677 glGenRenderbuffersOES (1, &gl_renderbuffer);
678 glBindRenderbufferOES (GL_RENDERBUFFER_OES, gl_renderbuffer);
681 // glRenderbufferStorageOES (GL_RENDERBUFFER_OES, GL_RGBA8_OES,
682 // (int)size.width, (int)size.height);
683 [ogl_ctx renderbufferStorage:GL_RENDERBUFFER_OES
684 fromDrawable:(CAEAGLLayer*)self.layer];
686 glFramebufferRenderbufferOES (GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES,
687 GL_RENDERBUFFER_OES, gl_renderbuffer);
689 [self addExtraRenderbuffers:screen_size];
691 int err = glCheckFramebufferStatusOES (GL_FRAMEBUFFER_OES);
693 case GL_FRAMEBUFFER_COMPLETE_OES:
695 case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_OES:
696 NSAssert (0, @"framebuffer incomplete attachment");
698 case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_OES:
699 NSAssert (0, @"framebuffer incomplete missing attachment");
701 case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_OES:
702 NSAssert (0, @"framebuffer incomplete dimensions");
704 case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_OES:
705 NSAssert (0, @"framebuffer incomplete formats");
707 case GL_FRAMEBUFFER_UNSUPPORTED_OES:
708 NSAssert (0, @"framebuffer unsupported");
711 case GL_FRAMEBUFFER_UNDEFINED:
712 NSAssert (0, @"framebuffer undefined");
714 case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
715 NSAssert (0, @"framebuffer incomplete draw buffer");
717 case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
718 NSAssert (0, @"framebuffer incomplete read buffer");
720 case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
721 NSAssert (0, @"framebuffer incomplete multisample");
723 case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS:
724 NSAssert (0, @"framebuffer incomplete layer targets");
728 NSAssert (0, @"framebuffer incomplete, unknown error 0x%04X", err);
732 glViewport (0, 0, screen_size.width, screen_size.height);
734 new_backbuffer_size = initial_bounds;
736 # endif // USE_IPHONE
738 check_gl_error ("startAnimation");
740 // NSLog (@"%s / %s / %s\n", glGetString (GL_VENDOR),
741 // glGetString (GL_RENDERER), glGetString (GL_VERSION));
743 [self enableBackbuffer:new_backbuffer_size];
745 #endif // BACKBUFFER_OPENGL
747 #ifdef USE_BACKBUFFER
748 [self createBackbuffer:new_backbuffer_size];
752 - (void)stopAnimation
754 NSAssert([self isAnimating], @"not animating");
758 [self lockFocus]; // in case something tries to draw from here
759 [self prepareContext];
761 /* I considered just not even calling the free callback at all...
762 But webcollage-cocoa needs it, to kill the inferior webcollage
763 processes (since the screen saver framework never generates a
764 SIGPIPE for them...) Instead, I turned off the free call in
765 xlockmore.c, which is where all of the bogus calls are anyway.
767 xsft->free_cb (xdpy, xwindow, xdata);
770 // xdpy must be freed before dealloc is called, because xdpy owns a
771 // circular reference to the parent XScreenSaverView.
772 jwxyz_free_display (xdpy);
776 // setup_p = NO; // #### wait, do we need this?
783 [crash_timer invalidate];
785 NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
786 [prefs removeObjectForKey:@"wasRunning"];
788 # endif // USE_IPHONE
790 [super stopAnimation];
792 // When an animation is no longer running (e.g., looking at the list)
793 // then it's ok to power off the screen when docked.
796 [UIApplication sharedApplication].idleTimerDisabled = NO;
797 [[UIApplication sharedApplication]
798 setStatusBarHidden:NO withAnimation:UIStatusBarAnimationNone];
801 // Without this, the GL frame stays on screen when switching tabs
802 // in System Preferences.
803 // (Or perhaps it used to. It doesn't seem to matter on 10.9.)
806 [NSOpenGLContext clearCurrentContext];
807 # endif // !USE_IPHONE
809 clear_gl_error(); // This hack is defunct, don't let this linger.
811 CGContextRelease (backbuffer);
815 munmap (backbuffer_data, backbuffer_len);
816 backbuffer_data = NULL;
821 // #### maybe this could/should just be on 'lockFocus' instead?
822 - (void) prepareContext
826 [EAGLContext setCurrentContext:ogl_ctx];
828 [ogl_ctx makeCurrentContext];
829 // check_gl_error ("makeCurrentContext");
830 #endif // !USE_IPHONE
836 screenhack_do_fps (Display *dpy, Window w, fps_state *fpst, void *closure)
838 fps_compute (fpst, 0, -1);
845 /* On iPhones with Retina displays, we can draw the savers in "real"
846 pixels, and that works great. The 320x480 "point" screen is really
847 a 640x960 *pixel* screen. However, Retina iPads have 768x1024
848 point screens which are 1536x2048 pixels, and apparently that's
849 enough pixels that copying those bits to the screen is slow. Like,
850 drops us from 15fps to 7fps. So, on Retina iPads, we don't draw in
851 real pixels. This will probably make the savers look better
852 anyway, since that's a higher resolution than most desktop monitors
853 have even today. (This is only true for X11 programs, not GL
854 programs. Those are fine at full rez.)
856 This method is overridden in XScreenSaverGLView, since this kludge
857 isn't necessary for GL programs, being resolution independent by
860 - (CGFloat) hackedContentScaleFactor
862 NSSize ssize = [[[UIScreen mainScreen] currentMode] size];
863 NSSize bsize = [self bounds].size;
866 max_ssize = ssize.width > ssize.height ? ssize.width : ssize.height,
867 max_bsize = bsize.width > bsize.height ? bsize.width : bsize.height;
869 // Ratio of screen size in pixels to view size in points.
870 CGFloat s = max_ssize / max_bsize;
874 // 1. Don't exceed -- let's say 1280 pixels in either direction.
875 // (Otherwise the frame rate gets bad.)
876 CGFloat mag0 = ceil(max_ssize / 1280);
878 // 2. Don't let the pixel size get too small.
879 // (Otherwise pixels in IFS and similar are too fine.)
880 // So don't let the result be > 2 pixels per point.
881 CGFloat mag1 = ceil(s / 2);
883 // As of iPhone 6, mag0 is always >= mag1. This may not be true in the future.
884 // (desired scale factor) = s / (desired magnification factor)
885 return s / (mag0 > mag1 ? mag0 : mag1);
889 static GLfloat _global_rot_current_angle_kludge;
891 double current_device_rotation (void)
893 return -_global_rot_current_angle_kludge;
897 - (void) hackRotation
899 if (rotation_ratio >= 0) { // in the midst of a rotation animation
901 # define CLAMP180(N) while (N < 0) N += 360; while (N > 180) N -= 360
902 GLfloat f = angle_from;
903 GLfloat t = angle_to;
906 GLfloat dist = -(t-f);
909 // Intermediate angle.
910 rot_current_angle = f - rotation_ratio * dist;
912 // Intermediate frame size.
913 rot_current_size.width = floor(rot_from.width +
914 rotation_ratio * (rot_to.width - rot_from.width));
915 rot_current_size.height = floor(rot_from.height +
916 rotation_ratio * (rot_to.height - rot_from.height));
918 // Tick animation. Complete rotation in 1/6th sec.
919 double now = double_time();
920 double duration = 1/6.0;
921 rotation_ratio = 1 - ((rot_start_time + duration - now) / duration);
923 if (rotation_ratio > 1 || ignore_rotation_p) { // Done animating.
924 orientation = new_orientation;
925 rot_current_angle = angle_to;
926 rot_current_size = rot_to;
929 # if TARGET_IPHONE_SIMULATOR
930 NSLog (@"rotation ended: %s %d, %d x %d",
931 orientname(orientation), (int) rot_current_angle,
932 (int) rot_current_size.width, (int) rot_current_size.height);
935 // Check orientation again in case we rotated again while rotating:
936 // this is a no-op if nothing has changed.
937 [self didRotate:nil];
939 } else { // Not animating a rotation.
940 rot_current_angle = angle_to;
941 rot_current_size = rot_to;
944 CLAMP180(rot_current_angle);
945 _global_rot_current_angle_kludge = rot_current_angle;
949 CGSize rotsize = ((ignore_rotation_p || ![self reshapeRotatedWindow])
952 if ((int) backbuffer_size.width != (int) rotsize.width ||
953 (int) backbuffer_size.height != (int) rotsize.height)
958 - (void)alertView:(UIAlertView *)av clickedButtonAtIndex:(NSInteger)i
960 if (i == 0) exit (-1); // Cancel
961 [self stopAndClose:NO]; // Keep going
964 - (void) handleException: (NSException *)e
966 NSLog (@"Caught exception: %@", e);
967 [[[UIAlertView alloc] initWithTitle:
968 [NSString stringWithFormat: @"%s crashed!",
971 [NSString stringWithFormat:
972 @"The error message was:"
974 "If it keeps crashing, try "
975 "resetting its options.",
978 cancelButtonTitle: @"Exit"
979 otherButtonTitles: @"Keep going", nil]
981 [self stopAnimation];
987 #ifdef USE_BACKBUFFER
993 // iOS always uses OpenGL ES 1.1.
999 gl_check_ver (const struct gl_version *caps,
1003 return caps->major > gl_major ||
1004 (caps->major == gl_major && caps->minor >= gl_minor);
1010 gluCheckExtension (const GLubyte *ext_name, const GLubyte *ext_string)
1012 size_t ext_len = strlen ((const char *)ext_name);
1015 const GLubyte *found = (const GLubyte *)strstr ((const char *)ext_string,
1016 (const char *)ext_name);
1020 char last_ch = found[ext_len];
1021 if ((found == ext_string || found[-1] == ' ') &&
1022 (last_ch == ' ' || !last_ch)) {
1026 ext_string = found + ext_len;
1034 /* Called during startAnimation before the first call to createBackbuffer. */
1035 - (void) enableBackbuffer:(CGSize)new_backbuffer_size
1038 struct gl_version version;
1041 const char *version_str = (const char *)glGetString (GL_VERSION);
1043 /* iPhone is always OpenGL ES 1.1. */
1044 if (sscanf ((const char *)version_str, "%u.%u",
1045 &version.major, &version.minor) < 2)
1053 // The OpenGL extensions in use in here are pretty are pretty much ubiquitous
1054 // on OS X, but it's still good form to check.
1055 const GLubyte *extensions = glGetString (GL_EXTENSIONS);
1057 glGenTextures (1, &backbuffer_texture);
1059 // On really old systems, it would make sense to split the texture
1062 gl_texture_target = (gluCheckExtension ((const GLubyte *)
1063 "GL_ARB_texture_rectangle",
1065 ? GL_TEXTURE_RECTANGLE_EXT : GL_TEXTURE_2D);
1067 // OES_texture_npot also provides this, but iOS never provides it.
1068 gl_limited_npot_p = gluCheckExtension ((const GLubyte *)
1069 "GL_APPLE_texture_2D_limited_npot",
1071 gl_texture_target = GL_TEXTURE_2D;
1074 glBindTexture (gl_texture_target, &backbuffer_texture);
1075 glTexParameteri (gl_texture_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
1076 // GL_LINEAR might make sense on Retina iPads.
1077 glTexParameteri (gl_texture_target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
1078 glTexParameteri (gl_texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
1079 glTexParameteri (gl_texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
1082 // There isn't much sense in supporting one of these if the other
1084 gl_apple_client_storage_p =
1085 gluCheckExtension ((const GLubyte *)"GL_APPLE_client_storage",
1087 gluCheckExtension ((const GLubyte *)"GL_APPLE_texture_range", extensions);
1089 if (gl_apple_client_storage_p) {
1090 glTexParameteri (gl_texture_target, GL_TEXTURE_STORAGE_HINT_APPLE,
1091 GL_STORAGE_SHARED_APPLE);
1092 glPixelStorei (GL_UNPACK_CLIENT_STORAGE_APPLE, GL_TRUE);
1096 // If a video adapter suports BGRA textures, then that's probably as fast as
1097 // you're gonna get for getting a texture onto the screen.
1100 gluCheckExtension ((const GLubyte *)"GL_APPLE_texture_format_BGRA8888",
1101 extensions) ? GL_BGRA : GL_RGBA;
1102 gl_pixel_type = GL_UNSIGNED_BYTE;
1103 // See also OES_read_format.
1105 if (gl_check_ver (&version, 1, 2) ||
1106 (gluCheckExtension ((const GLubyte *)"GL_EXT_bgra", extensions) &&
1107 gluCheckExtension ((const GLubyte *)"GL_APPLE_packed_pixels",
1109 gl_pixel_format = GL_BGRA;
1110 // Both Intel and PowerPC-era docs say to use GL_UNSIGNED_INT_8_8_8_8_REV.
1111 gl_pixel_type = GL_UNSIGNED_INT_8_8_8_8_REV;
1113 gl_pixel_format = GL_RGBA;
1114 gl_pixel_type = GL_UNSIGNED_BYTE;
1116 // GL_ABGR_EXT/GL_UNSIGNED_BYTE is another possibilty that may have made more
1117 // sense on PowerPC.
1120 glEnable (gl_texture_target);
1121 glEnableClientState (GL_VERTEX_ARRAY);
1122 glEnableClientState (GL_TEXTURE_COORD_ARRAY);
1125 glMatrixMode (GL_PROJECTION);
1127 NSAssert (new_backbuffer_size.width != 0 && new_backbuffer_size.height != 0,
1128 @"initial_bounds never got set");
1129 // This is pretty similar to the glOrtho in createBackbuffer for OS X.
1130 glOrthof (-new_backbuffer_size.width, new_backbuffer_size.width,
1131 -new_backbuffer_size.height, new_backbuffer_size.height, -1, 1);
1132 # endif // USE_IPHONE
1134 check_gl_error ("enableBackbuffer");
1144 size_t mask = (size_t)-1;
1145 unsigned bits = sizeof(x) * CHAR_BIT;
1146 unsigned log2 = bits;
1163 /* Create a bitmap context into which we render everything.
1164 If the desired size has changed, re-created it.
1165 new_size is in rotated pixels, not points: the same size
1166 and shape as the X11 window as seen by the hacks.
1168 - (void) createBackbuffer:(CGSize)new_size
1170 // Colorspaces and CGContexts only happen with non-GL hacks.
1172 CGColorSpaceRelease (colorspace);
1174 # ifdef BACKBUFFER_OPENGL
1175 NSAssert ([NSOpenGLContext currentContext] ==
1176 ogl_ctx, @"invalid GL context");
1178 // This almost isn't necessary, except for the ugly aliasing artifacts.
1180 glViewport (0, 0, new_size.width, new_size.height);
1182 glMatrixMode (GL_PROJECTION);
1184 // This is pretty similar to the glOrthof in enableBackbuffer for iPhone.
1185 glOrtho (-new_size.width, new_size.width, -new_size.height, new_size.height,
1187 # endif // !USE_IPHONE
1188 # endif // BACKBUFFER_OPENGL
1190 NSWindow *window = [self window];
1192 if (window && xdpy) {
1195 # ifdef BACKBUFFER_OPENGL
1196 // Was apparently faster until 10.9.
1197 colorspace = CGColorSpaceCreateDeviceRGB ();
1198 # endif // BACKBUFFER_OPENGL
1202 colorspace = CGColorSpaceCreateDeviceRGB();
1206 (int)backbuffer_size.width == (int)new_size.width &&
1207 (int)backbuffer_size.height == (int)new_size.height)
1210 CGContextRef ob = backbuffer;
1211 void *odata = backbuffer_data;
1212 size_t olen = backbuffer_len;
1214 CGSize osize = backbuffer_size; // pixels, not points.
1215 backbuffer_size = new_size; // pixels, not points.
1217 # if TARGET_IPHONE_SIMULATOR
1218 NSLog(@"backbuffer %.0fx%.0f",
1219 backbuffer_size.width, backbuffer_size.height);
1222 /* OS X uses APPLE_client_storage and APPLE_texture_range, as described in
1223 <https://developer.apple.com/library/mac/documentation/GraphicsImaging/Conceptual/OpenGL-MacProgGuide/opengl_texturedata/opengl_texturedata.html>.
1225 iOS uses bog-standard glTexImage2D (for now).
1227 glMapBuffer is the standard way to get data from system RAM to video
1228 memory asynchronously and without a memcpy, but support for
1229 APPLE_client_storage is ubiquitous on OS X (not so for glMapBuffer),
1230 and on iOS GL_PIXEL_UNPACK_BUFFER is only available on OpenGL ES 3
1231 (iPhone 5S or newer). Plus, glMapBuffer doesn't work well with
1232 CGBitmapContext: glMapBuffer can return a different pointer on each
1233 call, but a CGBitmapContext doesn't allow its data pointer to be
1234 changed -- and recreating the context for a new pointer can be
1235 expensive (glyph caches get dumped, for instance).
1237 glMapBufferRange has MAP_FLUSH_EXPLICIT_BIT and MAP_UNSYNCHRONIZED_BIT,
1238 and these seem to allow mapping the buffer and leaving it where it is
1239 in client address space while OpenGL works with the buffer, but it
1240 requires OpenGL 3 Core profile on OS X (and ES 3 on iOS for
1241 GL_PIXEL_UNPACK_BUFFER), so point goes to APPLE_client_storage.
1243 AMD_pinned_buffer provides the same advantage as glMapBufferRange, but
1244 Apple never implemented that one for OS X.
1247 backbuffer_data = NULL;
1248 gl_texture_w = (int)backbuffer_size.width;
1249 gl_texture_h = (int)backbuffer_size.height;
1251 NSAssert (gl_texture_target == GL_TEXTURE_2D
1253 || gl_texture_target == GL_TEXTURE_RECTANGLE_EXT
1255 , @"unexpected GL texture target");
1258 if (gl_texture_target != GL_TEXTURE_RECTANGLE_EXT)
1260 if (!gl_limited_npot_p)
1263 gl_texture_w = to_pow2 (gl_texture_w);
1264 gl_texture_h = to_pow2 (gl_texture_h);
1267 size_t bytes_per_row = gl_texture_w * 4;
1269 # if defined(BACKBUFFER_OPENGL) && !defined(USE_IPHONE)
1270 // APPLE_client_storage requires texture width to be aligned to 32 bytes, or
1271 // it will fall back to a memcpy.
1272 // https://developer.apple.com/library/mac/documentation/GraphicsImaging/Conceptual/OpenGL-MacProgGuide/opengl_texturedata/opengl_texturedata.html#//apple_ref/doc/uid/TP40001987-CH407-SW24
1273 bytes_per_row = (bytes_per_row + 31) & ~31;
1274 # endif // BACKBUFFER_OPENGL && !USE_IPHONE
1276 backbuffer_len = bytes_per_row * gl_texture_h;
1277 if (backbuffer_len) // mmap requires this to be non-zero.
1278 backbuffer_data = mmap (NULL, backbuffer_len,
1279 PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED,
1282 BOOL alpha_first_p, order_little_p;
1284 if (gl_pixel_format == GL_BGRA) {
1285 alpha_first_p = YES;
1286 order_little_p = YES;
1288 } else if (gl_pixel_format == GL_ABGR_EXT) {
1290 order_little_p = YES; */
1292 NSAssert (gl_pixel_format == GL_RGBA, @"unknown GL pixel format");
1294 order_little_p = NO;
1298 NSAssert (gl_pixel_type == GL_UNSIGNED_BYTE, @"unknown GL pixel type");
1300 NSAssert (gl_pixel_type == GL_UNSIGNED_INT_8_8_8_8 ||
1301 gl_pixel_type == GL_UNSIGNED_INT_8_8_8_8_REV ||
1302 gl_pixel_type == GL_UNSIGNED_BYTE,
1303 @"unknown GL pixel type");
1305 #if defined __LITTLE_ENDIAN__
1306 const GLenum backwards_pixel_type = GL_UNSIGNED_INT_8_8_8_8;
1307 #elif defined __BIG_ENDIAN__
1308 const GLenum backwards_pixel_type = GL_UNSIGNED_INT_8_8_8_8_REV;
1310 # error Unknown byte order.
1313 if (gl_pixel_type == backwards_pixel_type)
1314 order_little_p ^= YES;
1317 CGBitmapInfo bitmap_info =
1318 (alpha_first_p ? kCGImageAlphaNoneSkipFirst : kCGImageAlphaNoneSkipLast) |
1319 (order_little_p ? kCGBitmapByteOrder32Little : kCGBitmapByteOrder32Big);
1321 backbuffer = CGBitmapContextCreate (backbuffer_data,
1322 (int)backbuffer_size.width,
1323 (int)backbuffer_size.height,
1328 NSAssert (backbuffer, @"unable to allocate back buffer");
1332 r.origin.x = r.origin.y = 0;
1333 r.size = backbuffer_size;
1334 CGContextSetGrayFillColor (backbuffer, 0, 1);
1335 CGContextFillRect (backbuffer, r);
1337 # if defined(BACKBUFFER_OPENGL) && !defined(USE_IPHONE)
1338 if (gl_apple_client_storage_p)
1339 glTextureRangeAPPLE (gl_texture_target, backbuffer_len, backbuffer_data);
1340 # endif // BACKBUFFER_OPENGL && !USE_IPHONE
1343 // Restore old bits, as much as possible, to the X11 upper left origin.
1345 CGRect rect; // pixels, not points
1347 rect.origin.y = (backbuffer_size.height - osize.height);
1350 CGImageRef img = CGBitmapContextCreateImage (ob);
1351 CGContextDrawImage (backbuffer, rect, img);
1352 CGImageRelease (img);
1353 CGContextRelease (ob);
1356 // munmap should round len up to the nearest page.
1357 munmap (odata, olen);
1360 check_gl_error ("createBackbuffer");
1364 - (void) drawBackbuffer
1366 # ifdef BACKBUFFER_OPENGL
1368 NSAssert ([ogl_ctx isKindOfClass:[NSOpenGLContext class]],
1369 @"ogl_ctx is not an NSOpenGLContext");
1371 NSAssert (! (CGBitmapContextGetBytesPerRow (backbuffer) % 4),
1372 @"improperly-aligned backbuffer");
1374 // This gets width and height from the backbuffer in case
1375 // APPLE_client_storage is in use. See the note in createBackbuffer.
1376 // This still has to happen every frame even when APPLE_client_storage has
1377 // the video adapter pulling texture data straight from
1378 // XScreenSaverView-owned memory.
1379 glTexImage2D (gl_texture_target, 0, GL_RGBA,
1380 (GLsizei)(CGBitmapContextGetBytesPerRow (backbuffer) / 4),
1381 gl_texture_h, 0, gl_pixel_format, gl_pixel_type,
1384 GLfloat vertices[4][2] =
1386 {-backbuffer_size.width, backbuffer_size.height},
1387 { backbuffer_size.width, backbuffer_size.height},
1388 { backbuffer_size.width, -backbuffer_size.height},
1389 {-backbuffer_size.width, -backbuffer_size.height}
1392 GLfloat tex_coords[4][2];
1395 if (gl_texture_target == GL_TEXTURE_RECTANGLE_EXT) {
1396 tex_coords[0][0] = 0;
1397 tex_coords[0][1] = 0;
1398 tex_coords[1][0] = backbuffer_size.width;
1399 tex_coords[1][1] = 0;
1400 tex_coords[2][0] = backbuffer_size.width;
1401 tex_coords[2][1] = backbuffer_size.height;
1402 tex_coords[3][0] = 0;
1403 tex_coords[3][1] = backbuffer_size.height;
1405 # endif // USE_IPHONE
1407 GLfloat x = backbuffer_size.width / gl_texture_w;
1408 GLfloat y = backbuffer_size.height / gl_texture_h;
1409 tex_coords[0][0] = 0;
1410 tex_coords[0][1] = 0;
1411 tex_coords[1][0] = x;
1412 tex_coords[1][1] = 0;
1413 tex_coords[2][0] = x;
1414 tex_coords[2][1] = y;
1415 tex_coords[3][0] = 0;
1416 tex_coords[3][1] = y;
1420 if (!ignore_rotation_p) {
1421 glMatrixMode (GL_MODELVIEW);
1423 glRotatef (rot_current_angle, 0, 0, -1);
1425 if (rotation_ratio >= 0)
1426 glClear (GL_COLOR_BUFFER_BIT);
1428 # endif // USE_IPHONE
1430 glVertexPointer (2, GL_FLOAT, 0, vertices);
1431 glTexCoordPointer (2, GL_FLOAT, 0, tex_coords);
1432 glDrawArrays (GL_TRIANGLE_FAN, 0, 4);
1434 # if !defined __OPTIMIZE__ || TARGET_IPHONE_SIMULATOR
1435 check_gl_error ("drawBackbuffer");
1438 // This can also happen near the beginning of render_x11.
1439 [self flushBackbuffer];
1441 # endif // BACKBUFFER_OPENGL
1445 - (void)flushBackbuffer
1448 // The OpenGL pipeline is not automatically synchronized with the contents
1449 // of the backbuffer, so without glFinish, OpenGL can start rendering from
1450 // the backbuffer texture at the same time that JWXYZ is clearing and
1451 // drawing the next frame in the backing store for the backbuffer texture.
1454 if (double_buffered_p)
1455 [ogl_ctx flushBuffer]; // despite name, this actually swaps
1457 glBindRenderbufferOES (GL_RENDERBUFFER_OES, gl_renderbuffer);
1458 [ogl_ctx presentRenderbuffer:GL_RENDERBUFFER_OES];
1461 # if !defined __OPTIMIZE__ || TARGET_IPHONE_SIMULATOR
1462 // glGetError waits for the OpenGL command pipe to flush, so skip it in
1464 // OpenGL Programming Guide for Mac -> OpenGL Application Design
1465 // Strategies -> Allow OpenGL to Manage Your Resources
1466 // https://developer.apple.com/library/mac/documentation/GraphicsImaging/Conceptual/OpenGL-MacProgGuide/opengl_designstrategies/opengl_designstrategies.html#//apple_ref/doc/uid/TP40001987-CH2-SW7
1467 check_gl_error ("flushBackbuffer");
1472 #endif // USE_BACKBUFFER
1475 /* Inform X11 that the size of our window has changed.
1479 if (!xwindow) return; // early
1481 CGSize new_size; // pixels, not points
1483 # ifdef USE_BACKBUFFER
1485 [self prepareContext];
1487 # if defined(BACKBUFFER_OPENGL) && !defined(USE_IPHONE)
1489 # endif // BACKBUFFER_OPENGL && !USE_IPHONE
1492 CGSize rotsize = ((ignore_rotation_p || ![self reshapeRotatedWindow])
1494 : rot_current_size);
1495 new_size.width = rotsize.width;
1496 new_size.height = rotsize.height;
1497 # else // !USE_IPHONE
1498 new_size = NSSizeToCGSize([self bounds].size);
1499 # endif // !USE_IPHONE
1501 [self createBackbuffer:new_size];
1502 jwxyz_window_resized (xdpy, xwindow, 0, 0, new_size.width, new_size.height,
1504 # else // !USE_BACKBUFFER
1505 new_size = [self bounds].size;
1506 jwxyz_window_resized (xdpy, xwindow, 0, 0, new_size.width, new_size.height,
1508 # endif // !USE_BACKBUFFER
1510 # if TARGET_IPHONE_SIMULATOR
1511 NSLog(@"reshape %.0fx%.0f", new_size.width, new_size.height);
1514 // Next time render_x11 is called, run the saver's reshape_cb.
1524 if (orientation == UIDeviceOrientationUnknown)
1525 [self didRotate:nil];
1526 [self hackRotation];
1532 # ifdef USE_BACKBUFFER
1533 NSAssert (backbuffer, @"no back buffer");
1534 xdpy = jwxyz_make_display (self, backbuffer);
1536 xdpy = jwxyz_make_display (self, 0);
1538 xwindow = XRootWindow (xdpy, 0);
1541 /* Some X11 hacks (fluidballs) want to ignore all rotation events. */
1543 get_boolean_resource (xdpy, "ignoreRotation", "IgnoreRotation");
1544 # endif // USE_IPHONE
1552 xsft->setup_cb (xsft, xsft->setup_arg);
1556 NSAssert(!xdata, @"xdata already initialized");
1559 # undef ya_rand_init
1562 XSetWindowBackground (xdpy, xwindow,
1563 get_pixel_resource (xdpy, 0,
1564 "background", "Background"));
1565 XClearWindow (xdpy, xwindow);
1568 [[self window] setAcceptsMouseMovedEvents:YES];
1571 /* In MacOS 10.5, this enables "QuartzGL", meaning that the Quartz
1572 drawing primitives will run on the GPU instead of the CPU.
1573 It seems like it might make things worse rather than better,
1574 though... Plus it makes us binary-incompatible with 10.4.
1576 # if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
1577 [[self window] setPreferredBackingLocation:
1578 NSWindowBackingLocationVideoMemory];
1582 /* Kludge: even though the init_cb functions are declared to take 2 args,
1583 actually call them with 3, for the benefit of xlockmore_init() and
1586 void *(*init_cb) (Display *, Window, void *) =
1587 (void *(*) (Display *, Window, void *)) xsft->init_cb;
1589 xdata = init_cb (xdpy, xwindow, xsft->setup_arg);
1590 // NSAssert(xdata, @"no xdata from init");
1591 if (! xdata) abort();
1593 if (get_boolean_resource (xdpy, "doFPS", "DoFPS")) {
1594 fpst = fps_init (xdpy, xwindow);
1595 if (! xsft->fps_cb) xsft->fps_cb = screenhack_do_fps;
1601 [self checkForUpdates];
1605 /* I don't understand why we have to do this *every frame*, but we do,
1606 or else the cursor comes back on.
1609 if (![self isPreview])
1610 [NSCursor setHiddenUntilMouseMoves:YES];
1616 /* This is just a guess, but the -fps code wants to know how long
1617 we were sleeping between frames.
1619 long usecs = 1000000 * [self animationTimeInterval];
1620 usecs -= 200; // caller apparently sleeps for slightly less sometimes...
1621 if (usecs < 0) usecs = 0;
1622 fps_slept (fpst, usecs);
1626 /* It turns out that on some systems (possibly only 10.5 and older?)
1627 [ScreenSaverView setAnimationTimeInterval] does nothing. This means
1628 that we cannot rely on it.
1630 Some of the screen hacks want to delay for long periods, and letting the
1631 framework run the update function at 30 FPS when it really wanted half a
1632 minute between frames would be bad. So instead, we assume that the
1633 framework's animation timer might fire whenever, but we only invoke the
1634 screen hack's "draw frame" method when enough time has expired.
1636 This means two extra calls to gettimeofday() per frame. For fast-cycling
1637 screen savers, that might actually slow them down. Oh well.
1639 A side-effect of this is that it's not possible for a saver to request
1640 an animation interval that is faster than animationTimeInterval.
1642 HOWEVER! On modern systems where setAnimationTimeInterval is *not*
1643 ignored, it's important that it be faster than 30 FPS. 240 FPS is good.
1645 An NSTimer won't fire if the timer is already running the invocation
1646 function from a previous firing. So, if we use a 30 FPS
1647 animationTimeInterval (33333 µs) and a screenhack takes 40000 µs for a
1648 frame, there will be a 26666 µs delay until the next frame, 66666 µs
1649 after the beginning of the current frame. In other words, 25 FPS
1652 Frame rates tend to snap to values of 30/N, where N is a positive
1653 integer, i.e. 30 FPS, 15 FPS, 10, 7.5, 6. And the 'snapped' frame rate
1654 is rounded down from what it would normally be.
1656 So if we set animationTimeInterval to 1/240 instead of 1/30, frame rates
1657 become values of 60/N, 120/N, or 240/N, with coarser or finer frame rate
1658 steps for higher or lower animation time intervals respectively.
1661 gettimeofday (&tv, 0);
1662 double now = tv.tv_sec + (tv.tv_usec / 1000000.0);
1663 if (now < next_frame_time) return;
1665 [self prepareContext]; // resize_x11 also calls this.
1666 // [self flushBackbuffer];
1669 // We do this here instead of in setFrame so that all the
1670 // Xlib drawing takes place under the animation timer.
1674 [ogl_ctx setView:self];
1675 # endif // !USE_IPHONE
1678 # ifndef USE_BACKBUFFER
1680 # else // USE_BACKBUFFER
1683 r.size.width = backbuffer_size.width;
1684 r.size.height = backbuffer_size.height;
1685 # endif // USE_BACKBUFFER
1687 xsft->reshape_cb (xdpy, xwindow, xdata, r.size.width, r.size.height);
1691 // Run any XtAppAddInput callbacks now.
1692 // (Note that XtAppAddTimeOut callbacks have already been run by
1693 // the Cocoa event loop.)
1695 jwxyz_sources_run (display_sources_data (xdpy));
1700 // NSAssert(xdata, @"no xdata when drawing");
1701 if (! xdata) abort();
1702 unsigned long delay = xsft->draw_cb (xdpy, xwindow, xdata);
1703 if (fpst && xsft->fps_cb)
1704 xsft->fps_cb (xdpy, xwindow, fpst, xdata);
1706 gettimeofday (&tv, 0);
1707 now = tv.tv_sec + (tv.tv_usec / 1000000.0);
1708 next_frame_time = now + (delay / 1000000.0);
1710 [self drawBackbuffer];
1712 # ifdef USE_IPHONE // Allow savers on the iPhone to run full-tilt.
1713 if (delay < [self animationTimeInterval])
1714 [self setAnimationTimeInterval:(delay / 1000000.0)];
1717 # ifdef DO_GC_HACKERY
1718 /* Current theory is that the 10.6 garbage collector sucks in the
1721 It only does a collection when a threshold of outstanding
1722 collectable allocations has been surpassed. However, CoreGraphics
1723 creates lots of small collectable allocations that contain pointers
1724 to very large non-collectable allocations: a small CG object that's
1725 collectable referencing large malloc'd allocations (non-collectable)
1726 containing bitmap data. So the large allocation doesn't get freed
1727 until GC collects the small allocation, which triggers its finalizer
1728 to run which frees the large allocation. So GC is deciding that it
1729 doesn't really need to run, even though the process has gotten
1730 enormous. GC eventually runs once pageouts have happened, but by
1731 then it's too late, and the machine's resident set has been
1734 So, we force an exhaustive garbage collection in this process
1735 approximately every 5 seconds whether the system thinks it needs
1739 static int tick = 0;
1740 if (++tick > 5*30) {
1742 objc_collect (OBJC_EXHAUSTIVE_COLLECTION);
1745 # endif // DO_GC_HACKERY
1749 @catch (NSException *e) {
1750 [self handleException: e];
1752 # endif // USE_IPHONE
1756 #ifndef USE_BACKBUFFER
1758 - (void) animateOneFrame
1761 jwxyz_flush_context(xdpy);
1764 #else // USE_BACKBUFFER
1766 - (void) animateOneFrame
1768 // Render X11 into the backing store bitmap...
1770 NSAssert (backbuffer, @"no back buffer");
1773 UIGraphicsPushContext (backbuffer);
1778 # if defined USE_IPHONE && defined USE_BACKBUFFER
1779 UIGraphicsPopContext();
1783 #endif // USE_BACKBUFFER
1787 - (void) setFrame:(NSRect) newRect
1789 [super setFrame:newRect];
1791 if (xwindow) // inform Xlib that the window has changed now.
1796 # ifndef USE_IPHONE // Doesn't exist on iOS
1797 - (void) setFrameSize:(NSSize) newSize
1799 [super setFrameSize:newSize];
1803 # endif // !USE_IPHONE
1806 +(BOOL) performGammaFade
1811 - (BOOL) hasConfigureSheet
1816 + (NSString *) decompressXML: (NSData *)data
1818 if (! data) return 0;
1819 BOOL compressed_p = !!strncmp ((const char *) data.bytes, "<?xml", 5);
1821 // If it's not already XML, decompress it.
1822 NSAssert (compressed_p, @"xml isn't compressed");
1824 NSMutableData *data2 = 0;
1827 memset (&zs, 0, sizeof(zs));
1828 ret = inflateInit2 (&zs, 16 + MAX_WBITS);
1830 UInt32 usize = * (UInt32 *) (data.bytes + data.length - 4);
1831 data2 = [NSMutableData dataWithLength: usize];
1832 zs.next_in = (Bytef *) data.bytes;
1833 zs.avail_in = (uint) data.length;
1834 zs.next_out = (Bytef *) data2.bytes;
1835 zs.avail_out = (uint) data2.length;
1836 ret = inflate (&zs, Z_FINISH);
1839 if (ret == Z_OK || ret == Z_STREAM_END)
1842 NSAssert2 (0, @"gunzip error: %d: %s",
1843 ret, (zs.msg ? zs.msg : "<null>"));
1846 return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
1851 - (NSWindow *) configureSheet
1853 - (UIViewController *) configureView
1856 NSBundle *bundle = [NSBundle bundleForClass:[self class]];
1857 NSString *file = [NSString stringWithCString:xsft->progclass
1858 encoding:NSISOLatin1StringEncoding];
1859 file = [file lowercaseString];
1860 NSString *path = [bundle pathForResource:file ofType:@"xml"];
1862 NSLog (@"%@.xml does not exist in the application bundle: %@/",
1863 file, [bundle resourcePath]);
1868 UIViewController *sheet;
1869 # else // !USE_IPHONE
1871 # endif // !USE_IPHONE
1873 NSData *xmld = [NSData dataWithContentsOfFile:path];
1874 NSString *xml = [[self class] decompressXML: xmld];
1875 sheet = [[XScreenSaverConfigSheet alloc]
1876 initWithXML:[xml dataUsingEncoding:NSUTF8StringEncoding]
1877 options:xsft->options
1878 controller:[prefsReader userDefaultsController]
1879 globalController:[prefsReader globalDefaultsController]
1880 defaults:[prefsReader defaultOptions]];
1882 // #### am I expected to retain this, or not? wtf.
1883 // I thought not, but if I don't do this, we (sometimes) crash.
1884 // #### Analyze says "potential leak of an object stored into sheet"
1891 - (NSUserDefaultsController *) userDefaultsController
1893 return [prefsReader userDefaultsController];
1897 /* Announce our willingness to accept keyboard input.
1899 - (BOOL)acceptsFirstResponder
1909 # else // USE_IPHONE
1911 // There's no way to play a standard system alert sound!
1912 // We'd have to include our own WAV for that.
1914 // Or we could vibrate:
1915 // #import <AudioToolbox/AudioToolbox.h>
1916 // AudioServicesPlaySystemSound (kSystemSoundID_Vibrate);
1918 // Instead, just flash the screen white, then fade.
1920 UIView *v = [[UIView alloc] initWithFrame: [self frame]];
1921 [v setBackgroundColor: [UIColor whiteColor]];
1922 [[self window] addSubview:v];
1923 [UIView animateWithDuration: 0.1
1924 animations:^{ [v setAlpha: 0.0]; }
1925 completion:^(BOOL finished) { [v removeFromSuperview]; } ];
1927 # endif // USE_IPHONE
1931 /* Send an XEvent to the hack. Returns YES if it was handled.
1933 - (BOOL) sendEvent: (XEvent *) e
1935 if (!initted_p || ![self isAnimating]) // no event handling unless running.
1939 [self prepareContext];
1940 BOOL result = xsft->event_cb (xdpy, xwindow, xdata, e);
1948 /* Convert an NSEvent into an XEvent, and pass it along.
1949 Returns YES if it was handled.
1951 - (BOOL) convertEvent: (NSEvent *) e
1955 memset (&xe, 0, sizeof(xe));
1959 int flags = [e modifierFlags];
1960 if (flags & NSAlphaShiftKeyMask) state |= LockMask;
1961 if (flags & NSShiftKeyMask) state |= ShiftMask;
1962 if (flags & NSControlKeyMask) state |= ControlMask;
1963 if (flags & NSAlternateKeyMask) state |= Mod1Mask;
1964 if (flags & NSCommandKeyMask) state |= Mod2Mask;
1966 NSPoint p = [[[e window] contentView] convertPoint:[e locationInWindow]
1969 double s = [self hackedContentScaleFactor];
1974 int y = s * ([self bounds].size.height - p.y);
1976 xe.xany.type = type;
1982 xe.xbutton.state = state;
1983 if ([e type] == NSScrollWheel)
1984 xe.xbutton.button = ([e deltaY] > 0 ? Button4 :
1985 [e deltaY] < 0 ? Button5 :
1986 [e deltaX] > 0 ? Button6 :
1987 [e deltaX] < 0 ? Button7 :
1990 xe.xbutton.button = [e buttonNumber] + 1;
1995 xe.xmotion.state = state;
2000 NSString *ns = (([e type] == NSFlagsChanged) ? 0 :
2001 [e charactersIgnoringModifiers]);
2004 if (!ns || [ns length] == 0) // dead key
2006 // Cocoa hides the difference between left and right keys.
2007 // Also we only get KeyPress events for these, no KeyRelease
2008 // (unless we hack the mod state manually. Bleh.)
2010 if (flags & NSAlphaShiftKeyMask) k = XK_Caps_Lock;
2011 else if (flags & NSShiftKeyMask) k = XK_Shift_L;
2012 else if (flags & NSControlKeyMask) k = XK_Control_L;
2013 else if (flags & NSAlternateKeyMask) k = XK_Alt_L;
2014 else if (flags & NSCommandKeyMask) k = XK_Meta_L;
2016 else if ([ns length] == 1) // real key
2018 switch ([ns characterAtIndex:0]) {
2019 case NSLeftArrowFunctionKey: k = XK_Left; break;
2020 case NSRightArrowFunctionKey: k = XK_Right; break;
2021 case NSUpArrowFunctionKey: k = XK_Up; break;
2022 case NSDownArrowFunctionKey: k = XK_Down; break;
2023 case NSPageUpFunctionKey: k = XK_Page_Up; break;
2024 case NSPageDownFunctionKey: k = XK_Page_Down; break;
2025 case NSHomeFunctionKey: k = XK_Home; break;
2026 case NSPrevFunctionKey: k = XK_Prior; break;
2027 case NSNextFunctionKey: k = XK_Next; break;
2028 case NSBeginFunctionKey: k = XK_Begin; break;
2029 case NSEndFunctionKey: k = XK_End; break;
2030 case NSF1FunctionKey: k = XK_F1; break;
2031 case NSF2FunctionKey: k = XK_F2; break;
2032 case NSF3FunctionKey: k = XK_F3; break;
2033 case NSF4FunctionKey: k = XK_F4; break;
2034 case NSF5FunctionKey: k = XK_F5; break;
2035 case NSF6FunctionKey: k = XK_F6; break;
2036 case NSF7FunctionKey: k = XK_F7; break;
2037 case NSF8FunctionKey: k = XK_F8; break;
2038 case NSF9FunctionKey: k = XK_F9; break;
2039 case NSF10FunctionKey: k = XK_F10; break;
2040 case NSF11FunctionKey: k = XK_F11; break;
2041 case NSF12FunctionKey: k = XK_F12; break;
2045 [ns cStringUsingEncoding:NSISOLatin1StringEncoding];
2046 k = (s && *s ? *s : 0);
2052 if (! k) return YES; // E.g., "KeyRelease XK_Shift_L"
2054 xe.xkey.keycode = k;
2055 xe.xkey.state = state;
2059 NSAssert1 (0, @"unknown X11 event type: %d", type);
2063 return [self sendEvent: &xe];
2067 - (void) mouseDown: (NSEvent *) e
2069 if (! [self convertEvent:e type:ButtonPress])
2070 [super mouseDown:e];
2073 - (void) mouseUp: (NSEvent *) e
2075 if (! [self convertEvent:e type:ButtonRelease])
2079 - (void) otherMouseDown: (NSEvent *) e
2081 if (! [self convertEvent:e type:ButtonPress])
2082 [super otherMouseDown:e];
2085 - (void) otherMouseUp: (NSEvent *) e
2087 if (! [self convertEvent:e type:ButtonRelease])
2088 [super otherMouseUp:e];
2091 - (void) mouseMoved: (NSEvent *) e
2093 if (! [self convertEvent:e type:MotionNotify])
2094 [super mouseMoved:e];
2097 - (void) mouseDragged: (NSEvent *) e
2099 if (! [self convertEvent:e type:MotionNotify])
2100 [super mouseDragged:e];
2103 - (void) otherMouseDragged: (NSEvent *) e
2105 if (! [self convertEvent:e type:MotionNotify])
2106 [super otherMouseDragged:e];
2109 - (void) scrollWheel: (NSEvent *) e
2111 if (! [self convertEvent:e type:ButtonPress])
2112 [super scrollWheel:e];
2115 - (void) keyDown: (NSEvent *) e
2117 if (! [self convertEvent:e type:KeyPress])
2121 - (void) keyUp: (NSEvent *) e
2123 if (! [self convertEvent:e type:KeyRelease])
2127 - (void) flagsChanged: (NSEvent *) e
2129 if (! [self convertEvent:e type:KeyPress])
2130 [super flagsChanged:e];
2134 - (NSOpenGLPixelFormat *) getGLPixelFormat
2136 NSAssert (prefsReader, @"no prefsReader for getGLPixelFormat");
2138 NSOpenGLPixelFormatAttribute attrs[40];
2140 attrs[i++] = NSOpenGLPFAColorSize; attrs[i++] = 24;
2142 if (double_buffered_p)
2143 attrs[i++] = NSOpenGLPFADoubleBuffer;
2147 return [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs];
2153 - (void) stopAndClose:(Bool)relaunch_p
2155 if ([self isAnimating])
2156 [self stopAnimation];
2158 /* Need to make the SaverListController be the firstResponder again
2159 so that it can continue to receive its own shake events. I
2160 suppose that this abstraction-breakage means that I'm adding
2161 XScreenSaverView to the UINavigationController wrong...
2163 // UIViewController *v = [[self window] rootViewController];
2164 // if ([v isKindOfClass: [UINavigationController class]]) {
2165 // UINavigationController *n = (UINavigationController *) v;
2166 // [[n topViewController] becomeFirstResponder];
2168 [self resignFirstResponder];
2170 if (relaunch_p) { // Fake a shake on the SaverListController.
2171 [_delegate didShake:self];
2172 } else { // Not launching another, animate our return to the list.
2173 # if TARGET_IPHONE_SIMULATOR
2174 NSLog (@"fading back to saver list");
2176 [_delegate wantsFadeOut:self];
2181 /* Whether the shape of the X11 Window should be changed to HxW when the
2182 device is in a landscape orientation. X11 hacks want this, but OpenGL
2185 - (BOOL)reshapeRotatedWindow
2191 /* Called after the device's orientation has changed.
2193 Rotation is complicated: the UI, X11 and OpenGL work in 3 different ways.
2195 The UI (list of savers, preferences panels) is rotated by the system,
2196 because its UIWindow is under a UINavigationController that does
2197 automatic rotation, using Core Animation.
2199 The savers are under a different UIWindow and a UINavigationController
2200 that does not do automatic rotation.
2202 We have to do it this way because using Core Animation on an EAGLContext
2203 causes the OpenGL pipeline used on both X11 and GL savers to fall back on
2204 software rendering and performance goes to hell.
2206 During and after rotation, the size/shape of the X11 window changes,
2207 and ConfigureNotify events are generated.
2209 X11 code (jwxyz) continues to draw into the (reshaped) backbuffer, which is
2210 rendered onto a rotating OpenGL quad.
2212 GL code always recieves a portrait-oriented X11 Window whose size never
2213 changes. The GL COLOR_BUFFER is displayed on the hardware directly and
2214 unrotated, so the GL hacks themselves are responsible for rotating the
2215 GL scene to match current_device_rotation().
2217 Touch events are converted to mouse clicks, and those XEvent coordinates
2218 are reported in the coordinate system currently in use by the X11 window.
2219 Again, GL must convert those.
2221 - (void)didRotate:(NSNotification *)notification
2223 UIDeviceOrientation current = [[UIDevice currentDevice] orientation];
2225 /* Sometimes UIDevice doesn't know the proper orientation, or the device is
2226 face up/face down, so in those cases fall back to the status bar
2227 orientation. The SaverViewController tries to set the status bar to the
2228 proper orientation before it creates the XScreenSaverView; see
2229 _storedOrientation in SaverViewController.
2231 if (current == UIDeviceOrientationUnknown ||
2232 current == UIDeviceOrientationFaceUp ||
2233 current == UIDeviceOrientationFaceDown) {
2234 /* Mind the differences between UIInterfaceOrientation and
2235 UIDeviceOrientaiton:
2236 1. UIInterfaceOrientation does not include FaceUp and FaceDown.
2237 2. LandscapeLeft and LandscapeRight are swapped between the two. But
2238 converting between device and interface orientation doesn't need to
2239 take this into account, because (from the UIInterfaceOrientation
2240 description): "rotating the device requires rotating the content in
2241 the opposite direction."
2243 current = [UIApplication sharedApplication].statusBarOrientation;
2246 /* On the iPad (but not iPhone 3GS, or the simulator) sometimes we get
2247 an orientation change event with an unknown orientation. Those seem
2248 to always be immediately followed by another orientation change with
2249 a *real* orientation change, so let's try just ignoring those bogus
2250 ones and hoping that the real one comes in shortly...
2252 if (current == UIDeviceOrientationUnknown)
2255 if (rotation_ratio >= 0) return; // in the midst of rotation animation
2256 if (orientation == current) return; // no change
2258 new_orientation = current; // current animation target
2259 rotation_ratio = 0; // start animating
2260 rot_start_time = double_time();
2262 switch (orientation) {
2263 case UIDeviceOrientationLandscapeLeft: angle_from = 90; break;
2264 case UIDeviceOrientationLandscapeRight: angle_from = 270; break;
2265 case UIDeviceOrientationPortraitUpsideDown: angle_from = 180; break;
2266 default: angle_from = 0; break;
2269 switch (new_orientation) {
2270 case UIDeviceOrientationLandscapeLeft: angle_to = 90; break;
2271 case UIDeviceOrientationLandscapeRight: angle_to = 270; break;
2272 case UIDeviceOrientationPortraitUpsideDown: angle_to = 180; break;
2273 default: angle_to = 0; break;
2276 switch (orientation) {
2277 case UIDeviceOrientationLandscapeRight: // from landscape
2278 case UIDeviceOrientationLandscapeLeft:
2279 rot_from.width = initial_bounds.height;
2280 rot_from.height = initial_bounds.width;
2282 default: // from portrait
2283 rot_from.width = initial_bounds.width;
2284 rot_from.height = initial_bounds.height;
2288 switch (new_orientation) {
2289 case UIDeviceOrientationLandscapeRight: // to landscape
2290 case UIDeviceOrientationLandscapeLeft:
2291 rot_to.width = initial_bounds.height;
2292 rot_to.height = initial_bounds.width;
2294 default: // to portrait
2295 rot_to.width = initial_bounds.width;
2296 rot_to.height = initial_bounds.height;
2300 # if TARGET_IPHONE_SIMULATOR
2301 NSLog (@"%srotation begun: %s %d -> %s %d; %d x %d",
2302 initted_p ? "" : "initial ",
2303 orientname(orientation), (int) rot_current_angle,
2304 orientname(new_orientation), (int) angle_to,
2305 (int) rot_current_size.width, (int) rot_current_size.height);
2308 // Even though the status bar isn't on the screen, this still does two things:
2309 // 1. It fixes the orientation of the iOS simulator.
2310 // 2. It places the iOS notification center on the expected edge.
2311 // 3. It prevents the notification center from causing rotation events.
2312 [[UIApplication sharedApplication] setStatusBarOrientation:new_orientation
2316 // If we've done a rotation but the saver hasn't been initialized yet,
2317 // don't bother going through an X11 resize, but just do it now.
2318 rot_start_time = 0; // dawn of time
2319 [self hackRotation];
2324 /* We distinguish between taps and drags.
2326 - Drags/pans (down, motion, up) are sent to the saver to handle.
2327 - Single-taps exit the saver.
2328 - Double-taps are sent to the saver as a "Space" keypress.
2329 - Swipes (really, two-finger drags/pans) send Up/Down/Left/RightArrow keys.
2331 This means a saver cannot respond to a single-tap. Only a few try to.
2334 - (void)initGestures
2336 UITapGestureRecognizer *dtap = [[UITapGestureRecognizer alloc]
2338 action:@selector(handleDoubleTap)];
2339 dtap.numberOfTapsRequired = 2;
2340 dtap.numberOfTouchesRequired = 1;
2342 UITapGestureRecognizer *stap = [[UITapGestureRecognizer alloc]
2344 action:@selector(handleTap)];
2345 stap.numberOfTapsRequired = 1;
2346 stap.numberOfTouchesRequired = 1;
2348 UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]
2350 action:@selector(handlePan:)];
2351 pan.maximumNumberOfTouches = 1;
2352 pan.minimumNumberOfTouches = 1;
2354 // I couldn't get Swipe to work, but using a second Pan recognizer works.
2355 UIPanGestureRecognizer *pan2 = [[UIPanGestureRecognizer alloc]
2357 action:@selector(handlePan2:)];
2358 pan2.maximumNumberOfTouches = 2;
2359 pan2.minimumNumberOfTouches = 2;
2361 // Also handle long-touch, and treat that the same as Pan.
2362 // Without this, panning doesn't start until there's motion, so the trick
2363 // of holding down your finger to freeze the scene doesn't work.
2365 UILongPressGestureRecognizer *hold = [[UILongPressGestureRecognizer alloc]
2367 action:@selector(handleLongPress:)];
2368 hold.numberOfTapsRequired = 0;
2369 hold.numberOfTouchesRequired = 1;
2370 hold.minimumPressDuration = 0.25; /* 1/4th second */
2372 [stap requireGestureRecognizerToFail: dtap];
2373 [stap requireGestureRecognizerToFail: hold];
2374 [dtap requireGestureRecognizerToFail: hold];
2375 [pan requireGestureRecognizerToFail: hold];
2377 [self setMultipleTouchEnabled:YES];
2379 [self addGestureRecognizer: dtap];
2380 [self addGestureRecognizer: stap];
2381 [self addGestureRecognizer: pan];
2382 [self addGestureRecognizer: pan2];
2383 [self addGestureRecognizer: hold];
2393 /* Given a mouse (touch) coordinate in unrotated, unscaled view coordinates,
2394 convert it to what X11 and OpenGL expect.
2396 Getting this crap right is tricky, given the confusion of the various
2397 scale factors, so here's a checklist that I think covers all of the X11
2398 and OpenGL cases. For each of these: rotate to all 4 orientations;
2399 ensure the mouse tracks properly to all 4 corners.
2401 Test it in Xcode 6, because Xcode 5.0.2 can't run the iPhone6+ simulator.
2403 Test hacks must cover:
2404 X11 ignoreRotation = true
2405 X11 ignoreRotation = false
2406 OpenGL (rotation is handled manually, so they never ignoreRotation)
2408 Test devices must cover:
2409 contentScaleFactor = 1, hackedContentScaleFactor = 1 (iPad 2)
2410 contentScaleFactor = 2, hackedContentScaleFactor = 1 (iPad Retina Air)
2411 contentScaleFactor = 2, hackedContentScaleFactor = 2 (iPhone 5 5s 6 6+)
2413 iPad 2: 768x1024 / 1 = 768x1024
2414 iPad Air: 1536x2048 / 2 = 768x1024 (iPad Retina is identical)
2415 iPhone 4s: 640x960 / 2 = 320x480
2416 iPhone 5: 640x1136 / 2 = 320x568 (iPhone 5s and iPhone 6 are identical)
2417 iPhone 6+: 640x1136 / 2 = 320x568 (nativeBounds 960x1704 nativeScale 3)
2420 iPad2 iPadAir iPhone4s iPhone5 iPhone6+
2421 Attraction X yes Y Y Y Y Y
2422 Fireworkx X no Y Y Y Y Y
2423 Carousel GL yes Y Y Y Y Y
2424 Voronoi GL no Y Y Y Y Y
2426 - (void) convertMouse:(int)rot x:(int*)x y:(int *)y
2428 int xx = *x, yy = *y;
2430 # if TARGET_IPHONE_SIMULATOR
2432 XWindowAttributes xgwa;
2433 XGetWindowAttributes (xdpy, xwindow, &xgwa);
2434 NSLog (@"TOUCH %4d, %-4d in %4d x %-4d ig=%d rr=%d cs=%.0f hcs=%.0f\n",
2436 xgwa.width, xgwa.height,
2437 ignore_rotation_p, [self reshapeRotatedWindow],
2438 [self contentScaleFactor],
2439 [self hackedContentScaleFactor]);
2441 # endif // TARGET_IPHONE_SIMULATOR
2443 if (!ignore_rotation_p && [self reshapeRotatedWindow]) {
2445 // For X11 hacks with ignoreRotation == false, we need to rotate the
2446 // coordinates to match the unrotated X11 window. We do not do this
2447 // for GL hacks, or for X11 hacks with ignoreRotation == true.
2449 int w = [self frame].size.width;
2450 int h = [self frame].size.height;
2452 switch (orientation) {
2453 case UIDeviceOrientationLandscapeRight:
2454 swap = xx; xx = h-yy; yy = swap;
2456 case UIDeviceOrientationLandscapeLeft:
2457 swap = xx; xx = yy; yy = w-swap;
2459 case UIDeviceOrientationPortraitUpsideDown:
2460 xx = w-xx; yy = h-yy;
2466 double s = [self hackedContentScaleFactor];
2470 # if TARGET_IPHONE_SIMULATOR || !defined __OPTIMIZE__
2472 XWindowAttributes xgwa;
2473 XGetWindowAttributes (xdpy, xwindow, &xgwa);
2474 NSLog (@"touch %4d, %-4d in %4d x %-4d ig=%d rr=%d cs=%.0f hcs=%.0f\n",
2476 xgwa.width, xgwa.height,
2477 ignore_rotation_p, [self reshapeRotatedWindow],
2478 [self contentScaleFactor],
2479 [self hackedContentScaleFactor]);
2480 if (*x < 0 || *y < 0 || *x > xgwa.width || *y > xgwa.height)
2483 # endif // TARGET_IPHONE_SIMULATOR
2487 /* Single click exits saver.
2491 [self stopAndClose:NO];
2495 /* Double click sends Space KeyPress.
2497 - (void) handleDoubleTap
2499 if (!xsft->event_cb || !xwindow) return;
2502 memset (&xe, 0, sizeof(xe));
2503 xe.xkey.keycode = ' ';
2504 xe.xany.type = KeyPress;
2505 BOOL ok1 = [self sendEvent: &xe];
2506 xe.xany.type = KeyRelease;
2507 BOOL ok2 = [self sendEvent: &xe];
2513 /* Drag with one finger down: send MotionNotify.
2515 - (void) handlePan:(UIGestureRecognizer *)sender
2517 if (!xsft->event_cb || !xwindow) return;
2520 memset (&xe, 0, sizeof(xe));
2522 CGPoint p = [sender locationInView:self]; // this is in points, not pixels
2525 [self convertMouse: rot_current_angle x:&x y:&y];
2526 jwxyz_mouse_moved (xdpy, xwindow, x, y);
2528 switch (sender.state) {
2529 case UIGestureRecognizerStateBegan:
2530 xe.xany.type = ButtonPress;
2531 xe.xbutton.button = 1;
2536 case UIGestureRecognizerStateEnded:
2537 xe.xany.type = ButtonRelease;
2538 xe.xbutton.button = 1;
2543 case UIGestureRecognizerStateChanged:
2544 xe.xany.type = MotionNotify;
2553 BOOL ok = [self sendEvent: &xe];
2554 if (!ok && xe.xany.type == ButtonRelease)
2559 /* Hold one finger down: assume we're about to start dragging.
2560 Treat the same as Pan.
2562 - (void) handleLongPress:(UIGestureRecognizer *)sender
2564 [self handlePan:sender];
2569 /* Drag with 2 fingers down: send arrow keys.
2571 - (void) handlePan2:(UIPanGestureRecognizer *)sender
2573 if (!xsft->event_cb || !xwindow) return;
2575 if (sender.state != UIGestureRecognizerStateEnded)
2579 memset (&xe, 0, sizeof(xe));
2581 CGPoint p = [sender locationInView:self]; // this is in points, not pixels
2584 [self convertMouse: rot_current_angle x:&x y:&y];
2586 if (abs(x) > abs(y))
2587 xe.xkey.keycode = (x > 0 ? XK_Right : XK_Left);
2589 xe.xkey.keycode = (y > 0 ? XK_Down : XK_Up);
2591 BOOL ok1 = [self sendEvent: &xe];
2592 xe.xany.type = KeyRelease;
2593 BOOL ok2 = [self sendEvent: &xe];
2599 /* We need this to respond to "shake" gestures
2601 - (BOOL)canBecomeFirstResponder
2606 - (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
2611 - (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event
2615 /* Shake means exit and launch a new saver.
2617 - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
2619 [self stopAndClose:YES];
2623 - (void)setScreenLocked:(BOOL)locked
2625 if (screenLocked == locked) return;
2626 screenLocked = locked;
2628 if ([self isAnimating])
2629 [self stopAnimation];
2631 if (! [self isAnimating])
2632 [self startAnimation];
2636 - (NSDictionary *)getGLProperties
2638 return [NSDictionary dictionaryWithObjectsAndKeys:
2639 kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat,
2643 - (void)addExtraRenderbuffers:(CGSize)size
2645 // No extra renderbuffers are needed for 2D screenhacks.
2648 #endif // USE_IPHONE
2651 - (void) checkForUpdates
2654 // We only check once at startup, even if there are multiple screens,
2655 // and even if this saver is running for many days.
2656 // (Uh, except this doesn't work because this static isn't shared,
2657 // even if we make it an exported global. Not sure why. Oh well.)
2658 static BOOL checked_p = NO;
2659 if (checked_p) return;
2662 // If it's off, don't bother running the updater. Otherwise, the
2663 // updater will decide if it's time to hit the network.
2664 if (! get_boolean_resource (xdpy,
2665 SUSUEnableAutomaticChecksKey,
2666 SUSUEnableAutomaticChecksKey))
2669 NSString *updater = @"XScreenSaverUpdater.app";
2671 // There may be multiple copies of the updater: e.g., one in /Applications
2672 // and one in the mounted installer DMG! It's important that we run the
2673 // one from the disk and not the DMG, so search for the right one.
2675 NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
2676 NSBundle *bundle = [NSBundle bundleForClass:[self class]];
2678 @[[[bundle bundlePath] stringByDeletingLastPathComponent],
2679 [@"~/Library/Screen Savers" stringByExpandingTildeInPath],
2680 @"/Library/Screen Savers",
2681 @"/System/Library/Screen Savers",
2683 @"/Applications/Utilities"];
2684 NSString *app_path = nil;
2685 for (NSString *dir in search) {
2686 NSString *p = [dir stringByAppendingPathComponent:updater];
2687 if ([[NSFileManager defaultManager] fileExistsAtPath:p]) {
2694 app_path = [workspace fullPathForApplication:updater];
2696 if (app_path && [app_path hasPrefix:@"/Volumes/XScreenSaver "])
2697 app_path = 0; // The DMG version will not do.
2700 NSLog(@"Unable to find %@", updater);
2705 if (! [workspace launchApplicationAtURL:[NSURL fileURLWithPath:app_path]
2706 options:(NSWorkspaceLaunchWithoutAddingToRecents |
2707 NSWorkspaceLaunchWithoutActivation |
2708 NSWorkspaceLaunchAndHide)
2711 NSLog(@"Unable to launch %@: %@", app_path, err);
2714 # endif // !USE_IPHONE
2720 /* Utility functions...
2723 static PrefsReader *
2724 get_prefsReader (Display *dpy)
2726 XScreenSaverView *view = jwxyz_window_view (XRootWindow (dpy, 0));
2727 if (!view) return 0;
2728 return [view prefsReader];
2733 get_string_resource (Display *dpy, char *name, char *class)
2735 return [get_prefsReader(dpy) getStringResource:name];
2739 get_boolean_resource (Display *dpy, char *name, char *class)
2741 return [get_prefsReader(dpy) getBooleanResource:name];
2745 get_integer_resource (Display *dpy, char *name, char *class)
2747 return [get_prefsReader(dpy) getIntegerResource:name];
2751 get_float_resource (Display *dpy, char *name, char *class)
2753 return [get_prefsReader(dpy) getFloatResource:name];