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 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 /* 10.6 SDK */
45 # import <objc/objc-auto.h>
46 # define DO_GC_HACKERY
49 /* Duplicated in xlockmoreI.h and XScreenSaverGLView.m. */
50 extern void clear_gl_error (void);
51 extern void check_gl_error (const char *type);
53 extern struct xscreensaver_function_table *xscreensaver_function_table;
55 /* Global variables used by the screen savers
58 const char *progclass;
64 # define NSSizeToCGSize(x) (x)
66 extern NSDictionary *make_function_table_dict(void); // ios-function-table.m
68 /* Stub definition of the superclass, for iPhone.
70 @implementation ScreenSaverView
72 NSTimeInterval anim_interval;
77 - (id)initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview {
78 self = [super initWithFrame:frame];
80 anim_interval = 1.0/30;
83 - (NSTimeInterval)animationTimeInterval { return anim_interval; }
84 - (void)setAnimationTimeInterval:(NSTimeInterval)i { anim_interval = i; }
85 - (BOOL)hasConfigureSheet { return NO; }
86 - (NSWindow *)configureSheet { return nil; }
87 - (NSView *)configureView { return nil; }
88 - (BOOL)isPreview { return NO; }
89 - (BOOL)isAnimating { return animating_p; }
90 - (void)animateOneFrame { }
92 - (void)startAnimation {
93 if (animating_p) return;
95 anim_timer = [NSTimer scheduledTimerWithTimeInterval: anim_interval
97 selector:@selector(animateOneFrame)
102 - (void)stopAnimation {
104 [anim_timer invalidate];
111 # endif // !USE_IPHONE
115 @interface XScreenSaverView (Private)
116 - (void) stopAndClose:(Bool)relaunch;
119 @implementation XScreenSaverView
121 // Given a lower-cased saver name, returns the function table for it.
122 // If no name, guess the name from the class's bundle name.
124 - (struct xscreensaver_function_table *) findFunctionTable:(NSString *)name
126 NSBundle *nsb = [NSBundle bundleForClass:[self class]];
127 NSAssert1 (nsb, @"no bundle for class %@", [self class]);
129 NSString *path = [nsb bundlePath];
130 CFURLRef url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
132 kCFURLPOSIXPathStyle,
134 CFBundleRef cfb = CFBundleCreate (kCFAllocatorDefault, url);
136 NSAssert1 (cfb, @"no CFBundle for \"%@\"", path);
137 // #### Analyze says "Potential leak of an object stored into cfb"
140 name = [[path lastPathComponent] stringByDeletingPathExtension];
142 name = [[name lowercaseString]
143 stringByReplacingOccurrencesOfString:@" "
147 // CFBundleGetDataPointerForName doesn't work in "Archive" builds.
148 // I'm guessing that symbol-stripping is mandatory. Fuck.
149 NSString *table_name = [name stringByAppendingString:
150 @"_xscreensaver_function_table"];
151 void *addr = CFBundleGetDataPointerForName (cfb, (CFStringRef) table_name);
155 NSLog (@"no symbol \"%@\" for \"%@\"", table_name, path);
158 // Depends on the auto-generated "ios-function-table.m" being up to date.
159 if (! function_tables)
160 function_tables = [make_function_table_dict() retain];
161 NSValue *v = [function_tables objectForKey: name];
162 void *addr = v ? [v pointerValue] : 0;
163 # endif // USE_IPHONE
165 return (struct xscreensaver_function_table *) addr;
169 // Add the "Contents/Resources/" subdirectory of this screen saver's .bundle
170 // to $PATH for the benefit of savers that include helper shell scripts.
172 - (void) setShellPath
174 NSBundle *nsb = [NSBundle bundleForClass:[self class]];
175 NSAssert1 (nsb, @"no bundle for class %@", [self class]);
177 NSString *nsdir = [nsb resourcePath];
178 NSAssert1 (nsdir, @"no resourcePath for class %@", [self class]);
179 const char *dir = [nsdir cStringUsingEncoding:NSUTF8StringEncoding];
180 const char *opath = getenv ("PATH");
181 if (!opath) opath = "/bin"; // $PATH is unset when running under Shark!
182 char *npath = (char *) malloc (strlen (opath) + strlen (dir) + 2);
185 strcat (npath, opath);
186 if (setenv ("PATH", npath, 1)) {
188 NSAssert1 (0, @"setenv \"PATH=%s\" failed", npath);
195 // set an $XSCREENSAVER_CLASSPATH variable so that included shell scripts
196 // (e.g., "xscreensaver-text") know how to look up resources.
198 - (void) setResourcesEnv:(NSString *) name
200 NSBundle *nsb = [NSBundle bundleForClass:[self class]];
201 NSAssert1 (nsb, @"no bundle for class %@", [self class]);
203 const char *s = [name cStringUsingEncoding:NSUTF8StringEncoding];
204 if (setenv ("XSCREENSAVER_CLASSPATH", s, 1)) {
206 NSAssert1 (0, @"setenv \"XSCREENSAVER_CLASSPATH=%s\" failed", s);
211 - (void) loadCustomFonts
214 NSBundle *nsb = [NSBundle bundleForClass:[self class]];
215 NSMutableArray *fonts = [NSMutableArray arrayWithCapacity:20];
216 for (NSString *ext in @[@"ttf", @"otf"]) {
217 [fonts addObjectsFromArray: [nsb pathsForResourcesOfType:ext
220 for (NSString *font in fonts) {
221 CFURLRef url = (CFURLRef) [NSURL fileURLWithPath: font];
223 if (! CTFontManagerRegisterFontsForURL (url, kCTFontManagerScopeProcess,
225 // Just ignore errors:
226 // "The file has already been registered in the specified scope."
227 // NSLog (@"loading font: %@ %@", url, err);
230 # endif // !USE_IPHONE
235 add_default_options (const XrmOptionDescRec *opts,
236 const char * const *defs,
237 XrmOptionDescRec **opts_ret,
238 const char ***defs_ret)
240 /* These aren't "real" command-line options (there are no actual command-line
241 options in the Cocoa version); but this is the somewhat kludgey way that
242 the <xscreensaver-text /> and <xscreensaver-image /> tags in the
243 ../hacks/config/\*.xml files communicate with the preferences database.
245 static const XrmOptionDescRec default_options [] = {
246 { "-text-mode", ".textMode", XrmoptionSepArg, 0 },
247 { "-text-literal", ".textLiteral", XrmoptionSepArg, 0 },
248 { "-text-file", ".textFile", XrmoptionSepArg, 0 },
249 { "-text-url", ".textURL", XrmoptionSepArg, 0 },
250 { "-text-program", ".textProgram", XrmoptionSepArg, 0 },
251 { "-grab-desktop", ".grabDesktopImages", XrmoptionNoArg, "True" },
252 { "-no-grab-desktop", ".grabDesktopImages", XrmoptionNoArg, "False"},
253 { "-choose-random-images", ".chooseRandomImages",XrmoptionNoArg, "True" },
254 { "-no-choose-random-images",".chooseRandomImages",XrmoptionNoArg, "False"},
255 { "-image-directory", ".imageDirectory", XrmoptionSepArg, 0 },
256 { "-fps", ".doFPS", XrmoptionNoArg, "True" },
257 { "-no-fps", ".doFPS", XrmoptionNoArg, "False"},
258 { "-foreground", ".foreground", XrmoptionSepArg, 0 },
259 { "-fg", ".foreground", XrmoptionSepArg, 0 },
260 { "-background", ".background", XrmoptionSepArg, 0 },
261 { "-bg", ".background", XrmoptionSepArg, 0 },
264 // <xscreensaver-updater />
265 { "-" SUSUEnableAutomaticChecksKey,
266 "." SUSUEnableAutomaticChecksKey, XrmoptionNoArg, "True" },
267 { "-no-" SUSUEnableAutomaticChecksKey,
268 "." SUSUEnableAutomaticChecksKey, XrmoptionNoArg, "False" },
269 { "-" SUAutomaticallyUpdateKey,
270 "." SUAutomaticallyUpdateKey, XrmoptionNoArg, "True" },
271 { "-no-" SUAutomaticallyUpdateKey,
272 "." SUAutomaticallyUpdateKey, XrmoptionNoArg, "False" },
273 { "-" SUSendProfileInfoKey,
274 "." SUSendProfileInfoKey, XrmoptionNoArg,"True" },
275 { "-no-" SUSendProfileInfoKey,
276 "." SUSendProfileInfoKey, XrmoptionNoArg,"False"},
277 { "-" SUScheduledCheckIntervalKey,
278 "." SUScheduledCheckIntervalKey, XrmoptionSepArg, 0 },
279 # endif // !USE_IPHONE
283 static const char *default_defaults [] = {
285 # if defined(USE_IPHONE) && !defined(__OPTIMIZE__)
290 ".doubleBuffer: True",
291 ".multiSample: False",
299 ".textURL: https://en.wikipedia.org/w/index.php?title=Special:NewPages&feed=rss",
301 ".grabDesktopImages: yes",
303 ".chooseRandomImages: no",
305 ".chooseRandomImages: yes",
307 ".imageDirectory: ~/Pictures",
309 ".texFontCacheSize: 30",
313 # define STR(S) STR1(S)
314 # define __objc_yes Yes
315 # define __objc_no No
316 "." SUSUEnableAutomaticChecksKey ": " STR(SUSUEnableAutomaticChecksDef),
317 "." SUAutomaticallyUpdateKey ": " STR(SUAutomaticallyUpdateDef),
318 "." SUSendProfileInfoKey ": " STR(SUSendProfileInfoDef),
319 "." SUScheduledCheckIntervalKey ": " STR(SUScheduledCheckIntervalDef),
324 # endif // USE_IPHONE
329 for (i = 0; default_options[i].option; i++)
331 for (i = 0; opts[i].option; i++)
334 XrmOptionDescRec *opts2 = (XrmOptionDescRec *)
335 calloc (count + 1, sizeof (*opts2));
339 while (default_options[j].option) {
340 opts2[i] = default_options[j];
344 while (opts[j].option) {
355 for (i = 0; default_defaults[i]; i++)
357 for (i = 0; defs[i]; i++)
360 const char **defs2 = (const char **) calloc (count + 1, sizeof (*defs2));
364 while (default_defaults[j]) {
365 defs2[i] = default_defaults[j];
378 - (id) initWithFrame:(NSRect)frame
379 saverName:(NSString *)saverName
380 isPreview:(BOOL)isPreview
382 if (! (self = [super initWithFrame:frame isPreview:isPreview]))
385 xsft = [self findFunctionTable: saverName];
395 xsft->setup_cb (xsft, xsft->setup_arg);
397 /* The plist files for these preferences show up in
398 $HOME/Library/Preferences/ByHost/ in a file named like
399 "org.jwz.xscreensaver.<SAVERNAME>.<NUMBERS>.plist"
401 NSString *name = [NSString stringWithCString:xsft->progclass
402 encoding:NSISOLatin1StringEncoding];
403 name = [@"org.jwz.xscreensaver." stringByAppendingString:name];
404 [self setResourcesEnv:name];
405 [self loadCustomFonts];
407 XrmOptionDescRec *opts = 0;
408 const char **defs = 0;
409 add_default_options (xsft->options, xsft->defaults, &opts, &defs);
410 prefsReader = [[PrefsReader alloc]
411 initWithName:name xrmKeys:opts defaults:defs];
413 // free (opts); // bah, we need these! #### leak!
414 xsft->options = opts;
416 progname = progclass = xsft->progclass;
420 # if !defined USE_IPHONE && defined JWXYZ_QUARTZ
421 // When the view fills the screen and double buffering is enabled, OS X will
422 // use page flipping for a minor CPU/FPS boost. In windowed mode, double
423 // buffering reduces the frame rate to 1/2 the screen's refresh rate.
424 double_buffered_p = !isPreview;
430 // So we can tell when we're docked.
431 [UIDevice currentDevice].batteryMonitoringEnabled = YES;
433 [self setBackgroundColor:[NSColor blackColor]];
434 # endif // USE_IPHONE
443 return [CAEAGLLayer class];
448 - (id) initWithFrame:(NSRect)frame isPreview:(BOOL)p
450 return [self initWithFrame:frame saverName:0 isPreview:p];
456 if ([self isAnimating])
457 [self stopAnimation];
458 NSAssert(!xdata, @"xdata not yet freed");
459 NSAssert(!xdpy, @"xdpy not yet freed");
462 [[NSNotificationCenter defaultCenter] removeObserver:self];
465 # ifdef BACKBUFFER_OPENGL
468 # endif // !USE_IPHONE
470 // Releasing the OpenGL context should also free any OpenGL objects,
471 // including the backbuffer texture and frame/render/depthbuffers.
472 # endif // BACKBUFFER_OPENGL
474 # if defined JWXYZ_GL && defined USE_IPHONE
475 [ogl_ctx_pixmap release];
480 CGColorSpaceRelease (colorspace);
481 # endif // JWXYZ_QUARTZ
483 [prefsReader release];
491 - (PrefsReader *) prefsReader
498 - (void) lockFocus { }
499 - (void) unlockFocus { }
505 /* A few seconds after the saver launches, we store the "wasRunning"
506 preference. This is so that if the saver is crashing at startup,
507 we don't launch it again next time, getting stuck in a crash loop.
509 - (void) allSystemsGo: (NSTimer *) timer
511 NSAssert (timer == crash_timer, @"crash timer screwed up");
514 NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
515 [prefs setBool:YES forKey:@"wasRunning"];
525 CGSize screen_size = self.bounds.size;
526 double s = self.contentScaleFactor;
527 screen_size.width *= s;
528 screen_size.height *= s;
531 GLuint *framebuffer = &xwindow->gl_framebuffer;
532 GLuint *renderbuffer = &xwindow->gl_renderbuffer;
533 xwindow->window.current_drawable = xwindow;
534 #elif defined JWXYZ_QUARTZ
535 GLuint *framebuffer = &gl_framebuffer;
536 GLuint *renderbuffer = &gl_renderbuffer;
537 #endif // JWXYZ_QUARTZ
539 if (*framebuffer) glDeleteFramebuffersOES (1, framebuffer);
540 if (*renderbuffer) glDeleteRenderbuffersOES (1, renderbuffer);
542 create_framebuffer (framebuffer, renderbuffer);
545 // glRenderbufferStorageOES (GL_RENDERBUFFER_OES, GL_RGBA8_OES,
546 // (int)size.width, (int)size.height);
547 [ogl_ctx renderbufferStorage:GL_RENDERBUFFER_OES
548 fromDrawable:(CAEAGLLayer*)self.layer];
550 glFramebufferRenderbufferOES (GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES,
551 GL_RENDERBUFFER_OES, *renderbuffer);
553 [self addExtraRenderbuffers:screen_size];
555 check_framebuffer_status();
560 - (void) startAnimation
562 NSAssert(![self isAnimating], @"already animating");
563 NSAssert(!initted_p && !xdata, @"already initialized");
565 // See comment in render_x11() for why this value is important:
566 [self setAnimationTimeInterval: 1.0 / 240.0];
568 [super startAnimation];
569 /* We can't draw on the window from this method, so we actually do the
570 initialization of the screen saver (xsft->init_cb) in the first call
571 to animateOneFrame() instead.
576 [crash_timer invalidate];
578 NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
579 [prefs removeObjectForKey:@"wasRunning"];
582 crash_timer = [NSTimer scheduledTimerWithTimeInterval: 5
584 selector:@selector(allSystemsGo:)
588 # endif // USE_IPHONE
590 // Never automatically turn the screen off if we are docked,
591 // and an animation is running.
594 [UIApplication sharedApplication].idleTimerDisabled =
595 ([UIDevice currentDevice].batteryState != UIDeviceBatteryStateUnplugged);
596 [[UIApplication sharedApplication]
597 setStatusBarHidden:YES withAnimation:UIStatusBarAnimationNone];
600 xwindow = (Window) calloc (1, sizeof(*xwindow));
601 xwindow->type = WINDOW;
602 xwindow->window.view = self;
603 CFRetain (xwindow->window.view); // needed for garbage collection?
605 #ifdef BACKBUFFER_OPENGL
606 CGSize new_backbuffer_size;
612 pixfmt = [self getGLPixelFormat];
615 NSAssert (pixfmt, @"unable to create NSOpenGLPixelFormat");
617 // Fun: On OS X 10.7, the second time an OpenGL context is created, after
618 // the preferences dialog is launched in SaverTester, the context only
619 // lasts until the first full GC. Then it turns black. Solution is to
620 // reuse the OpenGL context after this point.
621 // "Analyze" says that both pixfmt and ogl_ctx are leaked.
622 ogl_ctx = [[NSOpenGLContext alloc] initWithFormat:pixfmt
625 // Sync refreshes to the vertical blanking interval
627 [ogl_ctx setValues:&r forParameter:NSOpenGLCPSwapInterval];
628 // check_gl_error ("NSOpenGLCPSwapInterval"); // SEGV sometimes. Too early?
631 [ogl_ctx makeCurrentContext];
632 check_gl_error ("makeCurrentContext");
634 // NSOpenGLContext logs an 'invalid drawable' when this is called
635 // from initWithFrame.
636 [ogl_ctx setView:self];
638 // This may not be necessary if there's FBO support.
640 xwindow->window.pixfmt = pixfmt;
641 CFRetain (xwindow->window.pixfmt);
642 xwindow->window.virtual_screen = [ogl_ctx currentVirtualScreen];
643 xwindow->window.current_drawable = xwindow;
644 NSAssert (ogl_ctx, @"no CGContext");
647 // Clear frame buffer ASAP, else there are bits left over from other apps.
648 glClearColor (0, 0, 0, 1);
649 glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
651 // glXSwapBuffers (mi->dpy, mi->window);
654 // Enable multi-threading, if possible. This runs most OpenGL commands
655 // and GPU management on a second CPU.
657 # ifndef kCGLCEMPEngine
658 # define kCGLCEMPEngine 313 // Added in MacOS 10.4.8 + XCode 2.4.
660 CGLContextObj cctx = CGLGetCurrentContext();
661 CGLError err = CGLEnable (cctx, kCGLCEMPEngine);
662 if (err != kCGLNoError) {
663 NSLog (@"enabling multi-threaded OpenGL failed: %d", err);
667 new_backbuffer_size = NSSizeToCGSize ([self bounds].size);
671 CAEAGLLayer *eagl_layer = (CAEAGLLayer *) self.layer;
672 eagl_layer.opaque = TRUE;
673 eagl_layer.drawableProperties = [self getGLProperties];
675 // Without this, the GL frame buffer is half the screen resolution!
676 eagl_layer.contentsScale = [UIScreen mainScreen].scale;
678 ogl_ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
680 ogl_ctx_pixmap = [[EAGLContext alloc]
681 initWithAPI:kEAGLRenderingAPIOpenGLES1
682 sharegroup:ogl_ctx.sharegroup];
685 eagl_layer.contentsGravity = [self getCAGravity];
689 xwindow->window.ogl_ctx_pixmap = ogl_ctx_pixmap;
692 [EAGLContext setCurrentContext: ogl_ctx];
696 double s = [self hackedContentScaleFactor];
697 new_backbuffer_size = self.bounds.size;
698 new_backbuffer_size.width *= s;
699 new_backbuffer_size.height *= s;
701 # endif // USE_IPHONE
704 xwindow->ogl_ctx = ogl_ctx;
706 CFRetain (xwindow->ogl_ctx);
707 # endif // USE_IPHONE
710 check_gl_error ("startAnimation");
712 // NSLog (@"%s / %s / %s\n", glGetString (GL_VENDOR),
713 // glGetString (GL_RENDERER), glGetString (GL_VERSION));
715 [self enableBackbuffer:new_backbuffer_size];
717 #endif // BACKBUFFER_OPENGL
720 [self createBackbuffer:new_backbuffer_size];
723 - (void)stopAnimation
725 NSAssert([self isAnimating], @"not animating");
729 [self lockFocus]; // in case something tries to draw from here
730 [self prepareContext];
732 /* I considered just not even calling the free callback at all...
733 But webcollage-cocoa needs it, to kill the inferior webcollage
734 processes (since the screen saver framework never generates a
735 SIGPIPE for them...) Instead, I turned off the free call in
736 xlockmore.c, which is where all of the bogus calls are anyway.
738 xsft->free_cb (xdpy, xwindow, xdata);
741 jwxyz_free_display (xdpy);
743 # if defined JWXYZ_GL && !defined USE_IPHONE
744 CFRelease (xwindow->ogl_ctx);
746 CFRelease (xwindow->window.view);
750 // setup_p = NO; // #### wait, do we need this?
757 [crash_timer invalidate];
759 NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
760 [prefs removeObjectForKey:@"wasRunning"];
762 # endif // USE_IPHONE
764 [super stopAnimation];
766 // When an animation is no longer running (e.g., looking at the list)
767 // then it's ok to power off the screen when docked.
770 [UIApplication sharedApplication].idleTimerDisabled = NO;
771 [[UIApplication sharedApplication]
772 setStatusBarHidden:NO withAnimation:UIStatusBarAnimationNone];
775 // Without this, the GL frame stays on screen when switching tabs
776 // in System Preferences.
777 // (Or perhaps it used to. It doesn't seem to matter on 10.9.)
780 [NSOpenGLContext clearCurrentContext];
781 # endif // !USE_IPHONE
783 clear_gl_error(); // This hack is defunct, don't let this linger.
786 CGContextRelease (backbuffer);
790 munmap (backbuffer_data, backbuffer_len);
791 backbuffer_data = NULL;
797 - (NSOpenGLContext *) oglContext
803 // #### maybe this could/should just be on 'lockFocus' instead?
804 - (void) prepareContext
808 [EAGLContext setCurrentContext:ogl_ctx];
810 [ogl_ctx makeCurrentContext];
811 // check_gl_error ("makeCurrentContext");
812 #endif // !USE_IPHONE
815 xwindow->window.current_drawable = xwindow;
822 screenhack_do_fps (Display *dpy, Window w, fps_state *fpst, void *closure)
824 fps_compute (fpst, 0, -1);
831 /* On iPhones with Retina displays, we can draw the savers in "real"
832 pixels, and that works great. The 320x480 "point" screen is really
833 a 640x960 *pixel* screen. However, Retina iPads have 768x1024
834 point screens which are 1536x2048 pixels, and apparently that's
835 enough pixels that copying those bits to the screen is slow. Like,
836 drops us from 15fps to 7fps. So, on Retina iPads, we don't draw in
837 real pixels. This will probably make the savers look better
838 anyway, since that's a higher resolution than most desktop monitors
839 have even today. (This is only true for X11 programs, not GL
840 programs. Those are fine at full rez.)
842 This method is overridden in XScreenSaverGLView, since this kludge
843 isn't necessary for GL programs, being resolution independent by
846 - (CGFloat) hackedContentScaleFactor
848 NSSize bsize = [self bounds].size;
851 max_bsize = bsize.width > bsize.height ? bsize.width : bsize.height;
853 // Ratio of screen size in pixels to view size in points.
854 CGFloat s = self.contentScaleFactor;
858 // 1. Don't exceed -- let's say 1280 pixels in either direction.
859 // (Otherwise the frame rate gets bad.)
860 // Actually let's make that 1440 since iPhone 6 is natively 1334.
861 CGFloat mag0 = ceil(max_bsize * s / 1440);
863 // 2. Don't let the pixel size get too small.
864 // (Otherwise pixels in IFS and similar are too fine.)
865 // So don't let the result be > 2 pixels per point.
866 CGFloat mag1 = ceil(s / 2);
868 // As of iPhone 6, mag0 is always >= mag1. This may not be true in the future.
869 // (desired scale factor) = s / (desired magnification factor)
870 return s / (mag0 > mag1 ? mag0 : mag1);
875 current_device_rotation (void)
877 UIDeviceOrientation o = [[UIDevice currentDevice] orientation];
879 /* Sometimes UIDevice doesn't know the proper orientation, or the device is
880 face up/face down, so in those cases fall back to the status bar
881 orientation. The SaverViewController tries to set the status bar to the
882 proper orientation before it creates the XScreenSaverView; see
883 _storedOrientation in SaverViewController.
885 if (o == UIDeviceOrientationUnknown ||
886 o == UIDeviceOrientationFaceUp ||
887 o == UIDeviceOrientationFaceDown) {
888 /* Mind the differences between UIInterfaceOrientation and
890 1. UIInterfaceOrientation does not include FaceUp and FaceDown.
891 2. LandscapeLeft and LandscapeRight are swapped between the two. But
892 converting between device and interface orientation doesn't need to
893 take this into account, because (from the UIInterfaceOrientation
894 description): "rotating the device requires rotating the content in
895 the opposite direction."
897 /* statusBarOrientation deprecated in iOS 9 */
898 o = [UIApplication sharedApplication].statusBarOrientation;
902 case UIDeviceOrientationLandscapeLeft: return -90; break;
903 case UIDeviceOrientationLandscapeRight: return 90; break;
904 case UIDeviceOrientationPortraitUpsideDown: return 180; break;
905 default: return 0; break;
910 - (void)alertView:(UIAlertView *)av clickedButtonAtIndex:(NSInteger)i
912 if (i == 0) exit (-1); // Cancel
913 [self stopAndClose:NO]; // Keep going
916 - (void) handleException: (NSException *)e
918 NSLog (@"Caught exception: %@", e);
919 [[[UIAlertView alloc] initWithTitle:
920 [NSString stringWithFormat: @"%s crashed!",
923 [NSString stringWithFormat:
924 @"The error message was:"
926 "If it keeps crashing, try "
927 "resetting its options.",
930 cancelButtonTitle: @"Exit"
931 otherButtonTitles: @"Keep going", nil]
933 [self stopAnimation];
945 // iOS always uses OpenGL ES 1.1.
951 gl_check_ver (const struct gl_version *caps,
955 return caps->major > gl_major ||
956 (caps->major == gl_major && caps->minor >= gl_minor);
961 /* Called during startAnimation before the first call to createBackbuffer. */
962 - (void) enableBackbuffer:(CGSize)new_backbuffer_size
965 struct gl_version version;
968 const char *version_str = (const char *)glGetString (GL_VERSION);
970 /* iPhone is always OpenGL ES 1.1. */
971 if (sscanf ((const char *)version_str, "%u.%u",
972 &version.major, &version.minor) < 2)
980 // The OpenGL extensions in use in here are pretty are pretty much ubiquitous
981 // on OS X, but it's still good form to check.
982 const GLubyte *extensions = glGetString (GL_EXTENSIONS);
984 glGenTextures (1, &backbuffer_texture);
986 // On really old systems, it would make sense to split the texture
989 gl_texture_target = (gluCheckExtension ((const GLubyte *)
990 "GL_ARB_texture_rectangle",
992 ? GL_TEXTURE_RECTANGLE_EXT : GL_TEXTURE_2D);
994 // OES_texture_npot also provides this, but iOS never provides it.
995 gl_limited_npot_p = jwzgles_gluCheckExtension
996 ((const GLubyte *) "GL_APPLE_texture_2D_limited_npot", extensions);
997 gl_texture_target = GL_TEXTURE_2D;
1000 glBindTexture (gl_texture_target, &backbuffer_texture);
1001 glTexParameteri (gl_texture_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
1002 // GL_LINEAR might make sense on Retina iPads.
1003 glTexParameteri (gl_texture_target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
1004 glTexParameteri (gl_texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
1005 glTexParameteri (gl_texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
1008 // There isn't much sense in supporting one of these if the other
1010 gl_apple_client_storage_p =
1011 gluCheckExtension ((const GLubyte *)"GL_APPLE_client_storage",
1013 gluCheckExtension ((const GLubyte *)"GL_APPLE_texture_range", extensions);
1015 if (gl_apple_client_storage_p) {
1016 glTexParameteri (gl_texture_target, GL_TEXTURE_STORAGE_HINT_APPLE,
1017 GL_STORAGE_SHARED_APPLE);
1018 glPixelStorei (GL_UNPACK_CLIENT_STORAGE_APPLE, GL_TRUE);
1022 // If a video adapter suports BGRA textures, then that's probably as fast as
1023 // you're gonna get for getting a texture onto the screen.
1026 jwzgles_gluCheckExtension
1027 ((const GLubyte *)"GL_APPLE_texture_format_BGRA8888", extensions) ?
1031 gl_pixel_type = GL_UNSIGNED_BYTE;
1032 // See also OES_read_format.
1034 if (gl_check_ver (&version, 1, 2) ||
1035 (gluCheckExtension ((const GLubyte *)"GL_EXT_bgra", extensions) &&
1036 gluCheckExtension ((const GLubyte *)"GL_APPLE_packed_pixels",
1038 gl_pixel_format = GL_BGRA;
1039 // Both Intel and PowerPC-era docs say to use GL_UNSIGNED_INT_8_8_8_8_REV.
1040 gl_pixel_type = GL_UNSIGNED_INT_8_8_8_8_REV;
1042 gl_pixel_format = GL_RGBA;
1043 gl_pixel_type = GL_UNSIGNED_BYTE;
1045 // GL_ABGR_EXT/GL_UNSIGNED_BYTE is another possibilty that may have made more
1046 // sense on PowerPC.
1049 glEnable (gl_texture_target);
1050 glEnableClientState (GL_VERTEX_ARRAY);
1051 glEnableClientState (GL_TEXTURE_COORD_ARRAY);
1053 check_gl_error ("enableBackbuffer");
1063 size_t mask = (size_t)-1;
1064 unsigned bits = sizeof(x) * CHAR_BIT;
1065 unsigned log2 = bits;
1083 - (BOOL) suppressRotationAnimation
1085 return [self ignoreRotation]; // Don't animate if we aren't rotating
1088 - (BOOL) rotateTouches
1090 return FALSE; // Adjust event coordinates only if rotating
1095 - (void) setViewport
1097 # ifdef BACKBUFFER_OPENGL
1098 NSAssert ([NSOpenGLContext currentContext] ==
1099 ogl_ctx, @"invalid GL context");
1101 NSSize new_size = self.bounds.size;
1104 GLfloat s = self.contentScaleFactor;
1105 GLfloat hs = self.hackedContentScaleFactor;
1106 # else // !USE_IPHONE
1107 const GLfloat s = 1;
1108 const GLfloat hs = s;
1111 // On OS X this almost isn't necessary, except for the ugly aliasing
1113 glViewport (0, 0, new_size.width * s, new_size.height * s);
1115 glMatrixMode (GL_PROJECTION);
1122 (-new_size.width * hs, new_size.width * hs,
1123 -new_size.height * hs, new_size.height * hs,
1127 if ([self ignoreRotation]) {
1128 int o = (int) -current_device_rotation();
1129 glRotatef (o, 0, 0, 1);
1131 # endif // USE_IPHONE
1132 # endif // BACKBUFFER_OPENGL
1136 /* Create a bitmap context into which we render everything.
1137 If the desired size has changed, re-created it.
1138 new_size is in rotated pixels, not points: the same size
1139 and shape as the X11 window as seen by the hacks.
1141 - (void) createBackbuffer:(CGSize)new_size
1143 // Colorspaces and CGContexts only happen with non-GL hacks.
1145 CGColorSpaceRelease (colorspace);
1147 NSWindow *window = [self window];
1149 if (window && xdpy) {
1152 # ifdef BACKBUFFER_OPENGL
1153 // Was apparently faster until 10.9.
1154 colorspace = CGColorSpaceCreateDeviceRGB ();
1155 # endif // BACKBUFFER_OPENGL
1159 colorspace = CGColorSpaceCreateDeviceRGB();
1162 CGSize osize = CGSizeZero;
1164 osize.width = CGBitmapContextGetWidth(backbuffer);
1165 osize.height = CGBitmapContextGetHeight(backbuffer);
1169 (int)osize.width == (int)new_size.width &&
1170 (int)osize.height == (int)new_size.height)
1173 CGContextRef ob = backbuffer;
1174 void *odata = backbuffer_data;
1175 size_t olen = backbuffer_len;
1177 # if !defined __OPTIMIZE__ || TARGET_IPHONE_SIMULATOR
1178 NSLog(@"backbuffer %.0fx%.0f",
1179 new_size.width, new_size.height);
1182 /* OS X uses APPLE_client_storage and APPLE_texture_range, as described in
1183 <https://developer.apple.com/library/mac/documentation/GraphicsImaging/Conceptual/OpenGL-MacProgGuide/opengl_texturedata/opengl_texturedata.html>.
1185 iOS uses bog-standard glTexImage2D (for now).
1187 glMapBuffer is the standard way to get data from system RAM to video
1188 memory asynchronously and without a memcpy, but support for
1189 APPLE_client_storage is ubiquitous on OS X (not so for glMapBuffer),
1190 and on iOS GL_PIXEL_UNPACK_BUFFER is only available on OpenGL ES 3
1191 (iPhone 5S or newer). Plus, glMapBuffer doesn't work well with
1192 CGBitmapContext: glMapBuffer can return a different pointer on each
1193 call, but a CGBitmapContext doesn't allow its data pointer to be
1194 changed -- and recreating the context for a new pointer can be
1195 expensive (glyph caches get dumped, for instance).
1197 glMapBufferRange has MAP_FLUSH_EXPLICIT_BIT and MAP_UNSYNCHRONIZED_BIT,
1198 and these seem to allow mapping the buffer and leaving it where it is
1199 in client address space while OpenGL works with the buffer, but it
1200 requires OpenGL 3 Core profile on OS X (and ES 3 on iOS for
1201 GL_PIXEL_UNPACK_BUFFER), so point goes to APPLE_client_storage.
1203 AMD_pinned_buffer provides the same advantage as glMapBufferRange, but
1204 Apple never implemented that one for OS X.
1207 backbuffer_data = NULL;
1208 gl_texture_w = (int)new_size.width;
1209 gl_texture_h = (int)new_size.height;
1211 NSAssert (gl_texture_target == GL_TEXTURE_2D
1213 || gl_texture_target == GL_TEXTURE_RECTANGLE_EXT
1215 , @"unexpected GL texture target");
1218 if (gl_texture_target != GL_TEXTURE_RECTANGLE_EXT)
1220 if (!gl_limited_npot_p)
1223 gl_texture_w = to_pow2 (gl_texture_w);
1224 gl_texture_h = to_pow2 (gl_texture_h);
1227 size_t bytes_per_row = gl_texture_w * 4;
1229 # if defined(BACKBUFFER_OPENGL) && !defined(USE_IPHONE)
1230 // APPLE_client_storage requires texture width to be aligned to 32 bytes, or
1231 // it will fall back to a memcpy.
1232 // https://developer.apple.com/library/mac/documentation/GraphicsImaging/Conceptual/OpenGL-MacProgGuide/opengl_texturedata/opengl_texturedata.html#//apple_ref/doc/uid/TP40001987-CH407-SW24
1233 bytes_per_row = (bytes_per_row + 31) & ~31;
1234 # endif // BACKBUFFER_OPENGL && !USE_IPHONE
1236 backbuffer_len = bytes_per_row * gl_texture_h;
1237 if (backbuffer_len) // mmap requires this to be non-zero.
1238 backbuffer_data = mmap (NULL, backbuffer_len,
1239 PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED,
1242 BOOL alpha_first_p, order_little_p;
1244 if (gl_pixel_format == GL_BGRA) {
1245 alpha_first_p = YES;
1246 order_little_p = YES;
1248 } else if (gl_pixel_format == GL_ABGR_EXT) {
1250 order_little_p = YES; */
1252 NSAssert (gl_pixel_format == GL_RGBA, @"unknown GL pixel format");
1254 order_little_p = NO;
1258 NSAssert (gl_pixel_type == GL_UNSIGNED_BYTE, @"unknown GL pixel type");
1260 NSAssert (gl_pixel_type == GL_UNSIGNED_INT_8_8_8_8 ||
1261 gl_pixel_type == GL_UNSIGNED_INT_8_8_8_8_REV ||
1262 gl_pixel_type == GL_UNSIGNED_BYTE,
1263 @"unknown GL pixel type");
1265 #if defined __LITTLE_ENDIAN__
1266 const GLenum backwards_pixel_type = GL_UNSIGNED_INT_8_8_8_8;
1267 #elif defined __BIG_ENDIAN__
1268 const GLenum backwards_pixel_type = GL_UNSIGNED_INT_8_8_8_8_REV;
1270 # error Unknown byte order.
1273 if (gl_pixel_type == backwards_pixel_type)
1274 order_little_p ^= YES;
1277 CGBitmapInfo bitmap_info =
1278 (alpha_first_p ? kCGImageAlphaNoneSkipFirst : kCGImageAlphaNoneSkipLast) |
1279 (order_little_p ? kCGBitmapByteOrder32Little : kCGBitmapByteOrder32Big);
1281 backbuffer = CGBitmapContextCreate (backbuffer_data,
1282 (int)new_size.width,
1283 (int)new_size.height,
1288 NSAssert (backbuffer, @"unable to allocate back buffer");
1292 r.origin.x = r.origin.y = 0;
1294 CGContextSetGrayFillColor (backbuffer, 0, 1);
1295 CGContextFillRect (backbuffer, r);
1297 # if defined(BACKBUFFER_OPENGL) && !defined(USE_IPHONE)
1298 if (gl_apple_client_storage_p)
1299 glTextureRangeAPPLE (gl_texture_target, backbuffer_len, backbuffer_data);
1300 # endif // BACKBUFFER_OPENGL && !USE_IPHONE
1303 // Restore old bits, as much as possible, to the X11 upper left origin.
1305 CGRect rect; // pixels, not points
1307 rect.origin.y = (new_size.height - osize.height);
1310 CGImageRef img = CGBitmapContextCreateImage (ob);
1311 CGContextDrawImage (backbuffer, rect, img);
1312 CGImageRelease (img);
1313 CGContextRelease (ob);
1316 // munmap should round len up to the nearest page.
1317 munmap (odata, olen);
1320 check_gl_error ("createBackbuffer");
1324 - (void) drawBackbuffer
1326 # ifdef BACKBUFFER_OPENGL
1328 NSAssert ([ogl_ctx isKindOfClass:[NSOpenGLContext class]],
1329 @"ogl_ctx is not an NSOpenGLContext");
1331 NSAssert (! (CGBitmapContextGetBytesPerRow (backbuffer) % 4),
1332 @"improperly-aligned backbuffer");
1334 // This gets width and height from the backbuffer in case
1335 // APPLE_client_storage is in use. See the note in createBackbuffer.
1336 // This still has to happen every frame even when APPLE_client_storage has
1337 // the video adapter pulling texture data straight from
1338 // XScreenSaverView-owned memory.
1339 glTexImage2D (gl_texture_target, 0, GL_RGBA,
1340 (GLsizei)(CGBitmapContextGetBytesPerRow (backbuffer) / 4),
1341 gl_texture_h, 0, gl_pixel_format, gl_pixel_type,
1344 GLfloat w = xwindow->frame.width, h = xwindow->frame.height;
1346 GLfloat vertices[4][2] = {{-w, h}, {w, h}, {w, -h}, {-w, -h}};
1348 GLfloat tex_coords[4][2];
1351 if (gl_texture_target != GL_TEXTURE_RECTANGLE_EXT)
1352 # endif // USE_IPHONE
1358 tex_coords[0][0] = 0;
1359 tex_coords[0][1] = 0;
1360 tex_coords[1][0] = w;
1361 tex_coords[1][1] = 0;
1362 tex_coords[2][0] = w;
1363 tex_coords[2][1] = h;
1364 tex_coords[3][0] = 0;
1365 tex_coords[3][1] = h;
1367 glVertexPointer (2, GL_FLOAT, 0, vertices);
1368 glTexCoordPointer (2, GL_FLOAT, 0, tex_coords);
1369 glDrawArrays (GL_TRIANGLE_FAN, 0, 4);
1371 # if !defined __OPTIMIZE__ || TARGET_IPHONE_SIMULATOR
1372 check_gl_error ("drawBackbuffer");
1374 # endif // BACKBUFFER_OPENGL
1377 #endif // JWXYZ_QUARTZ
1381 - (void)enableBackbuffer:(CGSize)new_backbuffer_size;
1383 jwxyz_set_matrices (new_backbuffer_size.width, new_backbuffer_size.height);
1384 check_gl_error ("enableBackbuffer");
1387 - (void)createBackbuffer:(CGSize)new_size
1389 NSAssert ([NSOpenGLContext currentContext] ==
1390 ogl_ctx, @"invalid GL context");
1391 NSAssert (xwindow->window.current_drawable == xwindow,
1392 @"current_drawable not set properly");
1395 /* On iOS, Retina means glViewport gets called with the screen size instead
1396 of the backbuffer/xwindow size. This happens in startAnimation.
1398 The GL screenhacks call glViewport themselves.
1400 glViewport (0, 0, new_size.width, new_size.height);
1403 // TODO: Preserve contents on resize.
1404 glClear (GL_COLOR_BUFFER_BIT);
1405 check_gl_error ("createBackbuffer");
1411 - (void)flushBackbuffer
1414 // Make sure the right context is active: there's two under JWXYZ_GL.
1415 jwxyz_bind_drawable (xwindow, xwindow);
1420 # ifdef JWXYZ_QUARTZ
1421 // The OpenGL pipeline is not automatically synchronized with the contents
1422 // of the backbuffer, so without glFinish, OpenGL can start rendering from
1423 // the backbuffer texture at the same time that JWXYZ is clearing and
1424 // drawing the next frame in the backing store for the backbuffer texture.
1425 // This is only a concern under JWXYZ_QUARTZ because of
1426 // APPLE_client_storage; JWXYZ_GL doesn't use that.
1428 # endif // JWXYZ_QUARTZ
1430 // If JWXYZ_GL was single-buffered, there would need to be a glFinish (or
1431 // maybe just glFlush?) here, because single-buffered contexts don't always
1432 // update what's on the screen after drawing finishes. (i.e., in safe mode)
1434 # ifdef JWXYZ_QUARTZ
1435 // JWXYZ_GL is always double-buffered.
1436 if (double_buffered_p)
1437 # endif // JWXYZ_QUARTZ
1438 [ogl_ctx flushBuffer]; // despite name, this actually swaps
1439 # else // USE_IPHONE
1441 // jwxyz_bind_drawable() only binds the framebuffer, not the renderbuffer.
1443 GLint gl_renderbuffer = xwindow->gl_renderbuffer;
1446 glBindRenderbufferOES (GL_RENDERBUFFER_OES, gl_renderbuffer);
1447 [ogl_ctx presentRenderbuffer:GL_RENDERBUFFER_OES];
1448 # endif // USE_IPHONE
1450 # if !defined __OPTIMIZE__ || TARGET_IPHONE_SIMULATOR
1451 // glGetError waits for the OpenGL command pipe to flush, so skip it in
1453 // OpenGL Programming Guide for Mac -> OpenGL Application Design
1454 // Strategies -> Allow OpenGL to Manage Your Resources
1455 // https://developer.apple.com/library/mac/documentation/GraphicsImaging/Conceptual/OpenGL-MacProgGuide/opengl_designstrategies/opengl_designstrategies.html#//apple_ref/doc/uid/TP40001987-CH2-SW7
1456 check_gl_error ("flushBackbuffer");
1461 /* Inform X11 that the size of our window has changed.
1465 if (!xdpy) return; // early
1467 NSSize new_size; // pixels, not points
1469 new_size = self.bounds.size;
1473 // If this hack ignores rotation, then that means that it pretends to
1474 // always be in portrait mode. If the View has been resized to a
1475 // landscape shape, swap width and height to keep the backbuffer
1478 if ([self ignoreRotation] && new_size.width > new_size.height) {
1479 CGFloat swap = new_size.width;
1480 new_size.width = new_size.height;
1481 new_size.height = swap;
1484 double s = self.hackedContentScaleFactor;
1485 new_size.width *= s;
1486 new_size.height *= s;
1487 # endif // USE_IPHONE
1491 // On first resize, xwindow->frame is 0x0.
1492 if (xwindow->frame.width == new_size.width &&
1493 xwindow->frame.height == new_size.height)
1496 [self prepareContext];
1498 # if defined(BACKBUFFER_OPENGL) && !defined(USE_IPHONE)
1500 # endif // BACKBUFFER_OPENGL && !USE_IPHONE
1502 NSAssert (xwindow && xwindow->type == WINDOW, @"not a window");
1503 xwindow->frame.x = 0;
1504 xwindow->frame.y = 0;
1505 xwindow->frame.width = new_size.width;
1506 xwindow->frame.height = new_size.height;
1508 [self createBackbuffer:CGSizeMake(xwindow->frame.width,
1509 xwindow->frame.height)];
1511 # if defined JWXYZ_QUARTZ
1512 xwindow->cgc = backbuffer;
1513 NSAssert (xwindow->cgc, @"no CGContext");
1514 # elif defined JWXYZ_GL && !defined USE_IPHONE
1516 [ogl_ctx setView:xwindow->window.view]; // (Is this necessary?)
1517 # endif // JWXYZ_GL && USE_IPHONE
1519 jwxyz_window_resized (xdpy);
1521 # if !defined __OPTIMIZE__ || TARGET_IPHONE_SIMULATOR
1522 NSLog(@"reshape %.0fx%.0f", new_size.width, new_size.height);
1525 // Next time render_x11 is called, run the saver's reshape_cb.
1532 /* Called by SaverRunner when the device has changed orientation.
1533 That means we need to generate a resize event, even if the size
1534 has not changed (e.g., from LandscapeLeft to LandscapeRight).
1536 - (void) orientationChanged
1540 next_frame_time = 0; // Get a new frame on screen quickly
1543 /* A hook run after the 'reshape_' method has been called. Used by
1544 XScreenSaverGLView to adjust the in-scene GL viewport.
1546 - (void) postReshape
1549 #endif // USE_IPHONE
1552 // Only render_x11 should call this. XScreenSaverGLView specializes it.
1553 - (void) reshape_x11
1555 xsft->reshape_cb (xdpy, xwindow, xdata,
1556 xwindow->frame.width, xwindow->frame.height);
1565 // jwxyz_make_display needs this.
1566 [self prepareContext]; // resize_x11 also calls this.
1571 # ifdef JWXYZ_QUARTZ
1572 xwindow->cgc = backbuffer;
1573 # endif // JWXYZ_QUARTZ
1574 xdpy = jwxyz_make_display (xwindow);
1576 # if defined USE_IPHONE
1577 /* Some X11 hacks (fluidballs) want to ignore all rotation events. */
1580 TRUE; // Rotation doesn't work yet. TODO: Make rotation work.
1582 get_boolean_resource (xdpy, "ignoreRotation", "IgnoreRotation");
1583 # endif // !JWXYZ_GL
1584 # endif // USE_IPHONE
1592 xsft->setup_cb (xsft, xsft->setup_arg);
1596 NSAssert(!xdata, @"xdata already initialized");
1599 # undef ya_rand_init
1602 XSetWindowBackground (xdpy, xwindow,
1603 get_pixel_resource (xdpy, 0,
1604 "background", "Background"));
1605 XClearWindow (xdpy, xwindow);
1608 [[self window] setAcceptsMouseMovedEvents:YES];
1611 /* In MacOS 10.5, this enables "QuartzGL", meaning that the Quartz
1612 drawing primitives will run on the GPU instead of the CPU.
1613 It seems like it might make things worse rather than better,
1614 though... Plus it makes us binary-incompatible with 10.4.
1616 # if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
1617 [[self window] setPreferredBackingLocation:
1618 NSWindowBackingLocationVideoMemory];
1622 /* Kludge: even though the init_cb functions are declared to take 2 args,
1623 actually call them with 3, for the benefit of xlockmore_init() and
1626 void *(*init_cb) (Display *, Window, void *) =
1627 (void *(*) (Display *, Window, void *)) xsft->init_cb;
1629 xdata = init_cb (xdpy, xwindow, xsft->setup_arg);
1630 // NSAssert(xdata, @"no xdata from init");
1631 if (! xdata) abort();
1633 if (get_boolean_resource (xdpy, "doFPS", "DoFPS")) {
1634 fpst = fps_init (xdpy, xwindow);
1635 if (! xsft->fps_cb) xsft->fps_cb = screenhack_do_fps;
1642 if (current_device_rotation() != 0) // launched while rotated
1646 [self checkForUpdates];
1650 /* I don't understand why we have to do this *every frame*, but we do,
1651 or else the cursor comes back on.
1654 if (![self isPreview])
1655 [NSCursor setHiddenUntilMouseMoves:YES];
1661 /* This is just a guess, but the -fps code wants to know how long
1662 we were sleeping between frames.
1664 long usecs = 1000000 * [self animationTimeInterval];
1665 usecs -= 200; // caller apparently sleeps for slightly less sometimes...
1666 if (usecs < 0) usecs = 0;
1667 fps_slept (fpst, usecs);
1671 /* Run any XtAppAddInput and XtAppAddTimeOut callbacks now.
1672 Do this before delaying for next_frame_time to avoid throttling
1673 timers to the hack's frame rate.
1675 XtAppProcessEvent (XtDisplayToApplicationContext (xdpy),
1676 XtIMTimer | XtIMAlternateInput);
1679 /* It turns out that on some systems (possibly only 10.5 and older?)
1680 [ScreenSaverView setAnimationTimeInterval] does nothing. This means
1681 that we cannot rely on it.
1683 Some of the screen hacks want to delay for long periods, and letting the
1684 framework run the update function at 30 FPS when it really wanted half a
1685 minute between frames would be bad. So instead, we assume that the
1686 framework's animation timer might fire whenever, but we only invoke the
1687 screen hack's "draw frame" method when enough time has expired.
1689 This means two extra calls to gettimeofday() per frame. For fast-cycling
1690 screen savers, that might actually slow them down. Oh well.
1692 A side-effect of this is that it's not possible for a saver to request
1693 an animation interval that is faster than animationTimeInterval.
1695 HOWEVER! On modern systems where setAnimationTimeInterval is *not*
1696 ignored, it's important that it be faster than 30 FPS. 240 FPS is good.
1698 An NSTimer won't fire if the timer is already running the invocation
1699 function from a previous firing. So, if we use a 30 FPS
1700 animationTimeInterval (33333 µs) and a screenhack takes 40000 µs for a
1701 frame, there will be a 26666 µs delay until the next frame, 66666 µs
1702 after the beginning of the current frame. In other words, 25 FPS
1705 Frame rates tend to snap to values of 30/N, where N is a positive
1706 integer, i.e. 30 FPS, 15 FPS, 10, 7.5, 6. And the 'snapped' frame rate
1707 is rounded down from what it would normally be.
1709 So if we set animationTimeInterval to 1/240 instead of 1/30, frame rates
1710 become values of 60/N, 120/N, or 240/N, with coarser or finer frame rate
1711 steps for higher or lower animation time intervals respectively.
1714 gettimeofday (&tv, 0);
1715 double now = tv.tv_sec + (tv.tv_usec / 1000000.0);
1716 if (now < next_frame_time) return;
1718 // [self flushBackbuffer];
1721 // We do this here instead of in setFrame so that all the
1722 // Xlib drawing takes place under the animation timer.
1726 [ogl_ctx setView:self];
1727 # endif // !USE_IPHONE
1736 // NSAssert(xdata, @"no xdata when drawing");
1737 if (! xdata) abort();
1738 unsigned long delay = xsft->draw_cb (xdpy, xwindow, xdata);
1739 if (fpst && xsft->fps_cb)
1740 xsft->fps_cb (xdpy, xwindow, fpst, xdata);
1742 gettimeofday (&tv, 0);
1743 now = tv.tv_sec + (tv.tv_usec / 1000000.0);
1744 next_frame_time = now + (delay / 1000000.0);
1746 # ifdef JWXYZ_QUARTZ
1747 [self drawBackbuffer];
1749 // This can also happen near the beginning of render_x11.
1750 [self flushBackbuffer];
1752 # ifdef USE_IPHONE // Allow savers on the iPhone to run full-tilt.
1753 if (delay < [self animationTimeInterval])
1754 [self setAnimationTimeInterval:(delay / 1000000.0)];
1757 # ifdef DO_GC_HACKERY
1758 /* Current theory is that the 10.6 garbage collector sucks in the
1761 It only does a collection when a threshold of outstanding
1762 collectable allocations has been surpassed. However, CoreGraphics
1763 creates lots of small collectable allocations that contain pointers
1764 to very large non-collectable allocations: a small CG object that's
1765 collectable referencing large malloc'd allocations (non-collectable)
1766 containing bitmap data. So the large allocation doesn't get freed
1767 until GC collects the small allocation, which triggers its finalizer
1768 to run which frees the large allocation. So GC is deciding that it
1769 doesn't really need to run, even though the process has gotten
1770 enormous. GC eventually runs once pageouts have happened, but by
1771 then it's too late, and the machine's resident set has been
1774 So, we force an exhaustive garbage collection in this process
1775 approximately every 5 seconds whether the system thinks it needs
1779 static int tick = 0;
1780 if (++tick > 5*30) {
1782 objc_collect (OBJC_EXHAUSTIVE_COLLECTION);
1785 # endif // DO_GC_HACKERY
1789 @catch (NSException *e) {
1790 [self handleException: e];
1792 # endif // USE_IPHONE
1796 - (void) animateOneFrame
1798 // Render X11 into the backing store bitmap...
1800 # ifdef JWXYZ_QUARTZ
1801 NSAssert (backbuffer, @"no back buffer");
1804 UIGraphicsPushContext (backbuffer);
1806 # endif // JWXYZ_QUARTZ
1810 # if defined USE_IPHONE && defined JWXYZ_QUARTZ
1811 UIGraphicsPopContext();
1816 # ifndef USE_IPHONE // Doesn't exist on iOS
1818 - (void) setFrame:(NSRect) newRect
1820 [super setFrame:newRect];
1822 if (xwindow) // inform Xlib that the window has changed now.
1826 - (void) setFrameSize:(NSSize) newSize
1828 [super setFrameSize:newSize];
1833 # else // USE_IPHONE
1835 - (void) layoutSubviews
1837 [super layoutSubviews];
1846 +(BOOL) performGammaFade
1851 - (BOOL) hasConfigureSheet
1856 + (NSString *) decompressXML: (NSData *)data
1858 if (! data) return 0;
1859 BOOL compressed_p = !!strncmp ((const char *) data.bytes, "<?xml", 5);
1861 // If it's not already XML, decompress it.
1862 NSAssert (compressed_p, @"xml isn't compressed");
1864 NSMutableData *data2 = 0;
1867 memset (&zs, 0, sizeof(zs));
1868 ret = inflateInit2 (&zs, 16 + MAX_WBITS);
1870 UInt32 usize = * (UInt32 *) (data.bytes + data.length - 4);
1871 data2 = [NSMutableData dataWithLength: usize];
1872 zs.next_in = (Bytef *) data.bytes;
1873 zs.avail_in = (uint) data.length;
1874 zs.next_out = (Bytef *) data2.bytes;
1875 zs.avail_out = (uint) data2.length;
1876 ret = inflate (&zs, Z_FINISH);
1879 if (ret == Z_OK || ret == Z_STREAM_END)
1882 NSAssert2 (0, @"gunzip error: %d: %s",
1883 ret, (zs.msg ? zs.msg : "<null>"));
1886 NSString *s = [[NSString alloc]
1887 initWithData:data encoding:NSUTF8StringEncoding];
1894 - (NSWindow *) configureSheet
1896 - (UIViewController *) configureView
1899 NSBundle *bundle = [NSBundle bundleForClass:[self class]];
1900 NSString *file = [NSString stringWithCString:xsft->progclass
1901 encoding:NSISOLatin1StringEncoding];
1902 file = [file lowercaseString];
1903 NSString *path = [bundle pathForResource:file ofType:@"xml"];
1905 NSLog (@"%@.xml does not exist in the application bundle: %@/",
1906 file, [bundle resourcePath]);
1911 UIViewController *sheet;
1912 # else // !USE_IPHONE
1914 # endif // !USE_IPHONE
1916 NSData *xmld = [NSData dataWithContentsOfFile:path];
1917 NSString *xml = [[self class] decompressXML: xmld];
1918 sheet = [[XScreenSaverConfigSheet alloc]
1919 initWithXML:[xml dataUsingEncoding:NSUTF8StringEncoding]
1920 options:xsft->options
1921 controller:[prefsReader userDefaultsController]
1922 globalController:[prefsReader globalDefaultsController]
1923 defaults:[prefsReader defaultOptions]];
1925 // #### am I expected to retain this, or not? wtf.
1926 // I thought not, but if I don't do this, we (sometimes) crash.
1927 // #### Analyze says "potential leak of an object stored into sheet"
1934 - (NSUserDefaultsController *) userDefaultsController
1936 return [prefsReader userDefaultsController];
1940 /* Announce our willingness to accept keyboard input.
1942 - (BOOL)acceptsFirstResponder
1952 # else // USE_IPHONE
1954 // There's no way to play a standard system alert sound!
1955 // We'd have to include our own WAV for that.
1957 // Or we could vibrate:
1958 // #import <AudioToolbox/AudioToolbox.h>
1959 // AudioServicesPlaySystemSound (kSystemSoundID_Vibrate);
1961 // Instead, just flash the screen white, then fade.
1963 UIView *v = [[UIView alloc] initWithFrame: [self frame]];
1964 [v setBackgroundColor: [UIColor whiteColor]];
1965 [[self window] addSubview:v];
1966 [UIView animateWithDuration: 0.1
1967 animations:^{ [v setAlpha: 0.0]; }
1968 completion:^(BOOL finished) { [v removeFromSuperview]; } ];
1970 # endif // USE_IPHONE
1974 /* Send an XEvent to the hack. Returns YES if it was handled.
1976 - (BOOL) sendEvent: (XEvent *) e
1978 if (!initted_p || ![self isAnimating]) // no event handling unless running.
1982 [self prepareContext];
1983 BOOL result = xsft->event_cb (xdpy, xwindow, xdata, e);
1991 /* Convert an NSEvent into an XEvent, and pass it along.
1992 Returns YES if it was handled.
1994 - (BOOL) convertEvent: (NSEvent *) e
1998 memset (&xe, 0, sizeof(xe));
2002 int flags = [e modifierFlags];
2003 if (flags & NSAlphaShiftKeyMask) state |= LockMask;
2004 if (flags & NSShiftKeyMask) state |= ShiftMask;
2005 if (flags & NSControlKeyMask) state |= ControlMask;
2006 if (flags & NSAlternateKeyMask) state |= Mod1Mask;
2007 if (flags & NSCommandKeyMask) state |= Mod2Mask;
2009 NSPoint p = [[[e window] contentView] convertPoint:[e locationInWindow]
2012 double s = [self hackedContentScaleFactor];
2017 int y = s * ([self bounds].size.height - p.y);
2019 xe.xany.type = type;
2025 xe.xbutton.state = state;
2026 if ([e type] == NSScrollWheel)
2027 xe.xbutton.button = ([e deltaY] > 0 ? Button4 :
2028 [e deltaY] < 0 ? Button5 :
2029 [e deltaX] > 0 ? Button6 :
2030 [e deltaX] < 0 ? Button7 :
2033 xe.xbutton.button = [e buttonNumber] + 1;
2038 xe.xmotion.state = state;
2043 NSString *ns = (([e type] == NSFlagsChanged) ? 0 :
2044 [e charactersIgnoringModifiers]);
2047 if (!ns || [ns length] == 0) // dead key
2049 // Cocoa hides the difference between left and right keys.
2050 // Also we only get KeyPress events for these, no KeyRelease
2051 // (unless we hack the mod state manually. Bleh.)
2053 if (flags & NSAlphaShiftKeyMask) k = XK_Caps_Lock;
2054 else if (flags & NSShiftKeyMask) k = XK_Shift_L;
2055 else if (flags & NSControlKeyMask) k = XK_Control_L;
2056 else if (flags & NSAlternateKeyMask) k = XK_Alt_L;
2057 else if (flags & NSCommandKeyMask) k = XK_Meta_L;
2059 else if ([ns length] == 1) // real key
2061 switch ([ns characterAtIndex:0]) {
2062 case NSLeftArrowFunctionKey: k = XK_Left; break;
2063 case NSRightArrowFunctionKey: k = XK_Right; break;
2064 case NSUpArrowFunctionKey: k = XK_Up; break;
2065 case NSDownArrowFunctionKey: k = XK_Down; break;
2066 case NSPageUpFunctionKey: k = XK_Page_Up; break;
2067 case NSPageDownFunctionKey: k = XK_Page_Down; break;
2068 case NSHomeFunctionKey: k = XK_Home; break;
2069 case NSPrevFunctionKey: k = XK_Prior; break;
2070 case NSNextFunctionKey: k = XK_Next; break;
2071 case NSBeginFunctionKey: k = XK_Begin; break;
2072 case NSEndFunctionKey: k = XK_End; break;
2073 case NSF1FunctionKey: k = XK_F1; break;
2074 case NSF2FunctionKey: k = XK_F2; break;
2075 case NSF3FunctionKey: k = XK_F3; break;
2076 case NSF4FunctionKey: k = XK_F4; break;
2077 case NSF5FunctionKey: k = XK_F5; break;
2078 case NSF6FunctionKey: k = XK_F6; break;
2079 case NSF7FunctionKey: k = XK_F7; break;
2080 case NSF8FunctionKey: k = XK_F8; break;
2081 case NSF9FunctionKey: k = XK_F9; break;
2082 case NSF10FunctionKey: k = XK_F10; break;
2083 case NSF11FunctionKey: k = XK_F11; break;
2084 case NSF12FunctionKey: k = XK_F12; break;
2088 [ns cStringUsingEncoding:NSISOLatin1StringEncoding];
2089 k = (s && *s ? *s : 0);
2095 if (! k) return YES; // E.g., "KeyRelease XK_Shift_L"
2097 xe.xkey.keycode = k;
2098 xe.xkey.state = state;
2102 NSAssert1 (0, @"unknown X11 event type: %d", type);
2106 return [self sendEvent: &xe];
2110 - (void) mouseDown: (NSEvent *) e
2112 if (! [self convertEvent:e type:ButtonPress])
2113 [super mouseDown:e];
2116 - (void) mouseUp: (NSEvent *) e
2118 if (! [self convertEvent:e type:ButtonRelease])
2122 - (void) otherMouseDown: (NSEvent *) e
2124 if (! [self convertEvent:e type:ButtonPress])
2125 [super otherMouseDown:e];
2128 - (void) otherMouseUp: (NSEvent *) e
2130 if (! [self convertEvent:e type:ButtonRelease])
2131 [super otherMouseUp:e];
2134 - (void) mouseMoved: (NSEvent *) e
2136 if (! [self convertEvent:e type:MotionNotify])
2137 [super mouseMoved:e];
2140 - (void) mouseDragged: (NSEvent *) e
2142 if (! [self convertEvent:e type:MotionNotify])
2143 [super mouseDragged:e];
2146 - (void) otherMouseDragged: (NSEvent *) e
2148 if (! [self convertEvent:e type:MotionNotify])
2149 [super otherMouseDragged:e];
2152 - (void) scrollWheel: (NSEvent *) e
2154 if (! [self convertEvent:e type:ButtonPress])
2155 [super scrollWheel:e];
2158 - (void) keyDown: (NSEvent *) e
2160 if (! [self convertEvent:e type:KeyPress])
2164 - (void) keyUp: (NSEvent *) e
2166 if (! [self convertEvent:e type:KeyRelease])
2170 - (void) flagsChanged: (NSEvent *) e
2172 if (! [self convertEvent:e type:KeyPress])
2173 [super flagsChanged:e];
2177 - (NSOpenGLPixelFormat *) getGLPixelFormat
2179 NSAssert (prefsReader, @"no prefsReader for getGLPixelFormat");
2181 NSOpenGLPixelFormatAttribute attrs[40];
2183 attrs[i++] = NSOpenGLPFAColorSize; attrs[i++] = 24;
2185 /* OpenGL's core profile removes a lot of the same stuff that was removed in
2186 OpenGL ES (e.g. glBegin, glDrawPixels), so it might be a possibility.
2188 opengl_core_p = True;
2189 if (opengl_core_p) {
2190 attrs[i++] = NSOpenGLPFAOpenGLProfile;
2191 attrs[i++] = NSOpenGLProfileVersion3_2Core;
2195 /* Eventually: multisampled pixmaps. May not be supported everywhere.
2196 if (multi_sample_p) {
2197 attrs[i++] = NSOpenGLPFASampleBuffers; attrs[i++] = 1;
2198 attrs[i++] = NSOpenGLPFASamples; attrs[i++] = 6;
2202 # ifdef JWXYZ_QUARTZ
2203 // Under Quartz, we're just blitting a texture.
2204 if (double_buffered_p)
2205 attrs[i++] = NSOpenGLPFADoubleBuffer;
2209 /* Under OpenGL, all sorts of drawing commands are being issued, and it might
2210 be a performance problem if this activity occurs on the front buffer.
2211 Also, some screenhacks expect OS X/iOS to always double-buffer.
2212 NSOpenGLPFABackingStore prevents flickering with screenhacks that
2213 don't redraw the entire screen every frame.
2215 attrs[i++] = NSOpenGLPFADoubleBuffer;
2216 attrs[i++] = NSOpenGLPFABackingStore;
2219 attrs[i++] = NSOpenGLPFAWindow;
2221 attrs[i++] = NSOpenGLPFAPixelBuffer;
2222 /* ...But not NSOpenGLPFAFullScreen, because that would be for
2223 [NSOpenGLContext setFullScreen].
2227 /* NSOpenGLPFAFullScreen would go here if initWithFrame's isPreview == NO.
2232 NSOpenGLPixelFormat *p = [[NSOpenGLPixelFormat alloc]
2233 initWithAttributes:attrs];
2241 - (void) stopAndClose:(Bool)relaunch_p
2243 if ([self isAnimating])
2244 [self stopAnimation];
2246 /* Need to make the SaverListController be the firstResponder again
2247 so that it can continue to receive its own shake events. I
2248 suppose that this abstraction-breakage means that I'm adding
2249 XScreenSaverView to the UINavigationController wrong...
2251 // UIViewController *v = [[self window] rootViewController];
2252 // if ([v isKindOfClass: [UINavigationController class]]) {
2253 // UINavigationController *n = (UINavigationController *) v;
2254 // [[n topViewController] becomeFirstResponder];
2256 [self resignFirstResponder];
2258 if (relaunch_p) { // Fake a shake on the SaverListController.
2259 [_delegate didShake:self];
2260 } else { // Not launching another, animate our return to the list.
2261 # if !defined __OPTIMIZE__ || TARGET_IPHONE_SIMULATOR
2262 NSLog (@"fading back to saver list");
2264 [_delegate wantsFadeOut:self];
2269 /* We distinguish between taps and drags.
2271 - Drags/pans (down, motion, up) are sent to the saver to handle.
2272 - Single-taps exit the saver.
2273 - Double-taps are sent to the saver as a "Space" keypress.
2274 - Swipes (really, two-finger drags/pans) send Up/Down/Left/RightArrow keys.
2276 This means a saver cannot respond to a single-tap. Only a few try to.
2279 - (void)initGestures
2281 UITapGestureRecognizer *dtap = [[UITapGestureRecognizer alloc]
2283 action:@selector(handleDoubleTap)];
2284 dtap.numberOfTapsRequired = 2;
2285 dtap.numberOfTouchesRequired = 1;
2287 UITapGestureRecognizer *stap = [[UITapGestureRecognizer alloc]
2289 action:@selector(handleTap)];
2290 stap.numberOfTapsRequired = 1;
2291 stap.numberOfTouchesRequired = 1;
2293 UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]
2295 action:@selector(handlePan:)];
2296 pan.maximumNumberOfTouches = 1;
2297 pan.minimumNumberOfTouches = 1;
2299 // I couldn't get Swipe to work, but using a second Pan recognizer works.
2300 UIPanGestureRecognizer *pan2 = [[UIPanGestureRecognizer alloc]
2302 action:@selector(handlePan2:)];
2303 pan2.maximumNumberOfTouches = 2;
2304 pan2.minimumNumberOfTouches = 2;
2306 // Also handle long-touch, and treat that the same as Pan.
2307 // Without this, panning doesn't start until there's motion, so the trick
2308 // of holding down your finger to freeze the scene doesn't work.
2310 UILongPressGestureRecognizer *hold = [[UILongPressGestureRecognizer alloc]
2312 action:@selector(handleLongPress:)];
2313 hold.numberOfTapsRequired = 0;
2314 hold.numberOfTouchesRequired = 1;
2315 hold.minimumPressDuration = 0.25; /* 1/4th second */
2317 [stap requireGestureRecognizerToFail: dtap];
2318 [stap requireGestureRecognizerToFail: hold];
2319 [dtap requireGestureRecognizerToFail: hold];
2320 [pan requireGestureRecognizerToFail: hold];
2322 [self setMultipleTouchEnabled:YES];
2324 [self addGestureRecognizer: dtap];
2325 [self addGestureRecognizer: stap];
2326 [self addGestureRecognizer: pan];
2327 [self addGestureRecognizer: pan2];
2328 [self addGestureRecognizer: hold];
2338 /* Given a mouse (touch) coordinate in unrotated, unscaled view coordinates,
2339 convert it to what X11 and OpenGL expect.
2341 Getting this crap right is tricky, given the confusion of the various
2342 scale factors, so here's a checklist that I think covers all of the X11
2343 and OpenGL cases. For each of these: rotate to all 4 orientations;
2344 ensure the mouse tracks properly to all 4 corners.
2346 Test it in Xcode 6, because Xcode 5.0.2 can't run the iPhone6+ simulator.
2348 Test hacks must cover:
2349 X11 ignoreRotation = true
2350 X11 ignoreRotation = false
2351 OpenGL (rotation is handled manually, so they never ignoreRotation)
2353 Test devices must cover:
2354 contentScaleFactor = 1, hackedContentScaleFactor = 1 (iPad 2)
2355 contentScaleFactor = 2, hackedContentScaleFactor = 1 (iPad Retina Air)
2356 contentScaleFactor = 2, hackedContentScaleFactor = 2 (iPhone 5 5s 6 6+)
2358 iPad 2: 768x1024 / 1 = 768x1024
2359 iPad Air: 1536x2048 / 2 = 768x1024 (iPad Retina is identical)
2360 iPhone 4s: 640x960 / 2 = 320x480
2361 iPhone 5: 640x1136 / 2 = 320x568 (iPhone 5s and iPhone 6 are identical)
2362 iPhone 6+: 640x1136 / 2 = 320x568 (nativeBounds 960x1704 nativeScale 3)
2365 iPad2 iPadAir iPhone4s iPhone5 iPhone6+
2366 Attraction X yes - - - - Y
2367 Fireworkx X no - - - - Y
2368 Carousel GL yes - - - - Y
2369 Voronoi GL no - - - - -
2371 - (void) convertMouse:(CGPoint *)p
2373 CGFloat xx = p->x, yy = p->y;
2375 # if TARGET_IPHONE_SIMULATOR
2377 XWindowAttributes xgwa;
2378 XGetWindowAttributes (xdpy, xwindow, &xgwa);
2379 NSLog (@"TOUCH %4g, %-4g in %4d x %-4d cs=%.0f hcs=%.0f r=%d ig=%d\n",
2381 xgwa.width, xgwa.height,
2382 [self contentScaleFactor],
2383 [self hackedContentScaleFactor],
2384 [self rotateTouches], [self ignoreRotation]);
2386 # endif // TARGET_IPHONE_SIMULATOR
2388 if ([self rotateTouches]) {
2390 // The XScreenSaverGLView case:
2391 // The X11 window is rotated, as is the framebuffer.
2392 // The device coordinates match the framebuffer dimensions,
2393 // but might have axes swapped... and we need to swap them
2396 int w = [self frame].size.width;
2397 int h = [self frame].size.height;
2398 GLfloat xr = (GLfloat) xx / w;
2399 GLfloat yr = (GLfloat) yy / h;
2401 int o = (int) current_device_rotation();
2403 case -90: case 270: swap = xr; xr = 1-yr; yr = swap; break;
2404 case 90: case -270: swap = xr; xr = yr; yr = 1-swap; break;
2405 case 180: case -180: xr = 1-xr; yr = 1-yr; break;
2411 } else if ([self ignoreRotation]) {
2413 // The X11 case, where the hack has opted not to rotate:
2414 // The X11 window is unrotated, but the framebuffer is rotated.
2415 // The device coordinates match the framebuffer, so they need to
2416 // be de-rotated to match the X11 window.
2418 int w = [self frame].size.width;
2419 int h = [self frame].size.height;
2421 int o = (int) current_device_rotation();
2423 case -90: case 270: swap = xx; xx = h-yy; yy = swap; break;
2424 case 90: case -270: swap = xx; xx = yy; yy = w-swap; break;
2425 case 180: case -180: xx = w-xx; yy = h-yy; break;
2430 double s = [self hackedContentScaleFactor];
2434 # if TARGET_IPHONE_SIMULATOR || !defined __OPTIMIZE__
2436 XWindowAttributes xgwa;
2437 XGetWindowAttributes (xdpy, xwindow, &xgwa);
2438 NSLog (@"touch %4g, %-4g in %4d x %-4d cs=%.0f hcs=%.0f r=%d ig=%d\n",
2440 xgwa.width, xgwa.height,
2441 [self contentScaleFactor],
2442 [self hackedContentScaleFactor],
2443 [self rotateTouches], [self ignoreRotation]);
2444 if (p->x < 0 || p->y < 0 || p->x > xgwa.width || p->y > xgwa.height)
2447 # endif // TARGET_IPHONE_SIMULATOR
2451 /* Single click exits saver.
2455 [self stopAndClose:NO];
2459 /* Double click sends Space KeyPress.
2461 - (void) handleDoubleTap
2463 if (!xsft->event_cb || !xwindow) return;
2466 memset (&xe, 0, sizeof(xe));
2467 xe.xkey.keycode = ' ';
2468 xe.xany.type = KeyPress;
2469 BOOL ok1 = [self sendEvent: &xe];
2470 xe.xany.type = KeyRelease;
2471 BOOL ok2 = [self sendEvent: &xe];
2477 /* Drag with one finger down: send MotionNotify.
2479 - (void) handlePan:(UIGestureRecognizer *)sender
2481 if (!xsft->event_cb || !xwindow) return;
2484 memset (&xe, 0, sizeof(xe));
2486 CGPoint p = [sender locationInView:self]; // this is in points, not pixels
2487 [self convertMouse:&p];
2488 NSAssert (xwindow && xwindow->type == WINDOW, @"not a window");
2489 xwindow->window.last_mouse_x = p.x;
2490 xwindow->window.last_mouse_y = p.y;
2492 switch (sender.state) {
2493 case UIGestureRecognizerStateBegan:
2494 xe.xany.type = ButtonPress;
2495 xe.xbutton.button = 1;
2500 case UIGestureRecognizerStateEnded:
2501 xe.xany.type = ButtonRelease;
2502 xe.xbutton.button = 1;
2507 case UIGestureRecognizerStateChanged:
2508 xe.xany.type = MotionNotify;
2517 BOOL ok = [self sendEvent: &xe];
2518 if (!ok && xe.xany.type == ButtonRelease)
2523 /* Hold one finger down: assume we're about to start dragging.
2524 Treat the same as Pan.
2526 - (void) handleLongPress:(UIGestureRecognizer *)sender
2528 [self handlePan:sender];
2533 /* Drag with 2 fingers down: send arrow keys.
2535 - (void) handlePan2:(UIPanGestureRecognizer *)sender
2537 if (!xsft->event_cb || !xwindow) return;
2539 if (sender.state != UIGestureRecognizerStateEnded)
2543 memset (&xe, 0, sizeof(xe));
2545 CGPoint p = [sender locationInView:self]; // this is in points, not pixels
2546 [self convertMouse:&p];
2548 if (fabs(p.x) > fabs(p.y))
2549 xe.xkey.keycode = (p.x > 0 ? XK_Right : XK_Left);
2551 xe.xkey.keycode = (p.y > 0 ? XK_Down : XK_Up);
2553 BOOL ok1 = [self sendEvent: &xe];
2554 xe.xany.type = KeyRelease;
2555 BOOL ok2 = [self sendEvent: &xe];
2561 /* We need this to respond to "shake" gestures
2563 - (BOOL)canBecomeFirstResponder
2568 - (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
2573 - (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event
2577 /* Shake means exit and launch a new saver.
2579 - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
2581 [self stopAndClose:YES];
2585 - (void)setScreenLocked:(BOOL)locked
2587 if (screenLocked == locked) return;
2588 screenLocked = locked;
2590 if ([self isAnimating])
2591 [self stopAnimation];
2593 if (! [self isAnimating])
2594 [self startAnimation];
2598 - (NSDictionary *)getGLProperties
2600 return [NSDictionary dictionaryWithObjectsAndKeys:
2601 kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat,
2603 /* This could be disabled if we knew the screen would be redrawn
2604 entirely for every frame.
2606 [NSNumber numberWithBool:YES], kEAGLDrawablePropertyRetainedBacking,
2611 - (void)addExtraRenderbuffers:(CGSize)size
2613 // No extra renderbuffers are needed for 2D screenhacks.
2617 - (NSString *)getCAGravity
2619 return kCAGravityCenter; // Looks better in e.g. Compass.
2620 // return kCAGravityBottomLeft;
2623 #endif // USE_IPHONE
2626 - (void) checkForUpdates
2629 // We only check once at startup, even if there are multiple screens,
2630 // and even if this saver is running for many days.
2631 // (Uh, except this doesn't work because this static isn't shared,
2632 // even if we make it an exported global. Not sure why. Oh well.)
2633 static BOOL checked_p = NO;
2634 if (checked_p) return;
2637 // If it's off, don't bother running the updater. Otherwise, the
2638 // updater will decide if it's time to hit the network.
2639 if (! get_boolean_resource (xdpy,
2640 SUSUEnableAutomaticChecksKey,
2641 SUSUEnableAutomaticChecksKey))
2644 NSString *updater = @"XScreenSaverUpdater.app";
2646 // There may be multiple copies of the updater: e.g., one in /Applications
2647 // and one in the mounted installer DMG! It's important that we run the
2648 // one from the disk and not the DMG, so search for the right one.
2650 NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
2651 NSBundle *bundle = [NSBundle bundleForClass:[self class]];
2653 @[[[bundle bundlePath] stringByDeletingLastPathComponent],
2654 [@"~/Library/Screen Savers" stringByExpandingTildeInPath],
2655 @"/Library/Screen Savers",
2656 @"/System/Library/Screen Savers",
2658 @"/Applications/Utilities"];
2659 NSString *app_path = nil;
2660 for (NSString *dir in search) {
2661 NSString *p = [dir stringByAppendingPathComponent:updater];
2662 if ([[NSFileManager defaultManager] fileExistsAtPath:p]) {
2669 app_path = [workspace fullPathForApplication:updater];
2671 if (app_path && [app_path hasPrefix:@"/Volumes/XScreenSaver "])
2672 app_path = 0; // The DMG version will not do.
2675 NSLog(@"Unable to find %@", updater);
2680 if (! [workspace launchApplicationAtURL:[NSURL fileURLWithPath:app_path]
2681 options:(NSWorkspaceLaunchWithoutAddingToRecents |
2682 NSWorkspaceLaunchWithoutActivation |
2683 NSWorkspaceLaunchAndHide)
2684 configuration:[NSMutableDictionary dictionary]
2686 NSLog(@"Unable to launch %@: %@", app_path, err);
2689 # endif // !USE_IPHONE
2695 /* Utility functions...
2698 static PrefsReader *
2699 get_prefsReader (Display *dpy)
2701 XScreenSaverView *view = jwxyz_window_view (XRootWindow (dpy, 0));
2702 if (!view) return 0;
2703 return [view prefsReader];
2708 get_string_resource (Display *dpy, char *name, char *class)
2710 return [get_prefsReader(dpy) getStringResource:name];
2714 get_boolean_resource (Display *dpy, char *name, char *class)
2716 return [get_prefsReader(dpy) getBooleanResource:name];
2720 get_integer_resource (Display *dpy, char *name, char *class)
2722 return [get_prefsReader(dpy) getIntegerResource:name];
2726 get_float_resource (Display *dpy, char *name, char *class)
2728 return [get_prefsReader(dpy) getFloatResource:name];