1 /* xscreensaver, Copyright (c) 2006-2016 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"
27 #import "jwxyz-cocoa.h"
28 #import "jwxyz-timers.h"
31 // XScreenSaverView.m speaks OpenGL ES just fine, but enableBackbuffer does
32 // need (jwzgles_)gluCheckExtension.
35 # import <OpenGL/glu.h>
38 /* Garbage collection only exists if we are being compiled against the
39 10.6 SDK or newer, not if we are building against the 10.4 SDK.
41 #ifndef MAC_OS_X_VERSION_10_6
42 # define MAC_OS_X_VERSION_10_6 1060 /* undefined in 10.4 SDK, grr */
44 #ifndef MAC_OS_X_VERSION_10_12
45 # define MAC_OS_X_VERSION_10_12 101200
47 #if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 && \
48 MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12)
49 /* 10.6 SDK or later, and earlier than 10.12 SDK */
50 # import <objc/objc-auto.h>
51 # define DO_GC_HACKERY
54 /* Duplicated in xlockmoreI.h and XScreenSaverGLView.m. */
55 extern void clear_gl_error (void);
56 extern void check_gl_error (const char *type);
58 extern struct xscreensaver_function_table *xscreensaver_function_table;
60 /* Global variables used by the screen savers
63 const char *progclass;
69 # define NSSizeToCGSize(x) (x)
71 extern NSDictionary *make_function_table_dict(void); // ios-function-table.m
73 /* Stub definition of the superclass, for iPhone.
75 @implementation ScreenSaverView
77 NSTimeInterval anim_interval;
82 - (id)initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview {
83 self = [super initWithFrame:frame];
85 anim_interval = 1.0/30;
88 - (NSTimeInterval)animationTimeInterval { return anim_interval; }
89 - (void)setAnimationTimeInterval:(NSTimeInterval)i { anim_interval = i; }
90 - (BOOL)hasConfigureSheet { return NO; }
91 - (NSWindow *)configureSheet { return nil; }
92 - (NSView *)configureView { return nil; }
93 - (BOOL)isPreview { return NO; }
94 - (BOOL)isAnimating { return animating_p; }
95 - (void)animateOneFrame { }
97 - (void)startAnimation {
98 if (animating_p) return;
100 anim_timer = [NSTimer scheduledTimerWithTimeInterval: anim_interval
102 selector:@selector(animateOneFrame)
107 - (void)stopAnimation {
109 [anim_timer invalidate];
116 # endif // !USE_IPHONE
120 @interface XScreenSaverView (Private)
121 - (void) stopAndClose:(Bool)relaunch;
124 @implementation XScreenSaverView
126 // Given a lower-cased saver name, returns the function table for it.
127 // If no name, guess the name from the class's bundle name.
129 - (struct xscreensaver_function_table *) findFunctionTable:(NSString *)name
131 NSBundle *nsb = [NSBundle bundleForClass:[self class]];
132 NSAssert1 (nsb, @"no bundle for class %@", [self class]);
134 NSString *path = [nsb bundlePath];
135 CFURLRef url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
137 kCFURLPOSIXPathStyle,
139 CFBundleRef cfb = CFBundleCreate (kCFAllocatorDefault, url);
141 NSAssert1 (cfb, @"no CFBundle for \"%@\"", path);
142 // #### Analyze says "Potential leak of an object stored into cfb"
145 name = [[path lastPathComponent] stringByDeletingPathExtension];
147 name = [[name lowercaseString]
148 stringByReplacingOccurrencesOfString:@" "
152 // CFBundleGetDataPointerForName doesn't work in "Archive" builds.
153 // I'm guessing that symbol-stripping is mandatory. Fuck.
154 NSString *table_name = [name stringByAppendingString:
155 @"_xscreensaver_function_table"];
156 void *addr = CFBundleGetDataPointerForName (cfb, (CFStringRef) table_name);
160 NSLog (@"no symbol \"%@\" for \"%@\"", table_name, path);
163 // Depends on the auto-generated "ios-function-table.m" being up to date.
164 if (! function_tables)
165 function_tables = [make_function_table_dict() retain];
166 NSValue *v = [function_tables objectForKey: name];
167 void *addr = v ? [v pointerValue] : 0;
168 # endif // USE_IPHONE
170 return (struct xscreensaver_function_table *) addr;
174 // Add the "Contents/Resources/" subdirectory of this screen saver's .bundle
175 // to $PATH for the benefit of savers that include helper shell scripts.
177 - (void) setShellPath
179 NSBundle *nsb = [NSBundle bundleForClass:[self class]];
180 NSAssert1 (nsb, @"no bundle for class %@", [self class]);
182 NSString *nsdir = [nsb resourcePath];
183 NSAssert1 (nsdir, @"no resourcePath for class %@", [self class]);
184 const char *dir = [nsdir cStringUsingEncoding:NSUTF8StringEncoding];
185 const char *opath = getenv ("PATH");
186 if (!opath) opath = "/bin"; // $PATH is unset when running under Shark!
187 char *npath = (char *) malloc (strlen (opath) + strlen (dir) + 2);
190 strcat (npath, opath);
191 if (setenv ("PATH", npath, 1)) {
193 NSAssert1 (0, @"setenv \"PATH=%s\" failed", npath);
200 // set an $XSCREENSAVER_CLASSPATH variable so that included shell scripts
201 // (e.g., "xscreensaver-text") know how to look up resources.
203 - (void) setResourcesEnv:(NSString *) name
205 NSBundle *nsb = [NSBundle bundleForClass:[self class]];
206 NSAssert1 (nsb, @"no bundle for class %@", [self class]);
208 const char *s = [name cStringUsingEncoding:NSUTF8StringEncoding];
209 if (setenv ("XSCREENSAVER_CLASSPATH", s, 1)) {
211 NSAssert1 (0, @"setenv \"XSCREENSAVER_CLASSPATH=%s\" failed", s);
216 - (void) loadCustomFonts
219 NSBundle *nsb = [NSBundle bundleForClass:[self class]];
220 NSMutableArray *fonts = [NSMutableArray arrayWithCapacity:20];
221 for (NSString *ext in @[@"ttf", @"otf"]) {
222 [fonts addObjectsFromArray: [nsb pathsForResourcesOfType:ext
225 for (NSString *font in fonts) {
226 CFURLRef url = (CFURLRef) [NSURL fileURLWithPath: font];
228 if (! CTFontManagerRegisterFontsForURL (url, kCTFontManagerScopeProcess,
230 // Just ignore errors:
231 // "The file has already been registered in the specified scope."
232 // NSLog (@"loading font: %@ %@", url, err);
235 # endif // !USE_IPHONE
240 add_default_options (const XrmOptionDescRec *opts,
241 const char * const *defs,
242 XrmOptionDescRec **opts_ret,
243 const char ***defs_ret)
245 /* These aren't "real" command-line options (there are no actual command-line
246 options in the Cocoa version); but this is the somewhat kludgey way that
247 the <xscreensaver-text /> and <xscreensaver-image /> tags in the
248 ../hacks/config/\*.xml files communicate with the preferences database.
250 static const XrmOptionDescRec default_options [] = {
251 { "-text-mode", ".textMode", XrmoptionSepArg, 0 },
252 { "-text-literal", ".textLiteral", XrmoptionSepArg, 0 },
253 { "-text-file", ".textFile", XrmoptionSepArg, 0 },
254 { "-text-url", ".textURL", XrmoptionSepArg, 0 },
255 { "-text-program", ".textProgram", XrmoptionSepArg, 0 },
256 { "-grab-desktop", ".grabDesktopImages", XrmoptionNoArg, "True" },
257 { "-no-grab-desktop", ".grabDesktopImages", XrmoptionNoArg, "False"},
258 { "-choose-random-images", ".chooseRandomImages",XrmoptionNoArg, "True" },
259 { "-no-choose-random-images",".chooseRandomImages",XrmoptionNoArg, "False"},
260 { "-image-directory", ".imageDirectory", XrmoptionSepArg, 0 },
261 { "-fps", ".doFPS", XrmoptionNoArg, "True" },
262 { "-no-fps", ".doFPS", XrmoptionNoArg, "False"},
263 { "-foreground", ".foreground", XrmoptionSepArg, 0 },
264 { "-fg", ".foreground", XrmoptionSepArg, 0 },
265 { "-background", ".background", XrmoptionSepArg, 0 },
266 { "-bg", ".background", XrmoptionSepArg, 0 },
269 // <xscreensaver-updater />
270 { "-" SUSUEnableAutomaticChecksKey,
271 "." SUSUEnableAutomaticChecksKey, XrmoptionNoArg, "True" },
272 { "-no-" SUSUEnableAutomaticChecksKey,
273 "." SUSUEnableAutomaticChecksKey, XrmoptionNoArg, "False" },
274 { "-" SUAutomaticallyUpdateKey,
275 "." SUAutomaticallyUpdateKey, XrmoptionNoArg, "True" },
276 { "-no-" SUAutomaticallyUpdateKey,
277 "." SUAutomaticallyUpdateKey, XrmoptionNoArg, "False" },
278 { "-" SUSendProfileInfoKey,
279 "." SUSendProfileInfoKey, XrmoptionNoArg,"True" },
280 { "-no-" SUSendProfileInfoKey,
281 "." SUSendProfileInfoKey, XrmoptionNoArg,"False"},
282 { "-" SUScheduledCheckIntervalKey,
283 "." SUScheduledCheckIntervalKey, XrmoptionSepArg, 0 },
284 # endif // !USE_IPHONE
288 static const char *default_defaults [] = {
290 # if defined(USE_IPHONE) && !defined(__OPTIMIZE__)
295 ".doubleBuffer: True",
296 ".multiSample: False",
304 ".textURL: https://en.wikipedia.org/w/index.php?title=Special:NewPages&feed=rss",
306 ".grabDesktopImages: yes",
308 ".chooseRandomImages: no",
310 ".chooseRandomImages: yes",
312 ".imageDirectory: ~/Pictures",
314 ".texFontCacheSize: 30",
318 # define STR(S) STR1(S)
319 # define __objc_yes Yes
320 # define __objc_no No
321 "." SUSUEnableAutomaticChecksKey ": " STR(SUSUEnableAutomaticChecksDef),
322 "." SUAutomaticallyUpdateKey ": " STR(SUAutomaticallyUpdateDef),
323 "." SUSendProfileInfoKey ": " STR(SUSendProfileInfoDef),
324 "." SUScheduledCheckIntervalKey ": " STR(SUScheduledCheckIntervalDef),
329 # endif // USE_IPHONE
334 for (i = 0; default_options[i].option; i++)
336 for (i = 0; opts[i].option; i++)
339 XrmOptionDescRec *opts2 = (XrmOptionDescRec *)
340 calloc (count + 1, sizeof (*opts2));
344 while (default_options[j].option) {
345 opts2[i] = default_options[j];
349 while (opts[j].option) {
360 for (i = 0; default_defaults[i]; i++)
362 for (i = 0; defs[i]; i++)
365 const char **defs2 = (const char **) calloc (count + 1, sizeof (*defs2));
369 while (default_defaults[j]) {
370 defs2[i] = default_defaults[j];
383 - (id) initWithFrame:(NSRect)frame
384 saverName:(NSString *)saverName
385 isPreview:(BOOL)isPreview
387 if (! (self = [super initWithFrame:frame isPreview:isPreview]))
390 xsft = [self findFunctionTable: saverName];
400 xsft->setup_cb (xsft, xsft->setup_arg);
402 /* The plist files for these preferences show up in
403 $HOME/Library/Preferences/ByHost/ in a file named like
404 "org.jwz.xscreensaver.<SAVERNAME>.<NUMBERS>.plist"
406 NSString *name = [NSString stringWithCString:xsft->progclass
407 encoding:NSISOLatin1StringEncoding];
408 name = [@"org.jwz.xscreensaver." stringByAppendingString:name];
409 [self setResourcesEnv:name];
410 [self loadCustomFonts];
412 XrmOptionDescRec *opts = 0;
413 const char **defs = 0;
414 add_default_options (xsft->options, xsft->defaults, &opts, &defs);
415 prefsReader = [[PrefsReader alloc]
416 initWithName:name xrmKeys:opts defaults:defs];
418 // free (opts); // bah, we need these! #### leak!
419 xsft->options = opts;
421 progname = progclass = xsft->progclass;
425 # if !defined USE_IPHONE && defined JWXYZ_QUARTZ
426 // When the view fills the screen and double buffering is enabled, OS X will
427 // use page flipping for a minor CPU/FPS boost. In windowed mode, double
428 // buffering reduces the frame rate to 1/2 the screen's refresh rate.
429 double_buffered_p = !isPreview;
435 // So we can tell when we're docked.
436 [UIDevice currentDevice].batteryMonitoringEnabled = YES;
438 [self setBackgroundColor:[NSColor blackColor]];
439 # endif // USE_IPHONE
442 // Colorspaces and CGContexts only happen with non-GL hacks.
443 colorspace = CGColorSpaceCreateDeviceRGB ();
453 return [CAEAGLLayer class];
458 - (id) initWithFrame:(NSRect)frame isPreview:(BOOL)p
460 return [self initWithFrame:frame saverName:0 isPreview:p];
466 if ([self isAnimating])
467 [self stopAnimation];
468 NSAssert(!xdata, @"xdata not yet freed");
469 NSAssert(!xdpy, @"xdpy not yet freed");
472 [[NSNotificationCenter defaultCenter] removeObserver:self];
475 # ifdef BACKBUFFER_OPENGL
478 # endif // !USE_IPHONE
480 // Releasing the OpenGL context should also free any OpenGL objects,
481 // including the backbuffer texture and frame/render/depthbuffers.
482 # endif // BACKBUFFER_OPENGL
484 # if defined JWXYZ_GL && defined USE_IPHONE
485 [ogl_ctx_pixmap release];
490 CGColorSpaceRelease (colorspace);
491 # endif // JWXYZ_QUARTZ
493 [prefsReader release];
501 - (PrefsReader *) prefsReader
508 - (void) lockFocus { }
509 - (void) unlockFocus { }
515 /* A few seconds after the saver launches, we store the "wasRunning"
516 preference. This is so that if the saver is crashing at startup,
517 we don't launch it again next time, getting stuck in a crash loop.
519 - (void) allSystemsGo: (NSTimer *) timer
521 NSAssert (timer == crash_timer, @"crash timer screwed up");
524 NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
525 [prefs setBool:YES forKey:@"wasRunning"];
535 CGSize screen_size = self.bounds.size;
536 double s = self.contentScaleFactor;
537 screen_size.width *= s;
538 screen_size.height *= s;
541 GLuint *framebuffer = &xwindow->gl_framebuffer;
542 GLuint *renderbuffer = &xwindow->gl_renderbuffer;
543 xwindow->window.current_drawable = xwindow;
544 #elif defined JWXYZ_QUARTZ
545 GLuint *framebuffer = &gl_framebuffer;
546 GLuint *renderbuffer = &gl_renderbuffer;
547 #endif // JWXYZ_QUARTZ
549 if (*framebuffer) glDeleteFramebuffersOES (1, framebuffer);
550 if (*renderbuffer) glDeleteRenderbuffersOES (1, renderbuffer);
552 create_framebuffer (framebuffer, renderbuffer);
555 // glRenderbufferStorageOES (GL_RENDERBUFFER_OES, GL_RGBA8_OES,
556 // (int)size.width, (int)size.height);
557 [ogl_ctx renderbufferStorage:GL_RENDERBUFFER_OES
558 fromDrawable:(CAEAGLLayer*)self.layer];
560 glFramebufferRenderbufferOES (GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES,
561 GL_RENDERBUFFER_OES, *renderbuffer);
563 [self addExtraRenderbuffers:screen_size];
565 check_framebuffer_status();
570 - (void) startAnimation
572 NSAssert(![self isAnimating], @"already animating");
573 NSAssert(!initted_p && !xdata, @"already initialized");
575 // See comment in render_x11() for why this value is important:
576 [self setAnimationTimeInterval: 1.0 / 240.0];
578 [super startAnimation];
579 /* We can't draw on the window from this method, so we actually do the
580 initialization of the screen saver (xsft->init_cb) in the first call
581 to animateOneFrame() instead.
586 [crash_timer invalidate];
588 NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
589 [prefs removeObjectForKey:@"wasRunning"];
592 crash_timer = [NSTimer scheduledTimerWithTimeInterval: 5
594 selector:@selector(allSystemsGo:)
598 # endif // USE_IPHONE
600 // Never automatically turn the screen off if we are docked,
601 // and an animation is running.
604 [UIApplication sharedApplication].idleTimerDisabled =
605 ([UIDevice currentDevice].batteryState != UIDeviceBatteryStateUnplugged);
606 [[UIApplication sharedApplication]
607 setStatusBarHidden:YES withAnimation:UIStatusBarAnimationNone];
610 xwindow = (Window) calloc (1, sizeof(*xwindow));
611 xwindow->type = WINDOW;
612 xwindow->window.view = self;
613 CFRetain (xwindow->window.view); // needed for garbage collection?
615 #ifdef BACKBUFFER_OPENGL
616 CGSize new_backbuffer_size;
622 pixfmt = [self getGLPixelFormat];
625 NSAssert (pixfmt, @"unable to create NSOpenGLPixelFormat");
627 // Fun: On OS X 10.7, the second time an OpenGL context is created, after
628 // the preferences dialog is launched in SaverTester, the context only
629 // lasts until the first full GC. Then it turns black. Solution is to
630 // reuse the OpenGL context after this point.
631 // "Analyze" says that both pixfmt and ogl_ctx are leaked.
632 ogl_ctx = [[NSOpenGLContext alloc] initWithFormat:pixfmt
635 // Sync refreshes to the vertical blanking interval
637 [ogl_ctx setValues:&r forParameter:NSOpenGLCPSwapInterval];
638 // check_gl_error ("NSOpenGLCPSwapInterval"); // SEGV sometimes. Too early?
641 [ogl_ctx makeCurrentContext];
642 check_gl_error ("makeCurrentContext");
644 // NSOpenGLContext logs an 'invalid drawable' when this is called
645 // from initWithFrame.
646 [ogl_ctx setView:self];
648 // This may not be necessary if there's FBO support.
650 xwindow->window.pixfmt = pixfmt;
651 CFRetain (xwindow->window.pixfmt);
652 xwindow->window.virtual_screen = [ogl_ctx currentVirtualScreen];
653 xwindow->window.current_drawable = xwindow;
654 NSAssert (ogl_ctx, @"no CGContext");
657 // Clear frame buffer ASAP, else there are bits left over from other apps.
658 glClearColor (0, 0, 0, 1);
659 glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
661 // glXSwapBuffers (mi->dpy, mi->window);
664 // Enable multi-threading, if possible. This runs most OpenGL commands
665 // and GPU management on a second CPU.
667 # ifndef kCGLCEMPEngine
668 # define kCGLCEMPEngine 313 // Added in MacOS 10.4.8 + XCode 2.4.
670 CGLContextObj cctx = CGLGetCurrentContext();
671 CGLError err = CGLEnable (cctx, kCGLCEMPEngine);
672 if (err != kCGLNoError) {
673 NSLog (@"enabling multi-threaded OpenGL failed: %d", err);
677 new_backbuffer_size = NSSizeToCGSize ([self bounds].size);
681 CAEAGLLayer *eagl_layer = (CAEAGLLayer *) self.layer;
682 eagl_layer.opaque = TRUE;
683 eagl_layer.drawableProperties = [self getGLProperties];
685 // Without this, the GL frame buffer is half the screen resolution!
686 eagl_layer.contentsScale = [UIScreen mainScreen].scale;
688 ogl_ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
690 ogl_ctx_pixmap = [[EAGLContext alloc]
691 initWithAPI:kEAGLRenderingAPIOpenGLES1
692 sharegroup:ogl_ctx.sharegroup];
695 eagl_layer.contentsGravity = [self getCAGravity];
699 xwindow->window.ogl_ctx_pixmap = ogl_ctx_pixmap;
702 [EAGLContext setCurrentContext: ogl_ctx];
706 double s = [self hackedContentScaleFactor];
707 new_backbuffer_size = self.bounds.size;
708 new_backbuffer_size.width *= s;
709 new_backbuffer_size.height *= s;
711 # endif // USE_IPHONE
714 xwindow->ogl_ctx = ogl_ctx;
716 CFRetain (xwindow->ogl_ctx);
717 # endif // USE_IPHONE
720 check_gl_error ("startAnimation");
722 // NSLog (@"%s / %s / %s\n", glGetString (GL_VENDOR),
723 // glGetString (GL_RENDERER), glGetString (GL_VERSION));
725 [self enableBackbuffer:new_backbuffer_size];
727 #endif // BACKBUFFER_OPENGL
730 [self createBackbuffer:new_backbuffer_size];
733 - (void)stopAnimation
735 NSAssert([self isAnimating], @"not animating");
739 [self lockFocus]; // in case something tries to draw from here
740 [self prepareContext];
742 /* I considered just not even calling the free callback at all...
743 But webcollage-cocoa needs it, to kill the inferior webcollage
744 processes (since the screen saver framework never generates a
745 SIGPIPE for them...) Instead, I turned off the free call in
746 xlockmore.c, which is where all of the bogus calls are anyway.
748 xsft->free_cb (xdpy, xwindow, xdata);
751 jwxyz_free_display (xdpy);
753 # if defined JWXYZ_GL && !defined USE_IPHONE
754 CFRelease (xwindow->ogl_ctx);
756 CFRelease (xwindow->window.view);
760 // setup_p = NO; // #### wait, do we need this?
767 [crash_timer invalidate];
769 NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
770 [prefs removeObjectForKey:@"wasRunning"];
772 # endif // USE_IPHONE
774 [super stopAnimation];
776 // When an animation is no longer running (e.g., looking at the list)
777 // then it's ok to power off the screen when docked.
780 [UIApplication sharedApplication].idleTimerDisabled = NO;
781 [[UIApplication sharedApplication]
782 setStatusBarHidden:NO withAnimation:UIStatusBarAnimationNone];
785 // Without this, the GL frame stays on screen when switching tabs
786 // in System Preferences.
787 // (Or perhaps it used to. It doesn't seem to matter on 10.9.)
790 [NSOpenGLContext clearCurrentContext];
791 # endif // !USE_IPHONE
793 clear_gl_error(); // This hack is defunct, don't let this linger.
796 CGContextRelease (backbuffer);
800 munmap (backbuffer_data, backbuffer_len);
801 backbuffer_data = NULL;
807 - (NSOpenGLContext *) oglContext
813 // #### maybe this could/should just be on 'lockFocus' instead?
814 - (void) prepareContext
818 [EAGLContext setCurrentContext:ogl_ctx];
820 [ogl_ctx makeCurrentContext];
821 // check_gl_error ("makeCurrentContext");
822 #endif // !USE_IPHONE
825 xwindow->window.current_drawable = xwindow;
832 screenhack_do_fps (Display *dpy, Window w, fps_state *fpst, void *closure)
834 fps_compute (fpst, 0, -1);
841 /* On iPhones with Retina displays, we can draw the savers in "real"
842 pixels, and that works great. The 320x480 "point" screen is really
843 a 640x960 *pixel* screen. However, Retina iPads have 768x1024
844 point screens which are 1536x2048 pixels, and apparently that's
845 enough pixels that copying those bits to the screen is slow. Like,
846 drops us from 15fps to 7fps. So, on Retina iPads, we don't draw in
847 real pixels. This will probably make the savers look better
848 anyway, since that's a higher resolution than most desktop monitors
849 have even today. (This is only true for X11 programs, not GL
850 programs. Those are fine at full rez.)
852 This method is overridden in XScreenSaverGLView, since this kludge
853 isn't necessary for GL programs, being resolution independent by
856 - (CGFloat) hackedContentScaleFactor
858 NSSize bsize = [self bounds].size;
861 max_bsize = bsize.width > bsize.height ? bsize.width : bsize.height;
863 // Ratio of screen size in pixels to view size in points.
864 CGFloat s = self.contentScaleFactor;
868 // 1. Don't exceed -- let's say 1280 pixels in either direction.
869 // (Otherwise the frame rate gets bad.)
870 // Actually let's make that 1440 since iPhone 6 is natively 1334.
871 CGFloat mag0 = ceil(max_bsize * s / 1440);
873 // 2. Don't let the pixel size get too small.
874 // (Otherwise pixels in IFS and similar are too fine.)
875 // So don't let the result be > 2 pixels per point.
876 CGFloat mag1 = ceil(s / 2);
878 // As of iPhone 6, mag0 is always >= mag1. This may not be true in the future.
879 // (desired scale factor) = s / (desired magnification factor)
880 return s / (mag0 > mag1 ? mag0 : mag1);
885 current_device_rotation (void)
887 UIDeviceOrientation o = [[UIDevice currentDevice] orientation];
889 /* Sometimes UIDevice doesn't know the proper orientation, or the device is
890 face up/face down, so in those cases fall back to the status bar
891 orientation. The SaverViewController tries to set the status bar to the
892 proper orientation before it creates the XScreenSaverView; see
893 _storedOrientation in SaverViewController.
895 if (o == UIDeviceOrientationUnknown ||
896 o == UIDeviceOrientationFaceUp ||
897 o == UIDeviceOrientationFaceDown) {
898 /* Mind the differences between UIInterfaceOrientation and
900 1. UIInterfaceOrientation does not include FaceUp and FaceDown.
901 2. LandscapeLeft and LandscapeRight are swapped between the two. But
902 converting between device and interface orientation doesn't need to
903 take this into account, because (from the UIInterfaceOrientation
904 description): "rotating the device requires rotating the content in
905 the opposite direction."
907 /* statusBarOrientation deprecated in iOS 9 */
908 o = [UIApplication sharedApplication].statusBarOrientation;
912 case UIDeviceOrientationLandscapeLeft: return -90; break;
913 case UIDeviceOrientationLandscapeRight: return 90; break;
914 case UIDeviceOrientationPortraitUpsideDown: return 180; break;
915 default: return 0; break;
920 - (void)alertView:(UIAlertView *)av clickedButtonAtIndex:(NSInteger)i
922 if (i == 0) exit (-1); // Cancel
923 [self stopAndClose:NO]; // Keep going
926 - (void) handleException: (NSException *)e
928 NSLog (@"Caught exception: %@", e);
929 [[[UIAlertView alloc] initWithTitle:
930 [NSString stringWithFormat: @"%s crashed!",
933 [NSString stringWithFormat:
934 @"The error message was:"
936 "If it keeps crashing, try "
937 "resetting its options.",
940 cancelButtonTitle: @"Exit"
941 otherButtonTitles: @"Keep going", nil]
943 [self stopAnimation];
955 // iOS always uses OpenGL ES 1.1.
961 gl_check_ver (const struct gl_version *caps,
965 return caps->major > gl_major ||
966 (caps->major == gl_major && caps->minor >= gl_minor);
971 /* Called during startAnimation before the first call to createBackbuffer. */
972 - (void) enableBackbuffer:(CGSize)new_backbuffer_size
975 struct gl_version version;
978 const char *version_str = (const char *)glGetString (GL_VERSION);
980 /* iPhone is always OpenGL ES 1.1. */
981 if (sscanf ((const char *)version_str, "%u.%u",
982 &version.major, &version.minor) < 2)
990 // The OpenGL extensions in use in here are pretty are pretty much ubiquitous
991 // on OS X, but it's still good form to check.
992 const GLubyte *extensions = glGetString (GL_EXTENSIONS);
994 glGenTextures (1, &backbuffer_texture);
996 // On really old systems, it would make sense to split the texture
999 gl_texture_target = (gluCheckExtension ((const GLubyte *)
1000 "GL_ARB_texture_rectangle",
1002 ? GL_TEXTURE_RECTANGLE_EXT : GL_TEXTURE_2D);
1004 // OES_texture_npot also provides this, but iOS never provides it.
1005 gl_limited_npot_p = jwzgles_gluCheckExtension
1006 ((const GLubyte *) "GL_APPLE_texture_2D_limited_npot", extensions);
1007 gl_texture_target = GL_TEXTURE_2D;
1010 glBindTexture (gl_texture_target, backbuffer_texture);
1011 glTexParameteri (gl_texture_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
1012 // GL_LINEAR might make sense on Retina iPads.
1013 glTexParameteri (gl_texture_target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
1014 glTexParameteri (gl_texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
1015 glTexParameteri (gl_texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
1018 // There isn't much sense in supporting one of these if the other
1020 gl_apple_client_storage_p =
1021 gluCheckExtension ((const GLubyte *)"GL_APPLE_client_storage",
1023 gluCheckExtension ((const GLubyte *)"GL_APPLE_texture_range", extensions);
1025 if (gl_apple_client_storage_p) {
1026 glTexParameteri (gl_texture_target, GL_TEXTURE_STORAGE_HINT_APPLE,
1027 GL_STORAGE_SHARED_APPLE);
1028 glPixelStorei (GL_UNPACK_CLIENT_STORAGE_APPLE, GL_TRUE);
1032 // If a video adapter suports BGRA textures, then that's probably as fast as
1033 // you're gonna get for getting a texture onto the screen.
1036 jwzgles_gluCheckExtension
1037 ((const GLubyte *)"GL_APPLE_texture_format_BGRA8888", extensions) ?
1041 gl_pixel_type = GL_UNSIGNED_BYTE;
1042 // See also OES_read_format.
1044 if (gl_check_ver (&version, 1, 2) ||
1045 (gluCheckExtension ((const GLubyte *)"GL_EXT_bgra", extensions) &&
1046 gluCheckExtension ((const GLubyte *)"GL_APPLE_packed_pixels",
1048 gl_pixel_format = GL_BGRA;
1049 // Both Intel and PowerPC-era docs say to use GL_UNSIGNED_INT_8_8_8_8_REV.
1050 gl_pixel_type = GL_UNSIGNED_INT_8_8_8_8_REV;
1052 gl_pixel_format = GL_RGBA;
1053 gl_pixel_type = GL_UNSIGNED_BYTE;
1055 // GL_ABGR_EXT/GL_UNSIGNED_BYTE is another possibilty that may have made more
1056 // sense on PowerPC.
1059 glEnable (gl_texture_target);
1060 glEnableClientState (GL_VERTEX_ARRAY);
1061 glEnableClientState (GL_TEXTURE_COORD_ARRAY);
1063 check_gl_error ("enableBackbuffer");
1073 size_t mask = (size_t)-1;
1074 unsigned bits = sizeof(x) * CHAR_BIT;
1075 unsigned log2 = bits;
1093 - (BOOL) suppressRotationAnimation
1095 return [self ignoreRotation]; // Don't animate if we aren't rotating
1098 - (BOOL) rotateTouches
1100 return FALSE; // Adjust event coordinates only if rotating
1105 - (void) setViewport
1107 # ifdef BACKBUFFER_OPENGL
1108 NSAssert ([NSOpenGLContext currentContext] ==
1109 ogl_ctx, @"invalid GL context");
1111 NSSize new_size = self.bounds.size;
1114 GLfloat s = self.contentScaleFactor;
1115 GLfloat hs = self.hackedContentScaleFactor;
1116 # else // !USE_IPHONE
1117 const GLfloat s = 1;
1118 const GLfloat hs = s;
1121 // On OS X this almost isn't necessary, except for the ugly aliasing
1123 glViewport (0, 0, new_size.width * s, new_size.height * s);
1125 glMatrixMode (GL_PROJECTION);
1132 (-new_size.width * hs, new_size.width * hs,
1133 -new_size.height * hs, new_size.height * hs,
1137 if ([self ignoreRotation]) {
1138 int o = (int) -current_device_rotation();
1139 glRotatef (o, 0, 0, 1);
1141 # endif // USE_IPHONE
1142 # endif // BACKBUFFER_OPENGL
1146 /* Create a bitmap context into which we render everything.
1147 If the desired size has changed, re-created it.
1148 new_size is in rotated pixels, not points: the same size
1149 and shape as the X11 window as seen by the hacks.
1151 - (void) createBackbuffer:(CGSize)new_size
1153 CGSize osize = CGSizeZero;
1155 osize.width = CGBitmapContextGetWidth(backbuffer);
1156 osize.height = CGBitmapContextGetHeight(backbuffer);
1160 (int)osize.width == (int)new_size.width &&
1161 (int)osize.height == (int)new_size.height)
1164 CGContextRef ob = backbuffer;
1165 void *odata = backbuffer_data;
1166 size_t olen = backbuffer_len;
1168 # if !defined __OPTIMIZE__ || TARGET_IPHONE_SIMULATOR
1169 NSLog(@"backbuffer %.0fx%.0f",
1170 new_size.width, new_size.height);
1173 /* OS X uses APPLE_client_storage and APPLE_texture_range, as described in
1174 <https://developer.apple.com/library/mac/documentation/GraphicsImaging/Conceptual/OpenGL-MacProgGuide/opengl_texturedata/opengl_texturedata.html>.
1176 iOS uses bog-standard glTexImage2D (for now).
1178 glMapBuffer is the standard way to get data from system RAM to video
1179 memory asynchronously and without a memcpy, but support for
1180 APPLE_client_storage is ubiquitous on OS X (not so for glMapBuffer),
1181 and on iOS GL_PIXEL_UNPACK_BUFFER is only available on OpenGL ES 3
1182 (iPhone 5S or newer). Plus, glMapBuffer doesn't work well with
1183 CGBitmapContext: glMapBuffer can return a different pointer on each
1184 call, but a CGBitmapContext doesn't allow its data pointer to be
1185 changed -- and recreating the context for a new pointer can be
1186 expensive (glyph caches get dumped, for instance).
1188 glMapBufferRange has MAP_FLUSH_EXPLICIT_BIT and MAP_UNSYNCHRONIZED_BIT,
1189 and these seem to allow mapping the buffer and leaving it where it is
1190 in client address space while OpenGL works with the buffer, but it
1191 requires OpenGL 3 Core profile on OS X (and ES 3 on iOS for
1192 GL_PIXEL_UNPACK_BUFFER), so point goes to APPLE_client_storage.
1194 AMD_pinned_buffer provides the same advantage as glMapBufferRange, but
1195 Apple never implemented that one for OS X.
1198 backbuffer_data = NULL;
1199 gl_texture_w = (int)new_size.width;
1200 gl_texture_h = (int)new_size.height;
1202 NSAssert (gl_texture_target == GL_TEXTURE_2D
1204 || gl_texture_target == GL_TEXTURE_RECTANGLE_EXT
1206 , @"unexpected GL texture target");
1209 if (gl_texture_target != GL_TEXTURE_RECTANGLE_EXT)
1211 if (!gl_limited_npot_p)
1214 gl_texture_w = to_pow2 (gl_texture_w);
1215 gl_texture_h = to_pow2 (gl_texture_h);
1218 size_t bytes_per_row = gl_texture_w * 4;
1220 # if defined(BACKBUFFER_OPENGL) && !defined(USE_IPHONE)
1221 // APPLE_client_storage requires texture width to be aligned to 32 bytes, or
1222 // it will fall back to a memcpy.
1223 // https://developer.apple.com/library/mac/documentation/GraphicsImaging/Conceptual/OpenGL-MacProgGuide/opengl_texturedata/opengl_texturedata.html#//apple_ref/doc/uid/TP40001987-CH407-SW24
1224 bytes_per_row = (bytes_per_row + 31) & ~31;
1225 # endif // BACKBUFFER_OPENGL && !USE_IPHONE
1227 backbuffer_len = bytes_per_row * gl_texture_h;
1228 if (backbuffer_len) // mmap requires this to be non-zero.
1229 backbuffer_data = mmap (NULL, backbuffer_len,
1230 PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED,
1233 BOOL alpha_first_p, order_little_p;
1235 if (gl_pixel_format == GL_BGRA) {
1236 alpha_first_p = YES;
1237 order_little_p = YES;
1239 } else if (gl_pixel_format == GL_ABGR_EXT) {
1241 order_little_p = YES; */
1243 NSAssert (gl_pixel_format == GL_RGBA, @"unknown GL pixel format");
1245 order_little_p = NO;
1249 NSAssert (gl_pixel_type == GL_UNSIGNED_BYTE, @"unknown GL pixel type");
1251 NSAssert (gl_pixel_type == GL_UNSIGNED_INT_8_8_8_8 ||
1252 gl_pixel_type == GL_UNSIGNED_INT_8_8_8_8_REV ||
1253 gl_pixel_type == GL_UNSIGNED_BYTE,
1254 @"unknown GL pixel type");
1256 #if defined __LITTLE_ENDIAN__
1257 const GLenum backwards_pixel_type = GL_UNSIGNED_INT_8_8_8_8;
1258 #elif defined __BIG_ENDIAN__
1259 const GLenum backwards_pixel_type = GL_UNSIGNED_INT_8_8_8_8_REV;
1261 # error Unknown byte order.
1264 if (gl_pixel_type == backwards_pixel_type)
1265 order_little_p ^= YES;
1268 CGBitmapInfo bitmap_info =
1269 (alpha_first_p ? kCGImageAlphaNoneSkipFirst : kCGImageAlphaNoneSkipLast) |
1270 (order_little_p ? kCGBitmapByteOrder32Little : kCGBitmapByteOrder32Big);
1272 backbuffer = CGBitmapContextCreate (backbuffer_data,
1273 (int)new_size.width,
1274 (int)new_size.height,
1279 NSAssert (backbuffer, @"unable to allocate back buffer");
1283 r.origin.x = r.origin.y = 0;
1285 CGContextSetGrayFillColor (backbuffer, 0, 1);
1286 CGContextFillRect (backbuffer, r);
1288 # if defined(BACKBUFFER_OPENGL) && !defined(USE_IPHONE)
1289 if (gl_apple_client_storage_p)
1290 glTextureRangeAPPLE (gl_texture_target, backbuffer_len, backbuffer_data);
1291 # endif // BACKBUFFER_OPENGL && !USE_IPHONE
1294 // Restore old bits, as much as possible, to the X11 upper left origin.
1296 CGRect rect; // pixels, not points
1298 rect.origin.y = (new_size.height - osize.height);
1301 CGImageRef img = CGBitmapContextCreateImage (ob);
1302 CGContextDrawImage (backbuffer, rect, img);
1303 CGImageRelease (img);
1304 CGContextRelease (ob);
1307 // munmap should round len up to the nearest page.
1308 munmap (odata, olen);
1311 check_gl_error ("createBackbuffer");
1315 - (void) drawBackbuffer
1317 # ifdef BACKBUFFER_OPENGL
1319 NSAssert ([ogl_ctx isKindOfClass:[NSOpenGLContext class]],
1320 @"ogl_ctx is not an NSOpenGLContext");
1322 NSAssert (! (CGBitmapContextGetBytesPerRow (backbuffer) % 4),
1323 @"improperly-aligned backbuffer");
1325 // This gets width and height from the backbuffer in case
1326 // APPLE_client_storage is in use. See the note in createBackbuffer.
1327 // This still has to happen every frame even when APPLE_client_storage has
1328 // the video adapter pulling texture data straight from
1329 // XScreenSaverView-owned memory.
1330 glTexImage2D (gl_texture_target, 0, GL_RGBA,
1331 (GLsizei)(CGBitmapContextGetBytesPerRow (backbuffer) / 4),
1332 gl_texture_h, 0, gl_pixel_format, gl_pixel_type,
1335 GLfloat w = xwindow->frame.width, h = xwindow->frame.height;
1337 GLfloat vertices[4][2] = {{-w, h}, {w, h}, {w, -h}, {-w, -h}};
1339 GLfloat tex_coords[4][2];
1342 if (gl_texture_target != GL_TEXTURE_RECTANGLE_EXT)
1343 # endif // USE_IPHONE
1349 tex_coords[0][0] = 0;
1350 tex_coords[0][1] = 0;
1351 tex_coords[1][0] = w;
1352 tex_coords[1][1] = 0;
1353 tex_coords[2][0] = w;
1354 tex_coords[2][1] = h;
1355 tex_coords[3][0] = 0;
1356 tex_coords[3][1] = h;
1358 glVertexPointer (2, GL_FLOAT, 0, vertices);
1359 glTexCoordPointer (2, GL_FLOAT, 0, tex_coords);
1360 glDrawArrays (GL_TRIANGLE_FAN, 0, 4);
1362 # if !defined __OPTIMIZE__ || TARGET_IPHONE_SIMULATOR
1363 check_gl_error ("drawBackbuffer");
1365 # endif // BACKBUFFER_OPENGL
1368 #endif // JWXYZ_QUARTZ
1372 - (void)enableBackbuffer:(CGSize)new_backbuffer_size;
1374 jwxyz_set_matrices (new_backbuffer_size.width, new_backbuffer_size.height);
1375 check_gl_error ("enableBackbuffer");
1378 - (void)createBackbuffer:(CGSize)new_size
1380 NSAssert ([NSOpenGLContext currentContext] ==
1381 ogl_ctx, @"invalid GL context");
1382 NSAssert (xwindow->window.current_drawable == xwindow,
1383 @"current_drawable not set properly");
1386 /* On iOS, Retina means glViewport gets called with the screen size instead
1387 of the backbuffer/xwindow size. This happens in startAnimation.
1389 The GL screenhacks call glViewport themselves.
1391 glViewport (0, 0, new_size.width, new_size.height);
1394 // TODO: Preserve contents on resize.
1395 glClear (GL_COLOR_BUFFER_BIT);
1396 check_gl_error ("createBackbuffer");
1402 - (void)flushBackbuffer
1405 // Make sure the right context is active: there's two under JWXYZ_GL.
1406 jwxyz_bind_drawable (xwindow, xwindow);
1411 # ifdef JWXYZ_QUARTZ
1412 // The OpenGL pipeline is not automatically synchronized with the contents
1413 // of the backbuffer, so without glFinish, OpenGL can start rendering from
1414 // the backbuffer texture at the same time that JWXYZ is clearing and
1415 // drawing the next frame in the backing store for the backbuffer texture.
1416 // This is only a concern under JWXYZ_QUARTZ because of
1417 // APPLE_client_storage; JWXYZ_GL doesn't use that.
1419 # endif // JWXYZ_QUARTZ
1421 // If JWXYZ_GL was single-buffered, there would need to be a glFinish (or
1422 // maybe just glFlush?) here, because single-buffered contexts don't always
1423 // update what's on the screen after drawing finishes. (i.e., in safe mode)
1425 # ifdef JWXYZ_QUARTZ
1426 // JWXYZ_GL is always double-buffered.
1427 if (double_buffered_p)
1428 # endif // JWXYZ_QUARTZ
1429 [ogl_ctx flushBuffer]; // despite name, this actually swaps
1430 # else // USE_IPHONE
1432 // jwxyz_bind_drawable() only binds the framebuffer, not the renderbuffer.
1434 GLint gl_renderbuffer = xwindow->gl_renderbuffer;
1437 glBindRenderbufferOES (GL_RENDERBUFFER_OES, gl_renderbuffer);
1438 [ogl_ctx presentRenderbuffer:GL_RENDERBUFFER_OES];
1439 # endif // USE_IPHONE
1441 # if !defined __OPTIMIZE__ || TARGET_IPHONE_SIMULATOR
1442 // glGetError waits for the OpenGL command pipe to flush, so skip it in
1444 // OpenGL Programming Guide for Mac -> OpenGL Application Design
1445 // Strategies -> Allow OpenGL to Manage Your Resources
1446 // https://developer.apple.com/library/mac/documentation/GraphicsImaging/Conceptual/OpenGL-MacProgGuide/opengl_designstrategies/opengl_designstrategies.html#//apple_ref/doc/uid/TP40001987-CH2-SW7
1447 check_gl_error ("flushBackbuffer");
1452 /* Inform X11 that the size of our window has changed.
1456 if (!xdpy) return; // early
1458 NSSize new_size; // pixels, not points
1460 new_size = self.bounds.size;
1464 // If this hack ignores rotation, then that means that it pretends to
1465 // always be in portrait mode. If the View has been resized to a
1466 // landscape shape, swap width and height to keep the backbuffer
1469 double rot = current_device_rotation();
1470 if ([self ignoreRotation] && (rot == 90 || rot == -90)) {
1471 CGFloat swap = new_size.width;
1472 new_size.width = new_size.height;
1473 new_size.height = swap;
1476 double s = self.hackedContentScaleFactor;
1477 new_size.width *= s;
1478 new_size.height *= s;
1479 # endif // USE_IPHONE
1481 [self prepareContext];
1484 // On first resize, xwindow->frame is 0x0.
1485 if (xwindow->frame.width == new_size.width &&
1486 xwindow->frame.height == new_size.height)
1489 # if defined(BACKBUFFER_OPENGL) && !defined(USE_IPHONE)
1491 # endif // BACKBUFFER_OPENGL && !USE_IPHONE
1493 NSAssert (xwindow && xwindow->type == WINDOW, @"not a window");
1494 xwindow->frame.x = 0;
1495 xwindow->frame.y = 0;
1496 xwindow->frame.width = new_size.width;
1497 xwindow->frame.height = new_size.height;
1499 [self createBackbuffer:CGSizeMake(xwindow->frame.width,
1500 xwindow->frame.height)];
1502 # if defined JWXYZ_QUARTZ
1503 xwindow->cgc = backbuffer;
1504 NSAssert (xwindow->cgc, @"no CGContext");
1505 # elif defined JWXYZ_GL && !defined USE_IPHONE
1507 [ogl_ctx setView:xwindow->window.view]; // (Is this necessary?)
1508 # endif // JWXYZ_GL && USE_IPHONE
1510 jwxyz_window_resized (xdpy);
1512 # if !defined __OPTIMIZE__ || TARGET_IPHONE_SIMULATOR
1513 NSLog(@"reshape %.0fx%.0f", new_size.width, new_size.height);
1516 // Next time render_x11 is called, run the saver's reshape_cb.
1523 /* Called by SaverRunner when the device has changed orientation.
1524 That means we need to generate a resize event, even if the size
1525 has not changed (e.g., from LandscapeLeft to LandscapeRight).
1527 - (void) orientationChanged
1531 next_frame_time = 0; // Get a new frame on screen quickly
1534 /* A hook run after the 'reshape_' method has been called. Used by
1535 XScreenSaverGLView to adjust the in-scene GL viewport.
1537 - (void) postReshape
1540 #endif // USE_IPHONE
1543 // Only render_x11 should call this. XScreenSaverGLView specializes it.
1544 - (void) reshape_x11
1546 xsft->reshape_cb (xdpy, xwindow, xdata,
1547 xwindow->frame.width, xwindow->frame.height);
1556 // jwxyz_make_display needs this.
1557 [self prepareContext]; // resize_x11 also calls this.
1562 # ifdef JWXYZ_QUARTZ
1563 xwindow->cgc = backbuffer;
1564 # endif // JWXYZ_QUARTZ
1565 xdpy = jwxyz_make_display (xwindow);
1567 # if defined USE_IPHONE
1568 /* Some X11 hacks (fluidballs) want to ignore all rotation events. */
1571 TRUE; // Rotation doesn't work yet. TODO: Make rotation work.
1573 get_boolean_resource (xdpy, "ignoreRotation", "IgnoreRotation");
1574 # endif // !JWXYZ_GL
1575 # endif // USE_IPHONE
1583 xsft->setup_cb (xsft, xsft->setup_arg);
1587 NSAssert(!xdata, @"xdata already initialized");
1590 # undef ya_rand_init
1593 XSetWindowBackground (xdpy, xwindow,
1594 get_pixel_resource (xdpy, 0,
1595 "background", "Background"));
1596 XClearWindow (xdpy, xwindow);
1599 [[self window] setAcceptsMouseMovedEvents:YES];
1602 /* In MacOS 10.5, this enables "QuartzGL", meaning that the Quartz
1603 drawing primitives will run on the GPU instead of the CPU.
1604 It seems like it might make things worse rather than better,
1605 though... Plus it makes us binary-incompatible with 10.4.
1607 # if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
1608 [[self window] setPreferredBackingLocation:
1609 NSWindowBackingLocationVideoMemory];
1613 /* Kludge: even though the init_cb functions are declared to take 2 args,
1614 actually call them with 3, for the benefit of xlockmore_init() and
1617 void *(*init_cb) (Display *, Window, void *) =
1618 (void *(*) (Display *, Window, void *)) xsft->init_cb;
1620 xdata = init_cb (xdpy, xwindow, xsft->setup_arg);
1621 // NSAssert(xdata, @"no xdata from init");
1622 if (! xdata) abort();
1624 if (get_boolean_resource (xdpy, "doFPS", "DoFPS")) {
1625 fpst = fps_init (xdpy, xwindow);
1626 if (! xsft->fps_cb) xsft->fps_cb = screenhack_do_fps;
1633 if (current_device_rotation() != 0) // launched while rotated
1637 [self checkForUpdates];
1641 /* I don't understand why we have to do this *every frame*, but we do,
1642 or else the cursor comes back on.
1645 if (![self isPreview])
1646 [NSCursor setHiddenUntilMouseMoves:YES];
1652 /* This is just a guess, but the -fps code wants to know how long
1653 we were sleeping between frames.
1655 long usecs = 1000000 * [self animationTimeInterval];
1656 usecs -= 200; // caller apparently sleeps for slightly less sometimes...
1657 if (usecs < 0) usecs = 0;
1658 fps_slept (fpst, usecs);
1662 /* Run any XtAppAddInput and XtAppAddTimeOut callbacks now.
1663 Do this before delaying for next_frame_time to avoid throttling
1664 timers to the hack's frame rate.
1666 XtAppProcessEvent (XtDisplayToApplicationContext (xdpy),
1667 XtIMTimer | XtIMAlternateInput);
1670 /* It turns out that on some systems (possibly only 10.5 and older?)
1671 [ScreenSaverView setAnimationTimeInterval] does nothing. This means
1672 that we cannot rely on it.
1674 Some of the screen hacks want to delay for long periods, and letting the
1675 framework run the update function at 30 FPS when it really wanted half a
1676 minute between frames would be bad. So instead, we assume that the
1677 framework's animation timer might fire whenever, but we only invoke the
1678 screen hack's "draw frame" method when enough time has expired.
1680 This means two extra calls to gettimeofday() per frame. For fast-cycling
1681 screen savers, that might actually slow them down. Oh well.
1683 A side-effect of this is that it's not possible for a saver to request
1684 an animation interval that is faster than animationTimeInterval.
1686 HOWEVER! On modern systems where setAnimationTimeInterval is *not*
1687 ignored, it's important that it be faster than 30 FPS. 240 FPS is good.
1689 An NSTimer won't fire if the timer is already running the invocation
1690 function from a previous firing. So, if we use a 30 FPS
1691 animationTimeInterval (33333 µs) and a screenhack takes 40000 µs for a
1692 frame, there will be a 26666 µs delay until the next frame, 66666 µs
1693 after the beginning of the current frame. In other words, 25 FPS
1696 Frame rates tend to snap to values of 30/N, where N is a positive
1697 integer, i.e. 30 FPS, 15 FPS, 10, 7.5, 6. And the 'snapped' frame rate
1698 is rounded down from what it would normally be.
1700 So if we set animationTimeInterval to 1/240 instead of 1/30, frame rates
1701 become values of 60/N, 120/N, or 240/N, with coarser or finer frame rate
1702 steps for higher or lower animation time intervals respectively.
1705 gettimeofday (&tv, 0);
1706 double now = tv.tv_sec + (tv.tv_usec / 1000000.0);
1707 if (now < next_frame_time) return;
1709 // [self flushBackbuffer];
1712 // We do this here instead of in setFrame so that all the
1713 // Xlib drawing takes place under the animation timer.
1717 [ogl_ctx setView:self];
1718 # endif // !USE_IPHONE
1727 // NSAssert(xdata, @"no xdata when drawing");
1728 if (! xdata) abort();
1729 unsigned long delay = xsft->draw_cb (xdpy, xwindow, xdata);
1730 if (fpst && xsft->fps_cb)
1731 xsft->fps_cb (xdpy, xwindow, fpst, xdata);
1733 gettimeofday (&tv, 0);
1734 now = tv.tv_sec + (tv.tv_usec / 1000000.0);
1735 next_frame_time = now + (delay / 1000000.0);
1737 # ifdef JWXYZ_QUARTZ
1738 [self drawBackbuffer];
1740 // This can also happen near the beginning of render_x11.
1741 [self flushBackbuffer];
1743 # ifdef USE_IPHONE // Allow savers on the iPhone to run full-tilt.
1744 if (delay < [self animationTimeInterval])
1745 [self setAnimationTimeInterval:(delay / 1000000.0)];
1748 # ifdef DO_GC_HACKERY
1749 /* Current theory is that the 10.6 garbage collector sucks in the
1752 It only does a collection when a threshold of outstanding
1753 collectable allocations has been surpassed. However, CoreGraphics
1754 creates lots of small collectable allocations that contain pointers
1755 to very large non-collectable allocations: a small CG object that's
1756 collectable referencing large malloc'd allocations (non-collectable)
1757 containing bitmap data. So the large allocation doesn't get freed
1758 until GC collects the small allocation, which triggers its finalizer
1759 to run which frees the large allocation. So GC is deciding that it
1760 doesn't really need to run, even though the process has gotten
1761 enormous. GC eventually runs once pageouts have happened, but by
1762 then it's too late, and the machine's resident set has been
1765 So, we force an exhaustive garbage collection in this process
1766 approximately every 5 seconds whether the system thinks it needs
1770 static int tick = 0;
1771 if (++tick > 5*30) {
1773 objc_collect (OBJC_EXHAUSTIVE_COLLECTION);
1776 # endif // DO_GC_HACKERY
1780 @catch (NSException *e) {
1781 [self handleException: e];
1783 # endif // USE_IPHONE
1787 - (void) animateOneFrame
1789 // Render X11 into the backing store bitmap...
1791 # ifdef JWXYZ_QUARTZ
1792 NSAssert (backbuffer, @"no back buffer");
1795 UIGraphicsPushContext (backbuffer);
1797 # endif // JWXYZ_QUARTZ
1801 # if defined USE_IPHONE && defined JWXYZ_QUARTZ
1802 UIGraphicsPopContext();
1807 # ifndef USE_IPHONE // Doesn't exist on iOS
1809 - (void) setFrame:(NSRect) newRect
1811 [super setFrame:newRect];
1813 if (xwindow) // inform Xlib that the window has changed now.
1817 - (void) setFrameSize:(NSSize) newSize
1819 [super setFrameSize:newSize];
1824 # else // USE_IPHONE
1826 - (void) layoutSubviews
1828 [super layoutSubviews];
1837 +(BOOL) performGammaFade
1842 - (BOOL) hasConfigureSheet
1847 + (NSString *) decompressXML: (NSData *)data
1849 if (! data) return 0;
1850 BOOL compressed_p = !!strncmp ((const char *) data.bytes, "<?xml", 5);
1852 // If it's not already XML, decompress it.
1853 NSAssert (compressed_p, @"xml isn't compressed");
1855 NSMutableData *data2 = 0;
1858 memset (&zs, 0, sizeof(zs));
1859 ret = inflateInit2 (&zs, 16 + MAX_WBITS);
1861 UInt32 usize = * (UInt32 *) (data.bytes + data.length - 4);
1862 data2 = [NSMutableData dataWithLength: usize];
1863 zs.next_in = (Bytef *) data.bytes;
1864 zs.avail_in = (uint) data.length;
1865 zs.next_out = (Bytef *) data2.bytes;
1866 zs.avail_out = (uint) data2.length;
1867 ret = inflate (&zs, Z_FINISH);
1870 if (ret == Z_OK || ret == Z_STREAM_END)
1873 NSAssert2 (0, @"gunzip error: %d: %s",
1874 ret, (zs.msg ? zs.msg : "<null>"));
1877 NSString *s = [[NSString alloc]
1878 initWithData:data encoding:NSUTF8StringEncoding];
1885 - (NSWindow *) configureSheet
1887 - (UIViewController *) configureView
1890 NSBundle *bundle = [NSBundle bundleForClass:[self class]];
1891 NSString *file = [NSString stringWithCString:xsft->progclass
1892 encoding:NSISOLatin1StringEncoding];
1893 file = [file lowercaseString];
1894 NSString *path = [bundle pathForResource:file ofType:@"xml"];
1896 NSLog (@"%@.xml does not exist in the application bundle: %@/",
1897 file, [bundle resourcePath]);
1902 UIViewController *sheet;
1903 # else // !USE_IPHONE
1905 # endif // !USE_IPHONE
1907 NSData *xmld = [NSData dataWithContentsOfFile:path];
1908 NSString *xml = [[self class] decompressXML: xmld];
1909 sheet = [[XScreenSaverConfigSheet alloc]
1910 initWithXML:[xml dataUsingEncoding:NSUTF8StringEncoding]
1911 options:xsft->options
1912 controller:[prefsReader userDefaultsController]
1913 globalController:[prefsReader globalDefaultsController]
1914 defaults:[prefsReader defaultOptions]];
1916 // #### am I expected to retain this, or not? wtf.
1917 // I thought not, but if I don't do this, we (sometimes) crash.
1918 // #### Analyze says "potential leak of an object stored into sheet"
1925 - (NSUserDefaultsController *) userDefaultsController
1927 return [prefsReader userDefaultsController];
1931 /* Announce our willingness to accept keyboard input.
1933 - (BOOL)acceptsFirstResponder
1943 # else // USE_IPHONE
1945 // There's no way to play a standard system alert sound!
1946 // We'd have to include our own WAV for that.
1948 // Or we could vibrate:
1949 // #import <AudioToolbox/AudioToolbox.h>
1950 // AudioServicesPlaySystemSound (kSystemSoundID_Vibrate);
1952 // Instead, just flash the screen white, then fade.
1954 UIView *v = [[UIView alloc] initWithFrame: [self frame]];
1955 [v setBackgroundColor: [UIColor whiteColor]];
1956 [[self window] addSubview:v];
1957 [UIView animateWithDuration: 0.1
1958 animations:^{ [v setAlpha: 0.0]; }
1959 completion:^(BOOL finished) { [v removeFromSuperview]; } ];
1961 # endif // USE_IPHONE
1965 /* Send an XEvent to the hack. Returns YES if it was handled.
1967 - (BOOL) sendEvent: (XEvent *) e
1969 if (!initted_p || ![self isAnimating]) // no event handling unless running.
1973 [self prepareContext];
1974 BOOL result = xsft->event_cb (xdpy, xwindow, xdata, e);
1982 /* Convert an NSEvent into an XEvent, and pass it along.
1983 Returns YES if it was handled.
1985 - (BOOL) convertEvent: (NSEvent *) e
1989 memset (&xe, 0, sizeof(xe));
1993 int flags = [e modifierFlags];
1994 if (flags & NSAlphaShiftKeyMask) state |= LockMask;
1995 if (flags & NSShiftKeyMask) state |= ShiftMask;
1996 if (flags & NSControlKeyMask) state |= ControlMask;
1997 if (flags & NSAlternateKeyMask) state |= Mod1Mask;
1998 if (flags & NSCommandKeyMask) state |= Mod2Mask;
2000 NSPoint p = [[[e window] contentView] convertPoint:[e locationInWindow]
2003 double s = [self hackedContentScaleFactor];
2008 int y = s * ([self bounds].size.height - p.y);
2010 xe.xany.type = type;
2016 xe.xbutton.state = state;
2017 if ([e type] == NSScrollWheel)
2018 xe.xbutton.button = ([e deltaY] > 0 ? Button4 :
2019 [e deltaY] < 0 ? Button5 :
2020 [e deltaX] > 0 ? Button6 :
2021 [e deltaX] < 0 ? Button7 :
2024 xe.xbutton.button = [e buttonNumber] + 1;
2029 xe.xmotion.state = state;
2034 NSString *ns = (([e type] == NSFlagsChanged) ? 0 :
2035 [e charactersIgnoringModifiers]);
2038 if (!ns || [ns length] == 0) // dead key
2040 // Cocoa hides the difference between left and right keys.
2041 // Also we only get KeyPress events for these, no KeyRelease
2042 // (unless we hack the mod state manually. Bleh.)
2044 if (flags & NSAlphaShiftKeyMask) k = XK_Caps_Lock;
2045 else if (flags & NSShiftKeyMask) k = XK_Shift_L;
2046 else if (flags & NSControlKeyMask) k = XK_Control_L;
2047 else if (flags & NSAlternateKeyMask) k = XK_Alt_L;
2048 else if (flags & NSCommandKeyMask) k = XK_Meta_L;
2050 else if ([ns length] == 1) // real key
2052 switch ([ns characterAtIndex:0]) {
2053 case NSLeftArrowFunctionKey: k = XK_Left; break;
2054 case NSRightArrowFunctionKey: k = XK_Right; break;
2055 case NSUpArrowFunctionKey: k = XK_Up; break;
2056 case NSDownArrowFunctionKey: k = XK_Down; break;
2057 case NSPageUpFunctionKey: k = XK_Page_Up; break;
2058 case NSPageDownFunctionKey: k = XK_Page_Down; break;
2059 case NSHomeFunctionKey: k = XK_Home; break;
2060 case NSPrevFunctionKey: k = XK_Prior; break;
2061 case NSNextFunctionKey: k = XK_Next; break;
2062 case NSBeginFunctionKey: k = XK_Begin; break;
2063 case NSEndFunctionKey: k = XK_End; break;
2064 case NSF1FunctionKey: k = XK_F1; break;
2065 case NSF2FunctionKey: k = XK_F2; break;
2066 case NSF3FunctionKey: k = XK_F3; break;
2067 case NSF4FunctionKey: k = XK_F4; break;
2068 case NSF5FunctionKey: k = XK_F5; break;
2069 case NSF6FunctionKey: k = XK_F6; break;
2070 case NSF7FunctionKey: k = XK_F7; break;
2071 case NSF8FunctionKey: k = XK_F8; break;
2072 case NSF9FunctionKey: k = XK_F9; break;
2073 case NSF10FunctionKey: k = XK_F10; break;
2074 case NSF11FunctionKey: k = XK_F11; break;
2075 case NSF12FunctionKey: k = XK_F12; break;
2079 [ns cStringUsingEncoding:NSISOLatin1StringEncoding];
2080 k = (s && *s ? *s : 0);
2086 if (! k) return YES; // E.g., "KeyRelease XK_Shift_L"
2088 xe.xkey.keycode = k;
2089 xe.xkey.state = state;
2093 NSAssert1 (0, @"unknown X11 event type: %d", type);
2097 return [self sendEvent: &xe];
2101 - (void) mouseDown: (NSEvent *) e
2103 if (! [self convertEvent:e type:ButtonPress])
2104 [super mouseDown:e];
2107 - (void) mouseUp: (NSEvent *) e
2109 if (! [self convertEvent:e type:ButtonRelease])
2113 - (void) otherMouseDown: (NSEvent *) e
2115 if (! [self convertEvent:e type:ButtonPress])
2116 [super otherMouseDown:e];
2119 - (void) otherMouseUp: (NSEvent *) e
2121 if (! [self convertEvent:e type:ButtonRelease])
2122 [super otherMouseUp:e];
2125 - (void) mouseMoved: (NSEvent *) e
2127 if (! [self convertEvent:e type:MotionNotify])
2128 [super mouseMoved:e];
2131 - (void) mouseDragged: (NSEvent *) e
2133 if (! [self convertEvent:e type:MotionNotify])
2134 [super mouseDragged:e];
2137 - (void) otherMouseDragged: (NSEvent *) e
2139 if (! [self convertEvent:e type:MotionNotify])
2140 [super otherMouseDragged:e];
2143 - (void) scrollWheel: (NSEvent *) e
2145 if (! [self convertEvent:e type:ButtonPress])
2146 [super scrollWheel:e];
2149 - (void) keyDown: (NSEvent *) e
2151 if (! [self convertEvent:e type:KeyPress])
2155 - (void) keyUp: (NSEvent *) e
2157 if (! [self convertEvent:e type:KeyRelease])
2161 - (void) flagsChanged: (NSEvent *) e
2163 if (! [self convertEvent:e type:KeyPress])
2164 [super flagsChanged:e];
2168 - (NSOpenGLPixelFormat *) getGLPixelFormat
2170 NSAssert (prefsReader, @"no prefsReader for getGLPixelFormat");
2172 NSOpenGLPixelFormatAttribute attrs[40];
2174 attrs[i++] = NSOpenGLPFAColorSize; attrs[i++] = 24;
2176 /* OpenGL's core profile removes a lot of the same stuff that was removed in
2177 OpenGL ES (e.g. glBegin, glDrawPixels), so it might be a possibility.
2179 opengl_core_p = True;
2180 if (opengl_core_p) {
2181 attrs[i++] = NSOpenGLPFAOpenGLProfile;
2182 attrs[i++] = NSOpenGLProfileVersion3_2Core;
2186 /* Eventually: multisampled pixmaps. May not be supported everywhere.
2187 if (multi_sample_p) {
2188 attrs[i++] = NSOpenGLPFASampleBuffers; attrs[i++] = 1;
2189 attrs[i++] = NSOpenGLPFASamples; attrs[i++] = 6;
2193 # ifdef JWXYZ_QUARTZ
2194 // Under Quartz, we're just blitting a texture.
2195 if (double_buffered_p)
2196 attrs[i++] = NSOpenGLPFADoubleBuffer;
2200 /* Under OpenGL, all sorts of drawing commands are being issued, and it might
2201 be a performance problem if this activity occurs on the front buffer.
2202 Also, some screenhacks expect OS X/iOS to always double-buffer.
2203 NSOpenGLPFABackingStore prevents flickering with screenhacks that
2204 don't redraw the entire screen every frame.
2206 attrs[i++] = NSOpenGLPFADoubleBuffer;
2207 attrs[i++] = NSOpenGLPFABackingStore;
2210 attrs[i++] = NSOpenGLPFAWindow;
2212 attrs[i++] = NSOpenGLPFAPixelBuffer;
2213 /* ...But not NSOpenGLPFAFullScreen, because that would be for
2214 [NSOpenGLContext setFullScreen].
2218 /* NSOpenGLPFAFullScreen would go here if initWithFrame's isPreview == NO.
2223 NSOpenGLPixelFormat *p = [[NSOpenGLPixelFormat alloc]
2224 initWithAttributes:attrs];
2232 - (void) stopAndClose:(Bool)relaunch_p
2234 if ([self isAnimating])
2235 [self stopAnimation];
2237 /* Need to make the SaverListController be the firstResponder again
2238 so that it can continue to receive its own shake events. I
2239 suppose that this abstraction-breakage means that I'm adding
2240 XScreenSaverView to the UINavigationController wrong...
2242 // UIViewController *v = [[self window] rootViewController];
2243 // if ([v isKindOfClass: [UINavigationController class]]) {
2244 // UINavigationController *n = (UINavigationController *) v;
2245 // [[n topViewController] becomeFirstResponder];
2247 [self resignFirstResponder];
2249 if (relaunch_p) { // Fake a shake on the SaverListController.
2250 [_delegate didShake:self];
2251 } else { // Not launching another, animate our return to the list.
2252 # if !defined __OPTIMIZE__ || TARGET_IPHONE_SIMULATOR
2253 NSLog (@"fading back to saver list");
2255 [_delegate wantsFadeOut:self];
2260 /* We distinguish between taps and drags.
2262 - Drags/pans (down, motion, up) are sent to the saver to handle.
2263 - Single-taps exit the saver.
2264 - Double-taps are sent to the saver as a "Space" keypress.
2265 - Swipes (really, two-finger drags/pans) send Up/Down/Left/RightArrow keys.
2267 This means a saver cannot respond to a single-tap. Only a few try to.
2270 - (void)initGestures
2272 UITapGestureRecognizer *dtap = [[UITapGestureRecognizer alloc]
2274 action:@selector(handleDoubleTap)];
2275 dtap.numberOfTapsRequired = 2;
2276 dtap.numberOfTouchesRequired = 1;
2278 UITapGestureRecognizer *stap = [[UITapGestureRecognizer alloc]
2280 action:@selector(handleTap)];
2281 stap.numberOfTapsRequired = 1;
2282 stap.numberOfTouchesRequired = 1;
2284 UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]
2286 action:@selector(handlePan:)];
2287 pan.maximumNumberOfTouches = 1;
2288 pan.minimumNumberOfTouches = 1;
2290 // I couldn't get Swipe to work, but using a second Pan recognizer works.
2291 UIPanGestureRecognizer *pan2 = [[UIPanGestureRecognizer alloc]
2293 action:@selector(handlePan2:)];
2294 pan2.maximumNumberOfTouches = 2;
2295 pan2.minimumNumberOfTouches = 2;
2297 // Also handle long-touch, and treat that the same as Pan.
2298 // Without this, panning doesn't start until there's motion, so the trick
2299 // of holding down your finger to freeze the scene doesn't work.
2301 UILongPressGestureRecognizer *hold = [[UILongPressGestureRecognizer alloc]
2303 action:@selector(handleLongPress:)];
2304 hold.numberOfTapsRequired = 0;
2305 hold.numberOfTouchesRequired = 1;
2306 hold.minimumPressDuration = 0.25; /* 1/4th second */
2308 [stap requireGestureRecognizerToFail: dtap];
2309 [stap requireGestureRecognizerToFail: hold];
2310 [dtap requireGestureRecognizerToFail: hold];
2311 [pan requireGestureRecognizerToFail: hold];
2313 [self setMultipleTouchEnabled:YES];
2315 [self addGestureRecognizer: dtap];
2316 [self addGestureRecognizer: stap];
2317 [self addGestureRecognizer: pan];
2318 [self addGestureRecognizer: pan2];
2319 [self addGestureRecognizer: hold];
2329 /* Given a mouse (touch) coordinate in unrotated, unscaled view coordinates,
2330 convert it to what X11 and OpenGL expect.
2332 Getting this crap right is tricky, given the confusion of the various
2333 scale factors, so here's a checklist that I think covers all of the X11
2334 and OpenGL cases. For each of these: rotate to all 4 orientations;
2335 ensure the mouse tracks properly to all 4 corners.
2337 Test it in Xcode 6, because Xcode 5.0.2 can't run the iPhone6+ simulator.
2339 Test hacks must cover:
2340 X11 ignoreRotation = true
2341 X11 ignoreRotation = false
2342 OpenGL (rotation is handled manually, so they never ignoreRotation)
2344 Test devices must cover:
2345 contentScaleFactor = 1, hackedContentScaleFactor = 1 (iPad 2)
2346 contentScaleFactor = 2, hackedContentScaleFactor = 1 (iPad Retina Air)
2347 contentScaleFactor = 2, hackedContentScaleFactor = 2 (iPhone 5 5s 6 6+)
2349 iPad 2: 768x1024 / 1 = 768x1024
2350 iPad Air: 1536x2048 / 2 = 768x1024 (iPad Retina is identical)
2351 iPhone 4s: 640x960 / 2 = 320x480
2352 iPhone 5: 640x1136 / 2 = 320x568 (iPhone 5s and iPhone 6 are identical)
2353 iPhone 6+: 640x1136 / 2 = 320x568 (nativeBounds 960x1704 nativeScale 3)
2356 iPad2 iPadAir iPhone4s iPhone5 iPhone6+
2357 Attraction X yes - - - - Y
2358 Fireworkx X no - - - - Y
2359 Carousel GL yes - - - - Y
2360 Voronoi GL no - - - - -
2362 - (void) convertMouse:(CGPoint *)p
2364 CGFloat xx = p->x, yy = p->y;
2366 # if TARGET_IPHONE_SIMULATOR
2368 XWindowAttributes xgwa;
2369 XGetWindowAttributes (xdpy, xwindow, &xgwa);
2370 NSLog (@"TOUCH %4g, %-4g in %4d x %-4d cs=%.0f hcs=%.0f r=%d ig=%d\n",
2372 xgwa.width, xgwa.height,
2373 [self contentScaleFactor],
2374 [self hackedContentScaleFactor],
2375 [self rotateTouches], [self ignoreRotation]);
2377 # endif // TARGET_IPHONE_SIMULATOR
2379 if ([self rotateTouches]) {
2381 // The XScreenSaverGLView case:
2382 // The X11 window is rotated, as is the framebuffer.
2383 // The device coordinates match the framebuffer dimensions,
2384 // but might have axes swapped... and we need to swap them
2387 int w = [self frame].size.width;
2388 int h = [self frame].size.height;
2389 GLfloat xr = (GLfloat) xx / w;
2390 GLfloat yr = (GLfloat) yy / h;
2392 int o = (int) current_device_rotation();
2394 case -90: case 270: swap = xr; xr = 1-yr; yr = swap; break;
2395 case 90: case -270: swap = xr; xr = yr; yr = 1-swap; break;
2396 case 180: case -180: xr = 1-xr; yr = 1-yr; break;
2402 } else if ([self ignoreRotation]) {
2404 // The X11 case, where the hack has opted not to rotate:
2405 // The X11 window is unrotated, but the framebuffer is rotated.
2406 // The device coordinates match the framebuffer, so they need to
2407 // be de-rotated to match the X11 window.
2409 int w = [self frame].size.width;
2410 int h = [self frame].size.height;
2412 int o = (int) current_device_rotation();
2414 case -90: case 270: swap = xx; xx = h-yy; yy = swap; break;
2415 case 90: case -270: swap = xx; xx = yy; yy = w-swap; break;
2416 case 180: case -180: xx = w-xx; yy = h-yy; break;
2421 double s = [self hackedContentScaleFactor];
2425 # if TARGET_IPHONE_SIMULATOR || !defined __OPTIMIZE__
2427 XWindowAttributes xgwa;
2428 XGetWindowAttributes (xdpy, xwindow, &xgwa);
2429 NSLog (@"touch %4g, %-4g in %4d x %-4d cs=%.0f hcs=%.0f r=%d ig=%d\n",
2431 xgwa.width, xgwa.height,
2432 [self contentScaleFactor],
2433 [self hackedContentScaleFactor],
2434 [self rotateTouches], [self ignoreRotation]);
2435 if (p->x < 0 || p->y < 0 || p->x > xgwa.width || p->y > xgwa.height)
2438 # endif // TARGET_IPHONE_SIMULATOR
2442 /* Single click exits saver.
2446 [self stopAndClose:NO];
2450 /* Double click sends Space KeyPress.
2452 - (void) handleDoubleTap
2454 if (!xsft->event_cb || !xwindow) return;
2457 memset (&xe, 0, sizeof(xe));
2458 xe.xkey.keycode = ' ';
2459 xe.xany.type = KeyPress;
2460 BOOL ok1 = [self sendEvent: &xe];
2461 xe.xany.type = KeyRelease;
2462 BOOL ok2 = [self sendEvent: &xe];
2468 /* Drag with one finger down: send MotionNotify.
2470 - (void) handlePan:(UIGestureRecognizer *)sender
2472 if (!xsft->event_cb || !xwindow) return;
2475 memset (&xe, 0, sizeof(xe));
2477 CGPoint p = [sender locationInView:self]; // this is in points, not pixels
2478 [self convertMouse:&p];
2479 NSAssert (xwindow && xwindow->type == WINDOW, @"not a window");
2480 xwindow->window.last_mouse_x = p.x;
2481 xwindow->window.last_mouse_y = p.y;
2483 switch (sender.state) {
2484 case UIGestureRecognizerStateBegan:
2485 xe.xany.type = ButtonPress;
2486 xe.xbutton.button = 1;
2491 case UIGestureRecognizerStateEnded:
2492 xe.xany.type = ButtonRelease;
2493 xe.xbutton.button = 1;
2498 case UIGestureRecognizerStateChanged:
2499 xe.xany.type = MotionNotify;
2508 BOOL ok = [self sendEvent: &xe];
2509 if (!ok && xe.xany.type == ButtonRelease)
2514 /* Hold one finger down: assume we're about to start dragging.
2515 Treat the same as Pan.
2517 - (void) handleLongPress:(UIGestureRecognizer *)sender
2519 [self handlePan:sender];
2524 /* Drag with 2 fingers down: send arrow keys.
2526 - (void) handlePan2:(UIPanGestureRecognizer *)sender
2528 if (!xsft->event_cb || !xwindow) return;
2530 if (sender.state != UIGestureRecognizerStateEnded)
2534 memset (&xe, 0, sizeof(xe));
2536 CGPoint p = [sender locationInView:self]; // this is in points, not pixels
2537 [self convertMouse:&p];
2539 if (fabs(p.x) > fabs(p.y))
2540 xe.xkey.keycode = (p.x > 0 ? XK_Right : XK_Left);
2542 xe.xkey.keycode = (p.y > 0 ? XK_Down : XK_Up);
2544 BOOL ok1 = [self sendEvent: &xe];
2545 xe.xany.type = KeyRelease;
2546 BOOL ok2 = [self sendEvent: &xe];
2552 /* We need this to respond to "shake" gestures
2554 - (BOOL)canBecomeFirstResponder
2559 - (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
2564 - (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event
2568 /* Shake means exit and launch a new saver.
2570 - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
2572 [self stopAndClose:YES];
2576 - (void)setScreenLocked:(BOOL)locked
2578 if (screenLocked == locked) return;
2579 screenLocked = locked;
2581 if ([self isAnimating])
2582 [self stopAnimation];
2584 if (! [self isAnimating])
2585 [self startAnimation];
2589 - (NSDictionary *)getGLProperties
2591 return [NSDictionary dictionaryWithObjectsAndKeys:
2592 kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat,
2594 /* This could be disabled if we knew the screen would be redrawn
2595 entirely for every frame.
2597 [NSNumber numberWithBool:YES], kEAGLDrawablePropertyRetainedBacking,
2602 - (void)addExtraRenderbuffers:(CGSize)size
2604 // No extra renderbuffers are needed for 2D screenhacks.
2608 - (NSString *)getCAGravity
2610 return kCAGravityCenter; // Looks better in e.g. Compass.
2611 // return kCAGravityBottomLeft;
2614 #endif // USE_IPHONE
2617 - (void) checkForUpdates
2620 // We only check once at startup, even if there are multiple screens,
2621 // and even if this saver is running for many days.
2622 // (Uh, except this doesn't work because this static isn't shared,
2623 // even if we make it an exported global. Not sure why. Oh well.)
2624 static BOOL checked_p = NO;
2625 if (checked_p) return;
2628 // If it's off, don't bother running the updater. Otherwise, the
2629 // updater will decide if it's time to hit the network.
2630 if (! get_boolean_resource (xdpy,
2631 SUSUEnableAutomaticChecksKey,
2632 SUSUEnableAutomaticChecksKey))
2635 NSString *updater = @"XScreenSaverUpdater.app";
2637 // There may be multiple copies of the updater: e.g., one in /Applications
2638 // and one in the mounted installer DMG! It's important that we run the
2639 // one from the disk and not the DMG, so search for the right one.
2641 NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
2642 NSBundle *bundle = [NSBundle bundleForClass:[self class]];
2644 @[[[bundle bundlePath] stringByDeletingLastPathComponent],
2645 [@"~/Library/Screen Savers" stringByExpandingTildeInPath],
2646 @"/Library/Screen Savers",
2647 @"/System/Library/Screen Savers",
2649 @"/Applications/Utilities"];
2650 NSString *app_path = nil;
2651 for (NSString *dir in search) {
2652 NSString *p = [dir stringByAppendingPathComponent:updater];
2653 if ([[NSFileManager defaultManager] fileExistsAtPath:p]) {
2660 app_path = [workspace fullPathForApplication:updater];
2662 if (app_path && [app_path hasPrefix:@"/Volumes/XScreenSaver "])
2663 app_path = 0; // The DMG version will not do.
2666 NSLog(@"Unable to find %@", updater);
2671 if (! [workspace launchApplicationAtURL:[NSURL fileURLWithPath:app_path]
2672 options:(NSWorkspaceLaunchWithoutAddingToRecents |
2673 NSWorkspaceLaunchWithoutActivation |
2674 NSWorkspaceLaunchAndHide)
2675 configuration:[NSMutableDictionary dictionary]
2677 NSLog(@"Unable to launch %@: %@", app_path, err);
2680 # endif // !USE_IPHONE
2686 /* Utility functions...
2689 static PrefsReader *
2690 get_prefsReader (Display *dpy)
2692 XScreenSaverView *view = jwxyz_window_view (XRootWindow (dpy, 0));
2693 if (!view) return 0;
2694 return [view prefsReader];
2699 get_string_resource (Display *dpy, char *name, char *class)
2701 return [get_prefsReader(dpy) getStringResource:name];
2705 get_boolean_resource (Display *dpy, char *name, char *class)
2707 return [get_prefsReader(dpy) getBooleanResource:name];
2711 get_integer_resource (Display *dpy, char *name, char *class)
2713 return [get_prefsReader(dpy) getIntegerResource:name];
2717 get_float_resource (Display *dpy, char *name, char *class)
2719 return [get_prefsReader(dpy) getFloatResource:name];