summaryrefslogtreecommitdiffstats
path: root/OSX/XScreenSaverView.m
diff options
context:
space:
mode:
Diffstat (limited to 'OSX/XScreenSaverView.m')
-rw-r--r--OSX/XScreenSaverView.m3059
1 files changed, 0 insertions, 3059 deletions
diff --git a/OSX/XScreenSaverView.m b/OSX/XScreenSaverView.m
deleted file mode 100644
index 371cf17..0000000
--- a/OSX/XScreenSaverView.m
+++ /dev/null
@@ -1,3059 +0,0 @@
-/* xscreensaver, Copyright (c) 2006-2018 Jamie Zawinski <jwz@jwz.org>
- *
- * Permission to use, copy, modify, distribute, and sell this software and its
- * documentation for any purpose is hereby granted without fee, provided that
- * the above copyright notice appear in all copies and that both that
- * copyright notice and this permission notice appear in supporting
- * documentation. No representations are made about the suitability of this
- * software for any purpose. It is provided "as is" without express or
- * implied warranty.
- */
-
-/* This is a subclass of Apple's ScreenSaverView that knows how to run
- xscreensaver programs without X11 via the dark magic of the "jwxyz"
- library. In xscreensaver terminology, this is the replacement for
- the "screenhack.c" module.
- */
-
-#import <QuartzCore/QuartzCore.h>
-#import <sys/mman.h>
-#import <zlib.h>
-#import "XScreenSaverView.h"
-#import "XScreenSaverConfigSheet.h"
-#import "Updater.h"
-#import "screenhackI.h"
-#import "pow2.h"
-#import "jwxyzI.h"
-#import "jwxyz-cocoa.h"
-#import "jwxyz-timers.h"
-
-#ifdef USE_IPHONE
-// XScreenSaverView.m speaks OpenGL ES just fine, but enableBackbuffer does
-// need (jwzgles_)gluCheckExtension.
-# import "jwzglesI.h"
-#else
-# import <OpenGL/glu.h>
-#endif
-
-/* Garbage collection only exists if we are being compiled against the
- 10.6 SDK or newer, not if we are building against the 10.4 SDK.
- */
-#ifndef MAC_OS_X_VERSION_10_6
-# define MAC_OS_X_VERSION_10_6 1060 /* undefined in 10.4 SDK, grr */
-#endif
-#ifndef MAC_OS_X_VERSION_10_12
-# define MAC_OS_X_VERSION_10_12 101200
-#endif
-#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 && \
- MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12)
- /* 10.6 SDK or later, and earlier than 10.12 SDK */
-# import <objc/objc-auto.h>
-# define DO_GC_HACKERY
-#endif
-
-/* Duplicated in xlockmoreI.h and XScreenSaverGLView.m. */
-extern void clear_gl_error (void);
-extern void check_gl_error (const char *type);
-
-extern struct xscreensaver_function_table *xscreensaver_function_table;
-
-/* Global variables used by the screen savers
- */
-const char *progname;
-const char *progclass;
-int mono_p = 0;
-
-
-# ifdef USE_IPHONE
-
-# define NSSizeToCGSize(x) (x)
-
-extern NSDictionary *make_function_table_dict(void); // ios-function-table.m
-
-/* Stub definition of the superclass, for iPhone.
- */
-@implementation ScreenSaverView
-{
- NSTimeInterval anim_interval;
- Bool animating_p;
- NSTimer *anim_timer;
-}
-
-- (id)initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview {
- self = [super initWithFrame:frame];
- if (! self) return 0;
- anim_interval = 1.0/30;
- return self;
-}
-- (NSTimeInterval)animationTimeInterval { return anim_interval; }
-- (void)setAnimationTimeInterval:(NSTimeInterval)i { anim_interval = i; }
-- (BOOL)hasConfigureSheet { return NO; }
-- (NSWindow *)configureSheet { return nil; }
-- (NSView *)configureView { return nil; }
-- (BOOL)isPreview { return NO; }
-- (BOOL)isAnimating { return animating_p; }
-- (void)animateOneFrame { }
-
-- (void)startAnimation {
- if (animating_p) return;
- animating_p = YES;
- anim_timer = [NSTimer scheduledTimerWithTimeInterval: anim_interval
- target:self
- selector:@selector(animateOneFrame)
- userInfo:nil
- repeats:YES];
-}
-
-- (void)stopAnimation {
- if (anim_timer) {
- [anim_timer invalidate];
- anim_timer = 0;
- }
- animating_p = NO;
-}
-@end
-
-# endif // !USE_IPHONE
-
-
-
-@interface XScreenSaverView (Private)
-- (void) stopAndClose;
-- (void) stopAndClose:(Bool)relaunch;
-@end
-
-@implementation XScreenSaverView
-
-// Given a lower-cased saver name, returns the function table for it.
-// If no name, guess the name from the class's bundle name.
-//
-- (struct xscreensaver_function_table *) findFunctionTable:(NSString *)name
-{
- NSBundle *nsb = [NSBundle bundleForClass:[self class]];
- NSAssert1 (nsb, @"no bundle for class %@", [self class]);
-
- NSString *path = [nsb bundlePath];
- CFURLRef url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
- (CFStringRef) path,
- kCFURLPOSIXPathStyle,
- true);
- CFBundleRef cfb = CFBundleCreate (kCFAllocatorDefault, url);
- CFRelease (url);
- NSAssert1 (cfb, @"no CFBundle for \"%@\"", path);
- // #### Analyze says "Potential leak of an object stored into cfb"
-
- if (! name)
- name = [[path lastPathComponent] stringByDeletingPathExtension];
-
- name = [[name lowercaseString]
- stringByReplacingOccurrencesOfString:@" "
- withString:@""];
-
-# ifndef USE_IPHONE
- // CFBundleGetDataPointerForName doesn't work in "Archive" builds.
- // I'm guessing that symbol-stripping is mandatory. Fuck.
- NSString *table_name = [name stringByAppendingString:
- @"_xscreensaver_function_table"];
- void *addr = CFBundleGetDataPointerForName (cfb, (CFStringRef) table_name);
- CFRelease (cfb);
-
- if (! addr)
- NSLog (@"no symbol \"%@\" for \"%@\"", table_name, path);
-
-# else // USE_IPHONE
- // Depends on the auto-generated "ios-function-table.m" being up to date.
- if (! function_tables)
- function_tables = [make_function_table_dict() retain];
- NSValue *v = [function_tables objectForKey: name];
- void *addr = v ? [v pointerValue] : 0;
-# endif // USE_IPHONE
-
- return (struct xscreensaver_function_table *) addr;
-}
-
-
-// Add the "Contents/Resources/" subdirectory of this screen saver's .bundle
-// to $PATH for the benefit of savers that include helper shell scripts.
-//
-- (void) setShellPath
-{
- NSBundle *nsb = [NSBundle bundleForClass:[self class]];
- NSAssert1 (nsb, @"no bundle for class %@", [self class]);
-
- NSString *nsdir = [nsb resourcePath];
- NSAssert1 (nsdir, @"no resourcePath for class %@", [self class]);
- const char *dir = [nsdir cStringUsingEncoding:NSUTF8StringEncoding];
- const char *opath = getenv ("PATH");
- if (!opath) opath = "/bin"; // $PATH is unset when running under Shark!
- char *npath = (char *) malloc (strlen (opath) + strlen (dir) + 2);
- strcpy (npath, dir);
- strcat (npath, ":");
- strcat (npath, opath);
- if (setenv ("PATH", npath, 1)) {
- perror ("setenv");
- NSAssert1 (0, @"setenv \"PATH=%s\" failed", npath);
- }
-
- free (npath);
-}
-
-
-// set an $XSCREENSAVER_CLASSPATH variable so that included shell scripts
-// (e.g., "xscreensaver-text") know how to look up resources.
-//
-- (void) setResourcesEnv:(NSString *) name
-{
- NSBundle *nsb = [NSBundle bundleForClass:[self class]];
- NSAssert1 (nsb, @"no bundle for class %@", [self class]);
-
- const char *s = [name cStringUsingEncoding:NSUTF8StringEncoding];
- if (setenv ("XSCREENSAVER_CLASSPATH", s, 1)) {
- perror ("setenv");
- NSAssert1 (0, @"setenv \"XSCREENSAVER_CLASSPATH=%s\" failed", s);
- }
-}
-
-
-- (void) loadCustomFonts
-{
-# ifndef USE_IPHONE
- NSBundle *nsb = [NSBundle bundleForClass:[self class]];
- NSMutableArray *fonts = [NSMutableArray arrayWithCapacity:20];
- for (NSString *ext in @[@"ttf", @"otf"]) {
- [fonts addObjectsFromArray: [nsb pathsForResourcesOfType:ext
- inDirectory:NULL]];
- }
- for (NSString *font in fonts) {
- CFURLRef url = (CFURLRef) [NSURL fileURLWithPath: font];
- CFErrorRef err = 0;
- if (! CTFontManagerRegisterFontsForURL (url, kCTFontManagerScopeProcess,
- &err)) {
- // Just ignore errors:
- // "The file has already been registered in the specified scope."
- // NSLog (@"loading font: %@ %@", url, err);
- }
- }
-# endif // !USE_IPHONE
-}
-
-
-static void
-add_default_options (const XrmOptionDescRec *opts,
- const char * const *defs,
- XrmOptionDescRec **opts_ret,
- const char ***defs_ret)
-{
- /* These aren't "real" command-line options (there are no actual command-line
- options in the Cocoa version); but this is the somewhat kludgey way that
- the <xscreensaver-text /> and <xscreensaver-image /> tags in the
- ../hacks/config/\*.xml files communicate with the preferences database.
- */
- static const XrmOptionDescRec default_options [] = {
- { "-text-mode", ".textMode", XrmoptionSepArg, 0 },
- { "-text-literal", ".textLiteral", XrmoptionSepArg, 0 },
- { "-text-file", ".textFile", XrmoptionSepArg, 0 },
- { "-text-url", ".textURL", XrmoptionSepArg, 0 },
- { "-text-program", ".textProgram", XrmoptionSepArg, 0 },
- { "-grab-desktop", ".grabDesktopImages", XrmoptionNoArg, "True" },
- { "-no-grab-desktop", ".grabDesktopImages", XrmoptionNoArg, "False"},
- { "-choose-random-images", ".chooseRandomImages",XrmoptionNoArg, "True" },
- { "-no-choose-random-images",".chooseRandomImages",XrmoptionNoArg, "False"},
- { "-image-directory", ".imageDirectory", XrmoptionSepArg, 0 },
- { "-fps", ".doFPS", XrmoptionNoArg, "True" },
- { "-no-fps", ".doFPS", XrmoptionNoArg, "False"},
- { "-foreground", ".foreground", XrmoptionSepArg, 0 },
- { "-fg", ".foreground", XrmoptionSepArg, 0 },
- { "-background", ".background", XrmoptionSepArg, 0 },
- { "-bg", ".background", XrmoptionSepArg, 0 },
-
-# ifndef USE_IPHONE
- // <xscreensaver-updater />
- { "-" SUSUEnableAutomaticChecksKey,
- "." SUSUEnableAutomaticChecksKey, XrmoptionNoArg, "True" },
- { "-no-" SUSUEnableAutomaticChecksKey,
- "." SUSUEnableAutomaticChecksKey, XrmoptionNoArg, "False" },
- { "-" SUAutomaticallyUpdateKey,
- "." SUAutomaticallyUpdateKey, XrmoptionNoArg, "True" },
- { "-no-" SUAutomaticallyUpdateKey,
- "." SUAutomaticallyUpdateKey, XrmoptionNoArg, "False" },
- { "-" SUSendProfileInfoKey,
- "." SUSendProfileInfoKey, XrmoptionNoArg,"True" },
- { "-no-" SUSendProfileInfoKey,
- "." SUSendProfileInfoKey, XrmoptionNoArg,"False"},
- { "-" SUScheduledCheckIntervalKey,
- "." SUScheduledCheckIntervalKey, XrmoptionSepArg, 0 },
-# endif // !USE_IPHONE
-
- { 0, 0, 0, 0 }
- };
- static const char *default_defaults [] = {
-
-# if defined(USE_IPHONE) && !defined(__OPTIMIZE__)
- ".doFPS: True",
-# else
- ".doFPS: False",
-# endif
- ".doubleBuffer: True",
- ".multiSample: False",
-# ifndef USE_IPHONE
- ".textMode: date",
-# else
- ".textMode: url",
-# endif
- // ".textLiteral: ",
- // ".textFile: ",
- ".textURL: https://en.wikipedia.org/w/index.php?title=Special:NewPages&feed=rss",
- // ".textProgram: ",
- ".grabDesktopImages: yes",
-# ifndef USE_IPHONE
- ".chooseRandomImages: no",
-# else
- ".chooseRandomImages: yes",
-# endif
- ".imageDirectory: ~/Pictures",
- ".relaunchDelay: 2",
- ".texFontCacheSize: 30",
-
-# ifndef USE_IPHONE
-# define STR1(S) #S
-# define STR(S) STR1(S)
-# define __objc_yes Yes
-# define __objc_no No
- "." SUSUEnableAutomaticChecksKey ": " STR(SUSUEnableAutomaticChecksDef),
- "." SUAutomaticallyUpdateKey ": " STR(SUAutomaticallyUpdateDef),
- "." SUSendProfileInfoKey ": " STR(SUSendProfileInfoDef),
- "." SUScheduledCheckIntervalKey ": " STR(SUScheduledCheckIntervalDef),
-# undef __objc_yes
-# undef __objc_no
-# undef STR1
-# undef STR
-# endif // USE_IPHONE
- 0
- };
-
- int count = 0, i, j;
- for (i = 0; default_options[i].option; i++)
- count++;
- for (i = 0; opts[i].option; i++)
- count++;
-
- XrmOptionDescRec *opts2 = (XrmOptionDescRec *)
- calloc (count + 1, sizeof (*opts2));
-
- i = 0;
- j = 0;
- while (default_options[j].option) {
- opts2[i] = default_options[j];
- i++, j++;
- }
- j = 0;
- while (opts[j].option) {
- opts2[i] = opts[j];
- i++, j++;
- }
-
- *opts_ret = opts2;
-
-
- /* now the defaults
- */
- count = 0;
- for (i = 0; default_defaults[i]; i++)
- count++;
- for (i = 0; defs[i]; i++)
- count++;
-
- const char **defs2 = (const char **) calloc (count + 1, sizeof (*defs2));
-
- i = 0;
- j = 0;
- while (default_defaults[j]) {
- defs2[i] = default_defaults[j];
- i++, j++;
- }
- j = 0;
- while (defs[j]) {
- defs2[i] = defs[j];
- i++, j++;
- }
-
- *defs_ret = defs2;
-}
-
-
-- (id) initWithFrame:(NSRect)frame
- saverName:(NSString *)saverName
- isPreview:(BOOL)isPreview
-{
- if (! (self = [super initWithFrame:frame isPreview:isPreview]))
- return 0;
-
- xsft = [self findFunctionTable: saverName];
- if (! xsft) {
- [self release];
- return 0;
- }
-
- [self setShellPath];
-
- setup_p = YES;
- if (xsft->setup_cb)
- xsft->setup_cb (xsft, xsft->setup_arg);
-
- /* The plist files for these preferences show up in
- $HOME/Library/Preferences/ByHost/ in a file named like
- "org.jwz.xscreensaver.<SAVERNAME>.<NUMBERS>.plist"
- */
- NSString *name = [NSString stringWithCString:xsft->progclass
- encoding:NSISOLatin1StringEncoding];
- name = [@"org.jwz.xscreensaver." stringByAppendingString:name];
- [self setResourcesEnv:name];
- [self loadCustomFonts];
-
- XrmOptionDescRec *opts = 0;
- const char **defs = 0;
- add_default_options (xsft->options, xsft->defaults, &opts, &defs);
- prefsReader = [[PrefsReader alloc]
- initWithName:name xrmKeys:opts defaults:defs];
- free (defs);
- // free (opts); // bah, we need these! #### leak!
- xsft->options = opts;
-
- progname = progclass = xsft->progclass;
-
- next_frame_time = 0;
-
-# if !defined USE_IPHONE && defined JWXYZ_QUARTZ
- // When the view fills the screen and double buffering is enabled, OS X will
- // use page flipping for a minor CPU/FPS boost. In windowed mode, double
- // buffering reduces the frame rate to 1/2 the screen's refresh rate.
- double_buffered_p = !isPreview;
-# endif
-
-# ifdef USE_IPHONE
- [self initGestures];
-
- // So we can tell when we're docked.
- [UIDevice currentDevice].batteryMonitoringEnabled = YES;
-
- [self setBackgroundColor:[NSColor blackColor]];
-# endif // USE_IPHONE
-
-# ifdef JWXYZ_QUARTZ
- // Colorspaces and CGContexts only happen with non-GL hacks.
- colorspace = CGColorSpaceCreateDeviceRGB ();
-# endif
-
- return self;
-}
-
-
-#ifdef USE_TOUCHBAR
-- (id) initWithFrame:(NSRect)frame
- saverName:(NSString *)saverName
- isPreview:(BOOL)isPreview
- isTouchbar:(BOOL)isTouchbar
-{
- if (! (self = [self initWithFrame:frame saverName:saverName
- isPreview:isPreview]))
- return 0;
- touchbar_p = isTouchbar;
- return self;
-}
-#endif // USE_TOUCHBAR
-
-
-#ifdef USE_IPHONE
-+ (Class) layerClass
-{
- return [CAEAGLLayer class];
-}
-#endif
-
-
-- (id) initWithFrame:(NSRect)frame isPreview:(BOOL)p
-{
- return [self initWithFrame:frame saverName:0 isPreview:p];
-}
-
-
-- (void) dealloc
-{
- if ([self isAnimating])
- [self stopAnimation];
- NSAssert(!xdata, @"xdata not yet freed");
- NSAssert(!xdpy, @"xdpy not yet freed");
-
-# ifdef USE_IPHONE
- [[NSNotificationCenter defaultCenter] removeObserver:self];
-# endif
-
-# ifdef BACKBUFFER_OPENGL
-# ifndef USE_IPHONE
- [pixfmt release];
-# endif // !USE_IPHONE
- [ogl_ctx release];
- // Releasing the OpenGL context should also free any OpenGL objects,
- // including the backbuffer texture and frame/render/depthbuffers.
-# endif // BACKBUFFER_OPENGL
-
-# if defined JWXYZ_GL && defined USE_IPHONE
- [ogl_ctx_pixmap release];
-# endif // JWXYZ_GL
-
-# ifdef JWXYZ_QUARTZ
- if (colorspace)
- CGColorSpaceRelease (colorspace);
-# endif // JWXYZ_QUARTZ
-
- [prefsReader release];
-
- // xsft
- // fpst
-
- [super dealloc];
-}
-
-- (PrefsReader *) prefsReader
-{
- return prefsReader;
-}
-
-
-#ifdef USE_IPHONE
-- (void) lockFocus { }
-- (void) unlockFocus { }
-#endif // USE_IPHONE
-
-
-
-# ifdef USE_IPHONE
-/* A few seconds after the saver launches, we store the "wasRunning"
- preference. This is so that if the saver is crashing at startup,
- we don't launch it again next time, getting stuck in a crash loop.
- */
-- (void) allSystemsGo: (NSTimer *) timer
-{
- NSAssert (timer == crash_timer, @"crash timer screwed up");
- crash_timer = 0;
-
- NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
- [prefs setBool:YES forKey:@"wasRunning"];
- [prefs synchronize];
-}
-
-
-- (void) resizeGL
-{
- if (!ogl_ctx)
- return;
-
- CGSize screen_size = self.bounds.size;
- double s = self.contentScaleFactor;
- screen_size.width *= s;
- screen_size.height *= s;
-
-#if defined JWXYZ_GL
- GLuint *framebuffer = &xwindow->gl_framebuffer;
- GLuint *renderbuffer = &xwindow->gl_renderbuffer;
- xwindow->window.current_drawable = xwindow;
-#elif defined JWXYZ_QUARTZ
- GLuint *framebuffer = &gl_framebuffer;
- GLuint *renderbuffer = &gl_renderbuffer;
-#endif // JWXYZ_QUARTZ
-
- if (*framebuffer) glDeleteFramebuffersOES (1, framebuffer);
- if (*renderbuffer) glDeleteRenderbuffersOES (1, renderbuffer);
-
- create_framebuffer (framebuffer, renderbuffer);
-
- // redundant?
- // glRenderbufferStorageOES (GL_RENDERBUFFER_OES, GL_RGBA8_OES,
- // (int)size.width, (int)size.height);
- [ogl_ctx renderbufferStorage:GL_RENDERBUFFER_OES
- fromDrawable:(CAEAGLLayer*)self.layer];
-
- glFramebufferRenderbufferOES (GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES,
- GL_RENDERBUFFER_OES, *renderbuffer);
-
- [self addExtraRenderbuffers:screen_size];
-
- check_framebuffer_status();
-}
-#endif // USE_IPHONE
-
-
-- (void) startAnimation
-{
- NSAssert(![self isAnimating], @"already animating");
- NSAssert(!initted_p && !xdata, @"already initialized");
-
- // See comment in render_x11() for why this value is important:
- [self setAnimationTimeInterval: 1.0 / 240.0];
-
- [super startAnimation];
- /* We can't draw on the window from this method, so we actually do the
- initialization of the screen saver (xsft->init_cb) in the first call
- to animateOneFrame() instead.
- */
-
-# ifdef USE_IPHONE
- if (crash_timer)
- [crash_timer invalidate];
-
- NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
- [prefs removeObjectForKey:@"wasRunning"];
- [prefs synchronize];
-
- crash_timer = [NSTimer scheduledTimerWithTimeInterval: 5
- target:self
- selector:@selector(allSystemsGo:)
- userInfo:nil
- repeats:NO];
-
-# endif // USE_IPHONE
-
- // Never automatically turn the screen off if we are docked,
- // and an animation is running.
- //
-# ifdef USE_IPHONE
- [UIApplication sharedApplication].idleTimerDisabled =
- ([UIDevice currentDevice].batteryState != UIDeviceBatteryStateUnplugged);
-# endif
-
- xwindow = (Window) calloc (1, sizeof(*xwindow));
- xwindow->type = WINDOW;
- xwindow->window.view = self;
- CFRetain (xwindow->window.view); // needed for garbage collection?
-
-#ifdef BACKBUFFER_OPENGL
- CGSize new_backbuffer_size;
-
- {
-# ifndef USE_IPHONE
- if (!ogl_ctx) {
-
- pixfmt = [self getGLPixelFormat];
- [pixfmt retain];
-
- NSAssert (pixfmt, @"unable to create NSOpenGLPixelFormat");
-
- // Fun: On OS X 10.7, the second time an OpenGL context is created, after
- // the preferences dialog is launched in SaverTester, the context only
- // lasts until the first full GC. Then it turns black. Solution is to
- // reuse the OpenGL context after this point.
- // "Analyze" says that both pixfmt and ogl_ctx are leaked.
- ogl_ctx = [[NSOpenGLContext alloc] initWithFormat:pixfmt
- shareContext:nil];
-
- // Sync refreshes to the vertical blanking interval
- GLint r = 1;
- [ogl_ctx setValues:&r forParameter:NSOpenGLCPSwapInterval];
-// check_gl_error ("NSOpenGLCPSwapInterval"); // SEGV sometimes. Too early?
- }
-
- [ogl_ctx makeCurrentContext];
- check_gl_error ("makeCurrentContext");
-
- // NSOpenGLContext logs an 'invalid drawable' when this is called
- // from initWithFrame.
- [ogl_ctx setView:self];
-
- // Get device pixels instead of points.
- self.wantsBestResolutionOpenGLSurface = YES;
-
- // This may not be necessary if there's FBO support.
-# ifdef JWXYZ_GL
- xwindow->window.pixfmt = pixfmt;
- CFRetain (xwindow->window.pixfmt);
- xwindow->window.virtual_screen = [ogl_ctx currentVirtualScreen];
- xwindow->window.current_drawable = xwindow;
- NSAssert (ogl_ctx, @"no CGContext");
-# endif
-
- // Clear frame buffer ASAP, else there are bits left over from other apps.
- glClearColor (0, 0, 0, 1);
- glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
-// glFinish ();
-// glXSwapBuffers (mi->dpy, mi->window);
-
-
- // Enable multi-threading, if possible. This runs most OpenGL commands
- // and GPU management on a second CPU.
- {
-# ifndef kCGLCEMPEngine
-# define kCGLCEMPEngine 313 // Added in MacOS 10.4.8 + XCode 2.4.
-# endif
- CGLContextObj cctx = CGLGetCurrentContext();
- CGLError err = CGLEnable (cctx, kCGLCEMPEngine);
- if (err != kCGLNoError) {
- NSLog (@"enabling multi-threaded OpenGL failed: %d", err);
- }
- }
-
- new_backbuffer_size = NSSizeToCGSize ([self bounds].size);
-
- // Scale factor for desktop retina displays
- double s = [self hackedContentScaleFactor];
- new_backbuffer_size.width *= s;
- new_backbuffer_size.height *= s;
-
-# else // USE_IPHONE
- if (!ogl_ctx) {
- CAEAGLLayer *eagl_layer = (CAEAGLLayer *) self.layer;
- eagl_layer.opaque = TRUE;
- eagl_layer.drawableProperties = [self getGLProperties];
-
- // Without this, the GL frame buffer is half the screen resolution!
- eagl_layer.contentsScale = [UIScreen mainScreen].scale;
-
- ogl_ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
-# ifdef JWXYZ_GL
- ogl_ctx_pixmap = [[EAGLContext alloc]
- initWithAPI:kEAGLRenderingAPIOpenGLES1
- sharegroup:ogl_ctx.sharegroup];
-# endif // JWXYZ_GL
-
- eagl_layer.contentsGravity = [self getCAGravity];
- }
-
-# ifdef JWXYZ_GL
- xwindow->window.ogl_ctx_pixmap = ogl_ctx_pixmap;
-# endif // JWXYZ_GL
-
- [EAGLContext setCurrentContext: ogl_ctx];
-
- [self resizeGL];
-
- double s = [self hackedContentScaleFactor];
- new_backbuffer_size = self.bounds.size;
- new_backbuffer_size.width *= s;
- new_backbuffer_size.height *= s;
-
-# endif // USE_IPHONE
-
-# ifdef JWXYZ_GL
- xwindow->ogl_ctx = ogl_ctx;
-# ifndef USE_IPHONE
- CFRetain (xwindow->ogl_ctx);
-# endif // USE_IPHONE
-# endif // JWXYZ_GL
-
- check_gl_error ("startAnimation");
-
-// NSLog (@"%s / %s / %s\n", glGetString (GL_VENDOR),
-// glGetString (GL_RENDERER), glGetString (GL_VERSION));
-
- [self enableBackbuffer:new_backbuffer_size];
- }
-#endif // BACKBUFFER_OPENGL
-
- [self setViewport];
- [self createBackbuffer:new_backbuffer_size];
-
-# ifdef USE_TOUCHBAR
- if (touchbar_view) [touchbar_view startAnimation];
-# endif // USE_TOUCHBAR
-}
-
-- (void)stopAnimation
-{
- NSAssert([self isAnimating], @"not animating");
-
- if (initted_p) {
-
- [self lockFocus]; // in case something tries to draw from here
- [self prepareContext];
-
- /* All of the xlockmore hacks need to have their release functions
- called, or launching the same saver twice does not work. Also
- webcollage-cocoa needs it in order to kill the inferior webcollage
- processes (since the screen saver framework never generates a
- SIGPIPE for them).
- */
- if (xdata)
- xsft->free_cb (xdpy, xwindow, xdata);
- [self unlockFocus];
-
- jwxyz_quartz_free_display (xdpy);
- xdpy = NULL;
-# if defined JWXYZ_GL && !defined USE_IPHONE
- CFRelease (xwindow->ogl_ctx);
-# endif
- CFRelease (xwindow->window.view);
- free (xwindow);
- xwindow = NULL;
-
-// setup_p = NO; // #### wait, do we need this?
- initted_p = NO;
- xdata = 0;
- }
-
-# ifdef USE_IPHONE
- if (crash_timer)
- [crash_timer invalidate];
- crash_timer = 0;
- NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
- [prefs removeObjectForKey:@"wasRunning"];
- [prefs synchronize];
-# endif // USE_IPHONE
-
- [super stopAnimation];
-
- // When an animation is no longer running (e.g., looking at the list)
- // then it's ok to power off the screen when docked.
- //
-# ifdef USE_IPHONE
- [UIApplication sharedApplication].idleTimerDisabled = NO;
-# endif
-
- // Without this, the GL frame stays on screen when switching tabs
- // in System Preferences.
- // (Or perhaps it used to. It doesn't seem to matter on 10.9.)
- //
-# ifndef USE_IPHONE
- [NSOpenGLContext clearCurrentContext];
-# endif // !USE_IPHONE
-
- clear_gl_error(); // This hack is defunct, don't let this linger.
-
-# ifdef JWXYZ_QUARTZ
- CGContextRelease (backbuffer);
- backbuffer = nil;
-
- if (backbuffer_len)
- munmap (backbuffer_data, backbuffer_len);
- backbuffer_data = NULL;
- backbuffer_len = 0;
-# endif
-
-# ifdef USE_TOUCHBAR
- if (touchbar_view) {
- [touchbar_view stopAnimation];
- [touchbar_view release];
- touchbar_view = nil;
- }
-# endif
-}
-
-
-- (NSOpenGLContext *) oglContext
-{
- return ogl_ctx;
-}
-
-
-// #### maybe this could/should just be on 'lockFocus' instead?
-- (void) prepareContext
-{
- if (xwindow) {
-#ifdef USE_IPHONE
- [EAGLContext setCurrentContext:ogl_ctx];
-#else // !USE_IPHONE
- [ogl_ctx makeCurrentContext];
-// check_gl_error ("makeCurrentContext");
-#endif // !USE_IPHONE
-
-#ifdef JWXYZ_GL
- xwindow->window.current_drawable = xwindow;
-#endif
- }
-}
-
-
-#ifdef USE_TOUCHBAR
-
-static NSString *touchbar_cid = @"org.jwz.xscreensaver.touchbar";
-static NSString *touchbar_iid = @"org.jwz.xscreensaver.touchbar";
-
-- (NSTouchBar *) makeTouchBar
-{
- NSTouchBar *t = [[NSTouchBar alloc] init];
- t.delegate = self;
- t.customizationIdentifier = touchbar_cid;
- t.defaultItemIdentifiers = @[touchbar_iid,
- NSTouchBarItemIdentifierOtherItemsProxy];
- t.customizationAllowedItemIdentifiers = @[touchbar_iid];
- t.principalItemIdentifier = touchbar_iid;
- return t;
-}
-
-- (NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar
- makeItemForIdentifier:(NSTouchBarItemIdentifier)id
-{
- if ([id isEqualToString:touchbar_iid])
- {
- NSRect rect = [self frame];
- // #### debugging
- rect.origin.x = 0;
- rect.origin.y = 0;
- rect.size.width = 200;
- rect.size.height = 40;
- touchbar_view = [[[self class] alloc]
- initWithFrame:rect
- saverName:[NSString stringWithCString:xsft->progclass
- encoding:NSISOLatin1StringEncoding]
- isPreview:self.isPreview
- isTouchbar:True];
- [touchbar_view setAutoresizingMask:
- NSViewWidthSizable|NSViewHeightSizable];
- NSCustomTouchBarItem *item =
- [[NSCustomTouchBarItem alloc] initWithIdentifier:id];
- item.view = touchbar_view;
- item.customizationLabel = touchbar_cid;
-
- if ([self isAnimating])
- // TouchBar was created after animation begun.
- [touchbar_view startAnimation];
- }
- return nil;
-}
-
-#endif // USE_TOUCHBAR
-
-
-static void
-screenhack_do_fps (Display *dpy, Window w, fps_state *fpst, void *closure)
-{
- fps_compute (fpst, 0, -1);
- fps_draw (fpst);
-}
-
-
-/* Some of the older X11 savers look bad if a "pixel" is not a thing you can
- see. They expect big, chunky, luxurious 1990s pixels, and if they use
- "device" pixels on a Retina screen, everything just disappears.
-
- Retina iPads have 768x1024 point screens which are 1536x2048 pixels,
- 2017 iMac screens are 5120x2880 in device pixels.
-
- This method is overridden in XScreenSaverGLView, since this kludge
- isn't necessary for GL programs, being resolution independent by
- nature.
- */
-- (CGFloat) hackedContentScaleFactor
-{
-# ifdef USE_IPHONE
- CGFloat s = self.contentScaleFactor;
-# else
- CGFloat s = self.window.backingScaleFactor;
-# endif
-
- if (_lowrez_p) {
- NSSize b = [self bounds].size;
- CGFloat wh = b.width > b.height ? b.width : b.height;
-
- // Scale down to as close to 1024 as we can get without going under,
- // while keeping an integral scale factor so that we don't get banding
- // artifacts and moire patterns.
- //
- // Retina sizes: 2208 => 1104, 2224 => 1112, 2732 => 1366, 2880 => 1440.
- //
- int s2 = wh / 1024;
- if (s2) s /= s2;
- }
-
- return s;
-}
-
-
-#ifdef USE_IPHONE
-
-double
-current_device_rotation (void)
-{
- UIDeviceOrientation o = [[UIDevice currentDevice] orientation];
-
- /* Sometimes UIDevice doesn't know the proper orientation, or the device is
- face up/face down, so in those cases fall back to the status bar
- orientation. The SaverViewController tries to set the status bar to the
- proper orientation before it creates the XScreenSaverView; see
- _storedOrientation in SaverViewController.
- */
- if (o == UIDeviceOrientationUnknown ||
- o == UIDeviceOrientationFaceUp ||
- o == UIDeviceOrientationFaceDown) {
- /* Mind the differences between UIInterfaceOrientation and
- UIDeviceOrientation:
- 1. UIInterfaceOrientation does not include FaceUp and FaceDown.
- 2. LandscapeLeft and LandscapeRight are swapped between the two. But
- converting between device and interface orientation doesn't need to
- take this into account, because (from the UIInterfaceOrientation
- description): "rotating the device requires rotating the content in
- the opposite direction."
- */
- /* statusBarOrientation deprecated in iOS 9 */
- o = (UIDeviceOrientation) // from UIInterfaceOrientation
- [UIApplication sharedApplication].statusBarOrientation;
- }
-
- switch (o) {
- case UIDeviceOrientationLandscapeLeft: return -90; break;
- case UIDeviceOrientationLandscapeRight: return 90; break;
- case UIDeviceOrientationPortraitUpsideDown: return 180; break;
- default: return 0; break;
- }
-}
-
-
-- (void) handleException: (NSException *)e
-{
- NSLog (@"Caught exception: %@", e);
- UIAlertController *c = [UIAlertController
- alertControllerWithTitle:
- [NSString stringWithFormat: @"%s crashed!",
- xsft->progclass]
- message: [NSString stringWithFormat:
- @"The error message was:"
- "\n\n%@\n\n"
- "If it keeps crashing, try "
- "resetting its options.",
- e]
- preferredStyle:UIAlertControllerStyleAlert];
-
- [c addAction: [UIAlertAction actionWithTitle: @"Exit"
- style: UIAlertActionStyleDefault
- handler: ^(UIAlertAction *a) {
- exit (-1);
- }]];
- [c addAction: [UIAlertAction actionWithTitle: @"Keep going"
- style: UIAlertActionStyleDefault
- handler: ^(UIAlertAction *a) {
- [self stopAndClose:NO];
- }]];
-
- UIViewController *vc =
- [UIApplication sharedApplication].keyWindow.rootViewController;
- while (vc.presentedViewController)
- vc = vc.presentedViewController;
- [vc presentViewController:c animated:YES completion:nil];
- [self stopAnimation];
-}
-
-#endif // USE_IPHONE
-
-
-#ifdef JWXYZ_QUARTZ
-
-# ifndef USE_IPHONE
-
-struct gl_version
-{
- // iOS always uses OpenGL ES 1.1.
- unsigned major;
- unsigned minor;
-};
-
-static GLboolean
-gl_check_ver (const struct gl_version *caps,
- unsigned gl_major,
- unsigned gl_minor)
-{
- return caps->major > gl_major ||
- (caps->major == gl_major && caps->minor >= gl_minor);
-}
-
-# endif
-
-/* Called during startAnimation before the first call to createBackbuffer. */
-- (void) enableBackbuffer:(CGSize)new_backbuffer_size
-{
-# ifndef USE_IPHONE
- struct gl_version version;
-
- {
- const char *version_str = (const char *)glGetString (GL_VERSION);
-
- /* iPhone is always OpenGL ES 1.1. */
- if (sscanf ((const char *)version_str, "%u.%u",
- &version.major, &version.minor) < 2)
- {
- version.major = 1;
- version.minor = 1;
- }
- }
-# endif
-
- // The OpenGL extensions in use in here are pretty are pretty much ubiquitous
- // on OS X, but it's still good form to check.
- const GLubyte *extensions = glGetString (GL_EXTENSIONS);
-
- glGenTextures (1, &backbuffer_texture);
-
- // On really old systems, it would make sense to split the texture
- // into subsections
-# ifndef USE_IPHONE
- gl_texture_target = (gluCheckExtension ((const GLubyte *)
- "GL_ARB_texture_rectangle",
- extensions)
- ? GL_TEXTURE_RECTANGLE_EXT : GL_TEXTURE_2D);
-# else
- // OES_texture_npot also provides this, but iOS never provides it.
- gl_limited_npot_p = jwzgles_gluCheckExtension
- ((const GLubyte *) "GL_APPLE_texture_2D_limited_npot", extensions);
- gl_texture_target = GL_TEXTURE_2D;
-# endif
-
- glBindTexture (gl_texture_target, backbuffer_texture);
- glTexParameteri (gl_texture_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
- // GL_LINEAR might make sense on Retina iPads.
- glTexParameteri (gl_texture_target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
- glTexParameteri (gl_texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- glTexParameteri (gl_texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
-
-# ifndef USE_IPHONE
- // There isn't much sense in supporting one of these if the other
- // isn't present.
- gl_apple_client_storage_p =
- gluCheckExtension ((const GLubyte *)"GL_APPLE_client_storage",
- extensions) &&
- gluCheckExtension ((const GLubyte *)"GL_APPLE_texture_range", extensions);
-
- if (gl_apple_client_storage_p) {
- glTexParameteri (gl_texture_target, GL_TEXTURE_STORAGE_HINT_APPLE,
- GL_STORAGE_SHARED_APPLE);
- glPixelStorei (GL_UNPACK_CLIENT_STORAGE_APPLE, GL_TRUE);
- }
-# endif
-
- // If a video adapter suports BGRA textures, then that's probably as fast as
- // you're gonna get for getting a texture onto the screen.
-# ifdef USE_IPHONE
- gl_pixel_format =
- jwzgles_gluCheckExtension
- ((const GLubyte *)"GL_APPLE_texture_format_BGRA8888", extensions) ?
- GL_BGRA :
- GL_RGBA;
-
- gl_pixel_type = GL_UNSIGNED_BYTE;
- // See also OES_read_format.
-# else
- if (gl_check_ver (&version, 1, 2) ||
- (gluCheckExtension ((const GLubyte *)"GL_EXT_bgra", extensions) &&
- gluCheckExtension ((const GLubyte *)"GL_APPLE_packed_pixels",
- extensions))) {
- gl_pixel_format = GL_BGRA;
- // Both Intel and PowerPC-era docs say to use GL_UNSIGNED_INT_8_8_8_8_REV.
- gl_pixel_type = GL_UNSIGNED_INT_8_8_8_8_REV;
- } else {
- gl_pixel_format = GL_RGBA;
- gl_pixel_type = GL_UNSIGNED_BYTE;
- }
- // GL_ABGR_EXT/GL_UNSIGNED_BYTE is another possibilty that may have made more
- // sense on PowerPC.
-# endif
-
- glEnable (gl_texture_target);
- glEnableClientState (GL_VERTEX_ARRAY);
- glEnableClientState (GL_TEXTURE_COORD_ARRAY);
-
- check_gl_error ("enableBackbuffer");
-}
-
-
-#ifdef USE_IPHONE
-- (BOOL) suppressRotationAnimation
-{
- return [self ignoreRotation]; // Don't animate if we aren't rotating
-}
-
-- (BOOL) rotateTouches
-{
- return FALSE; // Adjust event coordinates only if rotating
-}
-#endif
-
-
-- (void) setViewport
-{
-# ifdef BACKBUFFER_OPENGL
- NSAssert ([NSOpenGLContext currentContext] ==
- ogl_ctx, @"invalid GL context");
-
- NSSize new_size = self.bounds.size;
-
-# ifdef USE_IPHONE
- GLfloat s = self.contentScaleFactor;
-# else // !USE_IPHONE
- const GLfloat s = self.window.backingScaleFactor;
-# endif
- GLfloat hs = self.hackedContentScaleFactor;
-
- // On OS X this almost isn't necessary, except for the ugly aliasing
- // artifacts.
- glViewport (0, 0, new_size.width * s, new_size.height * s);
-
- glMatrixMode (GL_PROJECTION);
- glLoadIdentity();
-# ifdef USE_IPHONE
- glOrthof
-# else
- glOrtho
-# endif
- (-new_size.width * hs, new_size.width * hs,
- -new_size.height * hs, new_size.height * hs,
- -1, 1);
-
-# ifdef USE_IPHONE
- if ([self ignoreRotation]) {
- int o = (int) -current_device_rotation();
- glRotatef (o, 0, 0, 1);
- }
-# endif // USE_IPHONE
-# endif // BACKBUFFER_OPENGL
-}
-
-
-/* Create a bitmap context into which we render everything.
- If the desired size has changed, re-created it.
- new_size is in rotated pixels, not points: the same size
- and shape as the X11 window as seen by the hacks.
- */
-- (void) createBackbuffer:(CGSize)new_size
-{
- CGSize osize = CGSizeZero;
- if (backbuffer) {
- osize.width = CGBitmapContextGetWidth(backbuffer);
- osize.height = CGBitmapContextGetHeight(backbuffer);
- }
-
- if (backbuffer &&
- (int)osize.width == (int)new_size.width &&
- (int)osize.height == (int)new_size.height)
- return;
-
- CGContextRef ob = backbuffer;
- void *odata = backbuffer_data;
- GLsizei olen = backbuffer_len;
-
-# if !defined __OPTIMIZE__ || TARGET_IPHONE_SIMULATOR
- NSLog(@"backbuffer %.0fx%.0f",
- new_size.width, new_size.height);
-# endif
-
- /* OS X uses APPLE_client_storage and APPLE_texture_range, as described in
- <https://developer.apple.com/library/mac/documentation/GraphicsImaging/Conceptual/OpenGL-MacProgGuide/opengl_texturedata/opengl_texturedata.html>.
-
- iOS uses bog-standard glTexImage2D (for now).
-
- glMapBuffer is the standard way to get data from system RAM to video
- memory asynchronously and without a memcpy, but support for
- APPLE_client_storage is ubiquitous on OS X (not so for glMapBuffer),
- and on iOS GL_PIXEL_UNPACK_BUFFER is only available on OpenGL ES 3
- (iPhone 5S or newer). Plus, glMapBuffer doesn't work well with
- CGBitmapContext: glMapBuffer can return a different pointer on each
- call, but a CGBitmapContext doesn't allow its data pointer to be
- changed -- and recreating the context for a new pointer can be
- expensive (glyph caches get dumped, for instance).
-
- glMapBufferRange has MAP_FLUSH_EXPLICIT_BIT and MAP_UNSYNCHRONIZED_BIT,
- and these seem to allow mapping the buffer and leaving it where it is
- in client address space while OpenGL works with the buffer, but it
- requires OpenGL 3 Core profile on OS X (and ES 3 on iOS for
- GL_PIXEL_UNPACK_BUFFER), so point goes to APPLE_client_storage.
-
- AMD_pinned_buffer provides the same advantage as glMapBufferRange, but
- Apple never implemented that one for OS X.
- */
-
- backbuffer_data = NULL;
- gl_texture_w = (int)new_size.width;
- gl_texture_h = (int)new_size.height;
-
- NSAssert (gl_texture_target == GL_TEXTURE_2D
-# ifndef USE_IPHONE
- || gl_texture_target == GL_TEXTURE_RECTANGLE_EXT
-# endif
- , @"unexpected GL texture target");
-
-# ifndef USE_IPHONE
- if (gl_texture_target != GL_TEXTURE_RECTANGLE_EXT)
-# else
- if (!gl_limited_npot_p)
-# endif
- {
- gl_texture_w = (GLsizei) to_pow2 (gl_texture_w);
- gl_texture_h = (GLsizei) to_pow2 (gl_texture_h);
- }
-
- GLsizei bytes_per_row = gl_texture_w * 4;
-
-# if defined(BACKBUFFER_OPENGL) && !defined(USE_IPHONE)
- // APPLE_client_storage requires texture width to be aligned to 32 bytes, or
- // it will fall back to a memcpy.
- // https://developer.apple.com/library/mac/documentation/GraphicsImaging/Conceptual/OpenGL-MacProgGuide/opengl_texturedata/opengl_texturedata.html#//apple_ref/doc/uid/TP40001987-CH407-SW24
- bytes_per_row = (bytes_per_row + 31) & ~31;
-# endif // BACKBUFFER_OPENGL && !USE_IPHONE
-
- backbuffer_len = bytes_per_row * gl_texture_h;
- if (backbuffer_len) // mmap requires this to be non-zero.
- backbuffer_data = mmap (NULL, backbuffer_len,
- PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED,
- -1, 0);
-
- BOOL alpha_first_p, order_little_p;
-
- if (gl_pixel_format == GL_BGRA) {
- alpha_first_p = YES;
- order_little_p = YES;
-/*
- } else if (gl_pixel_format == GL_ABGR_EXT) {
- alpha_first_p = NO;
- order_little_p = YES; */
- } else {
- NSAssert (gl_pixel_format == GL_RGBA, @"unknown GL pixel format");
- alpha_first_p = NO;
- order_little_p = NO;
- }
-
-#ifdef USE_IPHONE
- NSAssert (gl_pixel_type == GL_UNSIGNED_BYTE, @"unknown GL pixel type");
-#else
- NSAssert (gl_pixel_type == GL_UNSIGNED_INT_8_8_8_8 ||
- gl_pixel_type == GL_UNSIGNED_INT_8_8_8_8_REV ||
- gl_pixel_type == GL_UNSIGNED_BYTE,
- @"unknown GL pixel type");
-
-#if defined __LITTLE_ENDIAN__
- const GLenum backwards_pixel_type = GL_UNSIGNED_INT_8_8_8_8;
-#elif defined __BIG_ENDIAN__
- const GLenum backwards_pixel_type = GL_UNSIGNED_INT_8_8_8_8_REV;
-#else
-# error Unknown byte order.
-#endif
-
- if (gl_pixel_type == backwards_pixel_type)
- order_little_p ^= YES;
-#endif
-
- CGBitmapInfo bitmap_info =
- (alpha_first_p ? kCGImageAlphaNoneSkipFirst : kCGImageAlphaNoneSkipLast) |
- (order_little_p ? kCGBitmapByteOrder32Little : kCGBitmapByteOrder32Big);
-
- backbuffer = CGBitmapContextCreate (backbuffer_data,
- (int)new_size.width,
- (int)new_size.height,
- 8,
- bytes_per_row,
- colorspace,
- bitmap_info);
- NSAssert (backbuffer, @"unable to allocate back buffer");
-
- // Clear it.
- CGRect r;
- r.origin.x = r.origin.y = 0;
- r.size = new_size;
- CGContextSetGrayFillColor (backbuffer, 0, 1);
- CGContextFillRect (backbuffer, r);
-
-# if defined(BACKBUFFER_OPENGL) && !defined(USE_IPHONE)
- if (gl_apple_client_storage_p)
- glTextureRangeAPPLE (gl_texture_target, backbuffer_len, backbuffer_data);
-# endif // BACKBUFFER_OPENGL && !USE_IPHONE
-
- if (ob) {
- // Restore old bits, as much as possible, to the X11 upper left origin.
-
- CGRect rect; // pixels, not points
- rect.origin.x = 0;
- rect.origin.y = (new_size.height - osize.height);
- rect.size = osize;
-
- CGImageRef img = CGBitmapContextCreateImage (ob);
- CGContextDrawImage (backbuffer, rect, img);
- CGImageRelease (img);
- CGContextRelease (ob);
-
- if (olen)
- // munmap should round len up to the nearest page.
- munmap (odata, olen);
- }
-
- check_gl_error ("createBackbuffer");
-}
-
-
-- (void) drawBackbuffer
-{
-# ifdef BACKBUFFER_OPENGL
-
- NSAssert ([ogl_ctx isKindOfClass:[NSOpenGLContext class]],
- @"ogl_ctx is not an NSOpenGLContext");
-
- NSAssert (! (CGBitmapContextGetBytesPerRow (backbuffer) % 4),
- @"improperly-aligned backbuffer");
-
- // This gets width and height from the backbuffer in case
- // APPLE_client_storage is in use. See the note in createBackbuffer.
- // This still has to happen every frame even when APPLE_client_storage has
- // the video adapter pulling texture data straight from
- // XScreenSaverView-owned memory.
- glTexImage2D (gl_texture_target, 0, GL_RGBA,
- (GLsizei)(CGBitmapContextGetBytesPerRow (backbuffer) / 4),
- gl_texture_h, 0, gl_pixel_format, gl_pixel_type,
- backbuffer_data);
-
- GLfloat w = xwindow->frame.width, h = xwindow->frame.height;
-
- GLfloat vertices[4][2] = {{-w, h}, {w, h}, {w, -h}, {-w, -h}};
-
- GLfloat tex_coords[4][2];
-
-# ifndef USE_IPHONE
- if (gl_texture_target != GL_TEXTURE_RECTANGLE_EXT)
-# endif // USE_IPHONE
- {
- w /= gl_texture_w;
- h /= gl_texture_h;
- }
-
- tex_coords[0][0] = 0;
- tex_coords[0][1] = 0;
- tex_coords[1][0] = w;
- tex_coords[1][1] = 0;
- tex_coords[2][0] = w;
- tex_coords[2][1] = h;
- tex_coords[3][0] = 0;
- tex_coords[3][1] = h;
-
- glVertexPointer (2, GL_FLOAT, 0, vertices);
- glTexCoordPointer (2, GL_FLOAT, 0, tex_coords);
- glDrawArrays (GL_TRIANGLE_FAN, 0, 4);
-
-# if !defined __OPTIMIZE__ || TARGET_IPHONE_SIMULATOR
- check_gl_error ("drawBackbuffer");
-# endif
-# endif // BACKBUFFER_OPENGL
-}
-
-#endif // JWXYZ_QUARTZ
-
-#ifdef JWXYZ_GL
-
-- (void)enableBackbuffer:(CGSize)new_backbuffer_size;
-{
- jwxyz_set_matrices (new_backbuffer_size.width, new_backbuffer_size.height);
- check_gl_error ("enableBackbuffer");
-}
-
-- (void)createBackbuffer:(CGSize)new_size
-{
- NSAssert ([NSOpenGLContext currentContext] ==
- ogl_ctx, @"invalid GL context");
- NSAssert (xwindow->window.current_drawable == xwindow,
- @"current_drawable not set properly");
-
-# ifndef USE_IPHONE
- /* On iOS, Retina means glViewport gets called with the screen size instead
- of the backbuffer/xwindow size. This happens in startAnimation.
-
- The GL screenhacks call glViewport themselves.
- */
- glViewport (0, 0, new_size.width, new_size.height);
-# endif
-
- // TODO: Preserve contents on resize.
- glClear (GL_COLOR_BUFFER_BIT);
- check_gl_error ("createBackbuffer");
-}
-
-#endif // JWXYZ_GL
-
-
-- (void)flushBackbuffer
-{
-# ifdef JWXYZ_GL
- // Make sure the right context is active: there's two under JWXYZ_GL.
- jwxyz_bind_drawable (xwindow, xwindow);
-# endif // JWXYZ_GL
-
-# ifndef USE_IPHONE
-
-# ifdef JWXYZ_QUARTZ
- // The OpenGL pipeline is not automatically synchronized with the contents
- // of the backbuffer, so without glFinish, OpenGL can start rendering from
- // the backbuffer texture at the same time that JWXYZ is clearing and
- // drawing the next frame in the backing store for the backbuffer texture.
- // This is only a concern under JWXYZ_QUARTZ because of
- // APPLE_client_storage; JWXYZ_GL doesn't use that.
- glFinish();
-# endif // JWXYZ_QUARTZ
-
- // If JWXYZ_GL was single-buffered, there would need to be a glFinish (or
- // maybe just glFlush?) here, because single-buffered contexts don't always
- // update what's on the screen after drawing finishes. (i.e., in safe mode)
-
-# ifdef JWXYZ_QUARTZ
- // JWXYZ_GL is always double-buffered.
- if (double_buffered_p)
-# endif // JWXYZ_QUARTZ
- [ogl_ctx flushBuffer]; // despite name, this actually swaps
-# else // USE_IPHONE
-
- // jwxyz_bind_drawable() only binds the framebuffer, not the renderbuffer.
-# ifdef JWXYZ_GL
- GLint gl_renderbuffer = xwindow->gl_renderbuffer;
-# endif
-
- glBindRenderbufferOES (GL_RENDERBUFFER_OES, gl_renderbuffer);
- [ogl_ctx presentRenderbuffer:GL_RENDERBUFFER_OES];
-# endif // USE_IPHONE
-
-# if !defined __OPTIMIZE__ || TARGET_IPHONE_SIMULATOR
- // glGetError waits for the OpenGL command pipe to flush, so skip it in
- // release builds.
- // OpenGL Programming Guide for Mac -> OpenGL Application Design
- // Strategies -> Allow OpenGL to Manage Your Resources
- // https://developer.apple.com/library/mac/documentation/GraphicsImaging/Conceptual/OpenGL-MacProgGuide/opengl_designstrategies/opengl_designstrategies.html#//apple_ref/doc/uid/TP40001987-CH2-SW7
- check_gl_error ("flushBackbuffer");
-# endif
-}
-
-
-/* Inform X11 that the size of our window has changed.
- */
-- (void) resize_x11
-{
- if (!xdpy) return; // early
-
- NSSize new_size; // pixels, not points
-
- new_size = self.bounds.size;
-
-# ifdef USE_IPHONE
-
- // If this hack ignores rotation, then that means that it pretends to
- // always be in portrait mode. If the View has been resized to a
- // landscape shape, swap width and height to keep the backbuffer
- // in portrait.
- //
- double rot = current_device_rotation();
- if ([self ignoreRotation] && (rot == 90 || rot == -90)) {
- CGFloat swap = new_size.width;
- new_size.width = new_size.height;
- new_size.height = swap;
- }
-# endif // USE_IPHONE
-
- double s = self.hackedContentScaleFactor;
- new_size.width *= s;
- new_size.height *= s;
-
- [self prepareContext];
- [self setViewport];
-
- // On first resize, xwindow->frame is 0x0.
- if (xwindow->frame.width == new_size.width &&
- xwindow->frame.height == new_size.height)
- return;
-
-# if defined(BACKBUFFER_OPENGL) && !defined(USE_IPHONE)
- [ogl_ctx update];
-# endif // BACKBUFFER_OPENGL && !USE_IPHONE
-
- NSAssert (xwindow && xwindow->type == WINDOW, @"not a window");
- xwindow->frame.x = 0;
- xwindow->frame.y = 0;
- xwindow->frame.width = new_size.width;
- xwindow->frame.height = new_size.height;
-
- [self createBackbuffer:CGSizeMake(xwindow->frame.width,
- xwindow->frame.height)];
-
-# if defined JWXYZ_QUARTZ
- xwindow->cgc = backbuffer;
- NSAssert (xwindow->cgc, @"no CGContext");
-# elif defined JWXYZ_GL && !defined USE_IPHONE
- [ogl_ctx update];
- [ogl_ctx setView:xwindow->window.view]; // (Is this necessary?)
-# endif // JWXYZ_GL && USE_IPHONE
-
- jwxyz_window_resized (xdpy);
-
-# if !defined __OPTIMIZE__ || TARGET_IPHONE_SIMULATOR
- NSLog(@"reshape %.0fx%.0f", new_size.width, new_size.height);
-# endif
-
- // Next time render_x11 is called, run the saver's reshape_cb.
- resized_p = YES;
-}
-
-
-#ifdef USE_IPHONE
-
-/* Called by SaverRunner when the device has changed orientation.
- That means we need to generate a resize event, even if the size
- has not changed (e.g., from LandscapeLeft to LandscapeRight).
- */
-- (void) orientationChanged
-{
- [self setViewport];
- resized_p = YES;
- next_frame_time = 0; // Get a new frame on screen quickly
-}
-
-/* A hook run after the 'reshape_' method has been called. Used by
- XScreenSaverGLView to adjust the in-scene GL viewport.
- */
-- (void) postReshape
-{
-}
-#endif // USE_IPHONE
-
-
-// Only render_x11 should call this. XScreenSaverGLView specializes it.
-- (void) reshape_x11
-{
- xsft->reshape_cb (xdpy, xwindow, xdata,
- xwindow->frame.width, xwindow->frame.height);
-}
-
-- (void) render_x11
-{
-# ifdef USE_IPHONE
- @try {
-# endif
-
- // jwxyz_make_display needs this.
- [self prepareContext]; // resize_x11 also calls this.
-
- if (!initted_p) {
-
- resized_p = NO;
-
- if (! xdpy) {
-# ifdef JWXYZ_QUARTZ
- xwindow->cgc = backbuffer;
-# endif // JWXYZ_QUARTZ
- xdpy = jwxyz_quartz_make_display (xwindow);
-
-# if defined USE_IPHONE
- /* Some X11 hacks (fluidballs) want to ignore all rotation events. */
- _ignoreRotation =
-# ifdef JWXYZ_GL
- TRUE; // Rotation doesn't work yet. TODO: Make rotation work.
-# else // !JWXYZ_GL
- get_boolean_resource (xdpy, "ignoreRotation", "IgnoreRotation");
-# endif // !JWXYZ_GL
-# endif // USE_IPHONE
-
- _lowrez_p = get_boolean_resource (xdpy, "lowrez", "Lowrez");
- if (_lowrez_p) {
- resized_p = YES;
-
-# if !defined __OPTIMIZE__ || TARGET_IPHONE_SIMULATOR
- NSSize b = [self bounds].size;
- CGFloat s = self.hackedContentScaleFactor;
-# ifdef USE_IPHONE
- CGFloat o = self.contentScaleFactor;
-# else
- CGFloat o = self.window.backingScaleFactor;
-# endif
- if (o != s)
- NSLog(@"lowrez: scaling %.0fx%.0f -> %.0fx%.0f (%.02f)",
- b.width * o, b.height * o,
- b.width * s, b.height * s, s);
-# endif
- }
-
- [self resize_x11];
- }
-
- if (!setup_p) {
- setup_p = YES;
- if (xsft->setup_cb)
- xsft->setup_cb (xsft, xsft->setup_arg);
- }
- initted_p = YES;
- NSAssert(!xdata, @"xdata already initialized");
-
-
-# undef ya_rand_init
- ya_rand_init (0);
-
- XSetWindowBackground (xdpy, xwindow,
- get_pixel_resource (xdpy, 0,
- "background", "Background"));
- XClearWindow (xdpy, xwindow);
-
-# ifndef USE_IPHONE
- [[self window] setAcceptsMouseMovedEvents:YES];
-# endif
-
- /* In MacOS 10.5, this enables "QuartzGL", meaning that the Quartz
- drawing primitives will run on the GPU instead of the CPU.
- It seems like it might make things worse rather than better,
- though... Plus it makes us binary-incompatible with 10.4.
-
-# if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
- [[self window] setPreferredBackingLocation:
- NSWindowBackingLocationVideoMemory];
-# endif
- */
-
- /* Kludge: even though the init_cb functions are declared to take 2 args,
- actually call them with 3, for the benefit of xlockmore_init() and
- xlockmore_setup().
- */
- void *(*init_cb) (Display *, Window, void *) =
- (void *(*) (Display *, Window, void *)) xsft->init_cb;
-
- xdata = init_cb (xdpy, xwindow, xsft->setup_arg);
- // NSAssert(xdata, @"no xdata from init");
- if (! xdata) abort();
-
- if (get_boolean_resource (xdpy, "doFPS", "DoFPS")) {
- fpst = fps_init (xdpy, xwindow);
- fps_cb = xsft->fps_cb;
- if (! fps_cb) fps_cb = screenhack_do_fps;
- } else {
- fpst = NULL;
- fps_cb = 0;
- }
-
-# ifdef USE_IPHONE
- if (current_device_rotation() != 0) // launched while rotated
- resized_p = YES;
-# endif
-
- [self checkForUpdates];
- }
-
-
- /* I don't understand why we have to do this *every frame*, but we do,
- or else the cursor comes back on.
- */
-# ifndef USE_IPHONE
- if (![self isPreview])
- [NSCursor setHiddenUntilMouseMoves:YES];
-# endif
-
-
- if (fpst)
- {
- /* This is just a guess, but the -fps code wants to know how long
- we were sleeping between frames.
- */
- long usecs = 1000000 * [self animationTimeInterval];
- usecs -= 200; // caller apparently sleeps for slightly less sometimes...
- if (usecs < 0) usecs = 0;
- fps_slept (fpst, usecs);
- }
-
-
- /* Run any XtAppAddInput and XtAppAddTimeOut callbacks now.
- Do this before delaying for next_frame_time to avoid throttling
- timers to the hack's frame rate.
- */
- XtAppProcessEvent (XtDisplayToApplicationContext (xdpy),
- XtIMTimer | XtIMAlternateInput);
-
-
- /* It turns out that on some systems (possibly only 10.5 and older?)
- [ScreenSaverView setAnimationTimeInterval] does nothing. This means
- that we cannot rely on it.
-
- Some of the screen hacks want to delay for long periods, and letting the
- framework run the update function at 30 FPS when it really wanted half a
- minute between frames would be bad. So instead, we assume that the
- framework's animation timer might fire whenever, but we only invoke the
- screen hack's "draw frame" method when enough time has expired.
-
- This means two extra calls to gettimeofday() per frame. For fast-cycling
- screen savers, that might actually slow them down. Oh well.
-
- A side-effect of this is that it's not possible for a saver to request
- an animation interval that is faster than animationTimeInterval.
-
- HOWEVER! On modern systems where setAnimationTimeInterval is *not*
- ignored, it's important that it be faster than 30 FPS. 240 FPS is good.
-
- An NSTimer won't fire if the timer is already running the invocation
- function from a previous firing. So, if we use a 30 FPS
- animationTimeInterval (33333 µs) and a screenhack takes 40000 µs for a
- frame, there will be a 26666 µs delay until the next frame, 66666 µs
- after the beginning of the current frame. In other words, 25 FPS
- becomes 15 FPS.
-
- Frame rates tend to snap to values of 30/N, where N is a positive
- integer, i.e. 30 FPS, 15 FPS, 10, 7.5, 6. And the 'snapped' frame rate
- is rounded down from what it would normally be.
-
- So if we set animationTimeInterval to 1/240 instead of 1/30, frame rates
- become values of 60/N, 120/N, or 240/N, with coarser or finer frame rate
- steps for higher or lower animation time intervals respectively.
- */
- struct timeval tv;
- gettimeofday (&tv, 0);
- double now = tv.tv_sec + (tv.tv_usec / 1000000.0);
- if (now < next_frame_time) return;
-
- // [self flushBackbuffer];
-
- if (resized_p) {
- // We do this here instead of in setFrame so that all the
- // Xlib drawing takes place under the animation timer.
-
-# ifndef USE_IPHONE
- if (ogl_ctx)
- [ogl_ctx setView:self];
-# endif // !USE_IPHONE
-
- [self reshape_x11];
- resized_p = NO;
- }
-
-
- // And finally:
- //
- // NSAssert(xdata, @"no xdata when drawing");
- if (! xdata) abort();
- unsigned long delay = xsft->draw_cb (xdpy, xwindow, xdata);
- if (fpst && fps_cb)
- fps_cb (xdpy, xwindow, fpst, xdata);
-
- gettimeofday (&tv, 0);
- now = tv.tv_sec + (tv.tv_usec / 1000000.0);
- next_frame_time = now + (delay / 1000000.0);
-
-# ifdef JWXYZ_QUARTZ
- [self drawBackbuffer];
-# endif
- // This can also happen near the beginning of render_x11.
- [self flushBackbuffer];
-
-# ifdef USE_IPHONE // Allow savers on the iPhone to run full-tilt.
- if (delay < [self animationTimeInterval])
- [self setAnimationTimeInterval:(delay / 1000000.0)];
-# endif
-
-# ifdef DO_GC_HACKERY
- /* Current theory is that the 10.6 garbage collector sucks in the
- following way:
-
- It only does a collection when a threshold of outstanding
- collectable allocations has been surpassed. However, CoreGraphics
- creates lots of small collectable allocations that contain pointers
- to very large non-collectable allocations: a small CG object that's
- collectable referencing large malloc'd allocations (non-collectable)
- containing bitmap data. So the large allocation doesn't get freed
- until GC collects the small allocation, which triggers its finalizer
- to run which frees the large allocation. So GC is deciding that it
- doesn't really need to run, even though the process has gotten
- enormous. GC eventually runs once pageouts have happened, but by
- then it's too late, and the machine's resident set has been
- sodomized.
-
- So, we force an exhaustive garbage collection in this process
- approximately every 5 seconds whether the system thinks it needs
- one or not.
- */
- {
- static int tick = 0;
- if (++tick > 5*30) {
- tick = 0;
- objc_collect (OBJC_EXHAUSTIVE_COLLECTION);
- }
- }
-# endif // DO_GC_HACKERY
-
-# ifdef USE_IPHONE
- }
- @catch (NSException *e) {
- [self handleException: e];
- }
-# endif // USE_IPHONE
-}
-
-
-- (void) animateOneFrame
-{
- // Render X11 into the backing store bitmap...
-
-# ifdef USE_TOUCHBAR
- if (touchbar_p) return;
-# endif
-
-# ifdef JWXYZ_QUARTZ
- NSAssert (backbuffer, @"no back buffer");
-
-# ifdef USE_IPHONE
- UIGraphicsPushContext (backbuffer);
-# endif
-# endif // JWXYZ_QUARTZ
-
- [self render_x11];
-
-# if defined USE_IPHONE && defined JWXYZ_QUARTZ
- UIGraphicsPopContext();
-# endif
-
-# ifdef USE_TOUCHBAR
- if (touchbar_view) [touchbar_view animateOneFrame];
-# endif
-}
-
-
-# ifndef USE_IPHONE // Doesn't exist on iOS
-
-- (void) setFrame:(NSRect) newRect
-{
- [super setFrame:newRect];
-
- if (xwindow) // inform Xlib that the window has changed now.
- [self resize_x11];
-}
-
-- (void) setFrameSize:(NSSize) newSize
-{
- [super setFrameSize:newSize];
- if (xwindow)
- [self resize_x11];
-}
-
-# else // USE_IPHONE
-
-- (void) layoutSubviews
-{
- [super layoutSubviews];
- [self resizeGL];
- if (xwindow)
- [self resize_x11];
-}
-
-# endif
-
-
-+(BOOL) performGammaFade
-{
- return YES;
-}
-
-- (BOOL) hasConfigureSheet
-{
- return YES;
-}
-
-+ (NSString *) decompressXML: (NSData *)data
-{
- if (! data) return 0;
- BOOL compressed_p = !!strncmp ((const char *) data.bytes, "<?xml", 5);
-
- // If it's not already XML, decompress it.
- NSAssert (compressed_p, @"xml isn't compressed");
- if (compressed_p) {
- NSMutableData *data2 = 0;
- int ret = -1;
- z_stream zs;
- memset (&zs, 0, sizeof(zs));
- ret = inflateInit2 (&zs, 16 + MAX_WBITS);
- if (ret == Z_OK) {
- UInt32 usize = * (UInt32 *) (data.bytes + data.length - 4);
- data2 = [NSMutableData dataWithLength: usize];
- zs.next_in = (Bytef *) data.bytes;
- zs.avail_in = (uint) data.length;
- zs.next_out = (Bytef *) data2.bytes;
- zs.avail_out = (uint) data2.length;
- ret = inflate (&zs, Z_FINISH);
- inflateEnd (&zs);
- }
- if (ret == Z_OK || ret == Z_STREAM_END)
- data = data2;
- else
- NSAssert2 (0, @"gunzip error: %d: %s",
- ret, (zs.msg ? zs.msg : "<null>"));
- }
-
- NSString *s = [[NSString alloc]
- initWithData:data encoding:NSUTF8StringEncoding];
- [s autorelease];
- return s;
-}
-
-
-#ifndef USE_IPHONE
-- (NSWindow *) configureSheet
-#else
-- (UIViewController *) configureView
-#endif
-{
- NSBundle *bundle = [NSBundle bundleForClass:[self class]];
- NSString *file = [NSString stringWithCString:xsft->progclass
- encoding:NSISOLatin1StringEncoding];
- file = [file lowercaseString];
- NSString *path = [bundle pathForResource:file ofType:@"xml"];
- if (!path) {
- NSLog (@"%@.xml does not exist in the application bundle: %@/",
- file, [bundle resourcePath]);
- return nil;
- }
-
-# ifdef USE_IPHONE
- UIViewController *sheet;
-# else // !USE_IPHONE
- NSWindow *sheet;
-# endif // !USE_IPHONE
-
- NSData *xmld = [NSData dataWithContentsOfFile:path];
- NSString *xml = [[self class] decompressXML: xmld];
- sheet = [[XScreenSaverConfigSheet alloc]
- initWithXML:[xml dataUsingEncoding:NSUTF8StringEncoding]
- options:xsft->options
- controller:[prefsReader userDefaultsController]
- globalController:[prefsReader globalDefaultsController]
- defaults:[prefsReader defaultOptions]];
-
- // #### am I expected to retain this, or not? wtf.
- // I thought not, but if I don't do this, we (sometimes) crash.
- // #### Analyze says "potential leak of an object stored into sheet"
- // [sheet retain];
-
- return sheet;
-}
-
-
-- (NSUserDefaultsController *) userDefaultsController
-{
- return [prefsReader userDefaultsController];
-}
-
-
-/* Announce our willingness to accept keyboard input.
- */
-- (BOOL)acceptsFirstResponder
-{
- return YES;
-}
-
-
-- (void) beep
-{
-# ifndef USE_IPHONE
- NSBeep();
-# else // USE_IPHONE
-
- // There's no way to play a standard system alert sound!
- // We'd have to include our own WAV for that.
- //
- // Or we could vibrate:
- // #import <AudioToolbox/AudioToolbox.h>
- // AudioServicesPlaySystemSound (kSystemSoundID_Vibrate);
- //
- // Instead, just flash the screen white, then fade.
- //
- UIView *v = [[UIView alloc] initWithFrame: [self frame]];
- [v setBackgroundColor: [UIColor whiteColor]];
- [[self window] addSubview:v];
- [UIView animateWithDuration: 0.1
- animations:^{ [v setAlpha: 0.0]; }
- completion:^(BOOL finished) { [v removeFromSuperview]; } ];
-
-# endif // USE_IPHONE
-}
-
-
-/* Send an XEvent to the hack. Returns YES if it was handled.
- */
-- (BOOL) sendEvent: (XEvent *) e
-{
- if (!initted_p || ![self isAnimating]) // no event handling unless running.
- return NO;
-
-// [self lockFocus]; // As of 10.14 this causes flicker on mouse motion
- [self prepareContext];
- BOOL result = xsft->event_cb (xdpy, xwindow, xdata, e);
-// [self unlockFocus];cp -Rf ${CONFIGURATION_BUILD_DIR}/BuildOutputPrefPane.prefPane ~/Library/PreferencePanes
- return result;
-}
-
-
-#ifndef USE_IPHONE
-
-/* Convert an NSEvent into an XEvent, and pass it along.
- Returns YES if it was handled.
- */
-- (BOOL) convertEvent: (NSEvent *) e
- type: (int) type
-{
- XEvent xe;
- memset (&xe, 0, sizeof(xe));
-
- int state = 0;
-
- int flags = [e modifierFlags];
- if (flags & NSAlphaShiftKeyMask) state |= LockMask;
- if (flags & NSShiftKeyMask) state |= ShiftMask;
- if (flags & NSControlKeyMask) state |= ControlMask;
- if (flags & NSAlternateKeyMask) state |= Mod1Mask;
- if (flags & NSCommandKeyMask) state |= Mod2Mask;
-
- NSPoint p = [[[e window] contentView] convertPoint:[e locationInWindow]
- toView:self];
- double s = [self hackedContentScaleFactor];
- int x = s * p.x;
- int y = s * ([self bounds].size.height - p.y);
-
- xe.xany.type = type;
- switch (type) {
- case ButtonPress:
- case ButtonRelease:
- xe.xbutton.x = x;
- xe.xbutton.y = y;
- xe.xbutton.state = state;
- if ([e type] == NSScrollWheel)
- xe.xbutton.button = ([e deltaY] > 0 ? Button4 :
- [e deltaY] < 0 ? Button5 :
- [e deltaX] > 0 ? Button6 :
- [e deltaX] < 0 ? Button7 :
- 0);
- else
- xe.xbutton.button = (unsigned int) [e buttonNumber] + 1;
- break;
- case MotionNotify:
- xe.xmotion.x = x;
- xe.xmotion.y = y;
- xe.xmotion.state = state;
- break;
- case KeyPress:
- case KeyRelease:
- {
- NSString *ns = (([e type] == NSFlagsChanged) ? 0 :
- [e charactersIgnoringModifiers]);
- KeySym k = 0;
-
- if (!ns || [ns length] == 0) // dead key
- {
- // Cocoa hides the difference between left and right keys.
- // Also we only get KeyPress events for these, no KeyRelease
- // (unless we hack the mod state manually. Bleh.)
- //
- if (flags & NSAlphaShiftKeyMask) k = XK_Caps_Lock;
- else if (flags & NSShiftKeyMask) k = XK_Shift_L;
- else if (flags & NSControlKeyMask) k = XK_Control_L;
- else if (flags & NSAlternateKeyMask) k = XK_Alt_L;
- else if (flags & NSCommandKeyMask) k = XK_Meta_L;
- }
- else if ([ns length] == 1) // real key
- {
- switch ([ns characterAtIndex:0]) {
- case NSLeftArrowFunctionKey: k = XK_Left; break;
- case NSRightArrowFunctionKey: k = XK_Right; break;
- case NSUpArrowFunctionKey: k = XK_Up; break;
- case NSDownArrowFunctionKey: k = XK_Down; break;
- case NSPageUpFunctionKey: k = XK_Page_Up; break;
- case NSPageDownFunctionKey: k = XK_Page_Down; break;
- case NSHomeFunctionKey: k = XK_Home; break;
- case NSPrevFunctionKey: k = XK_Prior; break;
- case NSNextFunctionKey: k = XK_Next; break;
- case NSBeginFunctionKey: k = XK_Begin; break;
- case NSEndFunctionKey: k = XK_End; break;
- case NSF1FunctionKey: k = XK_F1; break;
- case NSF2FunctionKey: k = XK_F2; break;
- case NSF3FunctionKey: k = XK_F3; break;
- case NSF4FunctionKey: k = XK_F4; break;
- case NSF5FunctionKey: k = XK_F5; break;
- case NSF6FunctionKey: k = XK_F6; break;
- case NSF7FunctionKey: k = XK_F7; break;
- case NSF8FunctionKey: k = XK_F8; break;
- case NSF9FunctionKey: k = XK_F9; break;
- case NSF10FunctionKey: k = XK_F10; break;
- case NSF11FunctionKey: k = XK_F11; break;
- case NSF12FunctionKey: k = XK_F12; break;
- default:
- {
- const char *ss =
- [ns cStringUsingEncoding:NSISOLatin1StringEncoding];
- k = (ss && *ss ? *ss : 0);
- }
- break;
- }
- }
-
- if (! k) return YES; // E.g., "KeyRelease XK_Shift_L"
-
- xe.xkey.keycode = k;
- xe.xkey.state = state;
- break;
- }
- default:
- NSAssert1 (0, @"unknown X11 event type: %d", type);
- break;
- }
-
- return [self sendEvent: &xe];
-}
-
-
-- (void) mouseDown: (NSEvent *) e
-{
- if (! [self convertEvent:e type:ButtonPress])
- [super mouseDown:e];
-}
-
-- (void) mouseUp: (NSEvent *) e
-{
- if (! [self convertEvent:e type:ButtonRelease])
- [super mouseUp:e];
-}
-
-- (void) otherMouseDown: (NSEvent *) e
-{
- if (! [self convertEvent:e type:ButtonPress])
- [super otherMouseDown:e];
-}
-
-- (void) otherMouseUp: (NSEvent *) e
-{
- if (! [self convertEvent:e type:ButtonRelease])
- [super otherMouseUp:e];
-}
-
-- (void) mouseMoved: (NSEvent *) e
-{
- if (! [self convertEvent:e type:MotionNotify])
- [super mouseMoved:e];
-}
-
-- (void) mouseDragged: (NSEvent *) e
-{
- if (! [self convertEvent:e type:MotionNotify])
- [super mouseDragged:e];
-}
-
-- (void) otherMouseDragged: (NSEvent *) e
-{
- if (! [self convertEvent:e type:MotionNotify])
- [super otherMouseDragged:e];
-}
-
-- (void) scrollWheel: (NSEvent *) e
-{
- if (! [self convertEvent:e type:ButtonPress])
- [super scrollWheel:e];
-}
-
-- (void) keyDown: (NSEvent *) e
-{
- if (! [self convertEvent:e type:KeyPress])
- [super keyDown:e];
-}
-
-- (void) keyUp: (NSEvent *) e
-{
- if (! [self convertEvent:e type:KeyRelease])
- [super keyUp:e];
-}
-
-- (void) flagsChanged: (NSEvent *) e
-{
- if (! [self convertEvent:e type:KeyPress])
- [super flagsChanged:e];
-}
-
-
-- (NSOpenGLPixelFormat *) getGLPixelFormat
-{
- NSAssert (prefsReader, @"no prefsReader for getGLPixelFormat");
-
- NSOpenGLPixelFormatAttribute attrs[40];
- int i = 0;
- attrs[i++] = NSOpenGLPFAColorSize; attrs[i++] = 24;
-
-/* OpenGL's core profile removes a lot of the same stuff that was removed in
- OpenGL ES (e.g. glBegin, glDrawPixels), so it might be a possibility.
-
- opengl_core_p = True;
- if (opengl_core_p) {
- attrs[i++] = NSOpenGLPFAOpenGLProfile;
- attrs[i++] = NSOpenGLProfileVersion3_2Core;
- }
- */
-
-/* Eventually: multisampled pixmaps. May not be supported everywhere.
- if (multi_sample_p) {
- attrs[i++] = NSOpenGLPFASampleBuffers; attrs[i++] = 1;
- attrs[i++] = NSOpenGLPFASamples; attrs[i++] = 6;
- }
- */
-
-# ifdef JWXYZ_QUARTZ
- // Under Quartz, we're just blitting a texture.
- if (double_buffered_p)
- attrs[i++] = NSOpenGLPFADoubleBuffer;
-# endif
-
-# ifdef JWXYZ_GL
- /* Under OpenGL, all sorts of drawing commands are being issued, and it might
- be a performance problem if this activity occurs on the front buffer.
- Also, some screenhacks expect OS X/iOS to always double-buffer.
- NSOpenGLPFABackingStore prevents flickering with screenhacks that
- don't redraw the entire screen every frame.
- */
- attrs[i++] = NSOpenGLPFADoubleBuffer;
- attrs[i++] = NSOpenGLPFABackingStore;
-# endif
-
- attrs[i++] = NSOpenGLPFAWindow;
-# ifdef JWXYZ_GL
- attrs[i++] = NSOpenGLPFAPixelBuffer;
- /* ...But not NSOpenGLPFAFullScreen, because that would be for
- [NSOpenGLContext setFullScreen].
- */
-# endif
-
- /* NSOpenGLPFAFullScreen would go here if initWithFrame's isPreview == NO.
- */
-
- attrs[i] = 0;
-
- NSOpenGLPixelFormat *p = [[NSOpenGLPixelFormat alloc]
- initWithAttributes:attrs];
- [p autorelease];
- return p;
-}
-
-#else // USE_IPHONE
-
-
-- (void) stopAndClose
-{
- [self stopAndClose:NO];
-}
-
-
-- (void) stopAndClose:(Bool)relaunch_p
-{
- if ([self isAnimating])
- [self stopAnimation];
-
- /* Need to make the SaverListController be the firstResponder again
- so that it can continue to receive its own shake events. I
- suppose that this abstraction-breakage means that I'm adding
- XScreenSaverView to the UINavigationController wrong...
- */
-// UIViewController *v = [[self window] rootViewController];
-// if ([v isKindOfClass: [UINavigationController class]]) {
-// UINavigationController *n = (UINavigationController *) v;
-// [[n topViewController] becomeFirstResponder];
-// }
- [self resignFirstResponder];
-
- if (relaunch_p) { // Fake a shake on the SaverListController.
- [_delegate didShake:self];
- } else { // Not launching another, animate our return to the list.
-# if !defined __OPTIMIZE__ || TARGET_IPHONE_SIMULATOR
- NSLog (@"fading back to saver list");
-# endif
- [_delegate wantsFadeOut:self];
- }
-}
-
-
-/* We distinguish between taps and drags.
-
- - Drags/pans (down, motion, up) are sent to the saver to handle.
- - Single-taps are sent to the saver to handle.
- - Double-taps are sent to the saver as a "Space" keypress.
- - Swipes (really, two-finger drags/pans) send Up/Down/Left/RightArrow keys.
- - All taps expose the momentary "Close" button.
- */
-
-- (void)initGestures
-{
- UITapGestureRecognizer *dtap = [[UITapGestureRecognizer alloc]
- initWithTarget:self
- action:@selector(handleDoubleTap)];
- dtap.numberOfTapsRequired = 2;
- dtap.numberOfTouchesRequired = 1;
-
- UITapGestureRecognizer *stap = [[UITapGestureRecognizer alloc]
- initWithTarget:self
- action:@selector(handleTap:)];
- stap.numberOfTapsRequired = 1;
- stap.numberOfTouchesRequired = 1;
-
- UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]
- initWithTarget:self
- action:@selector(handlePan:)];
- pan.maximumNumberOfTouches = 1;
- pan.minimumNumberOfTouches = 1;
-
- // I couldn't get Swipe to work, but using a second Pan recognizer works.
- UIPanGestureRecognizer *pan2 = [[UIPanGestureRecognizer alloc]
- initWithTarget:self
- action:@selector(handlePan2:)];
- pan2.maximumNumberOfTouches = 2;
- pan2.minimumNumberOfTouches = 2;
-
- // Also handle long-touch, and treat that the same as Pan.
- // Without this, panning doesn't start until there's motion, so the trick
- // of holding down your finger to freeze the scene doesn't work.
- //
- UILongPressGestureRecognizer *hold = [[UILongPressGestureRecognizer alloc]
- initWithTarget:self
- action:@selector(handleLongPress:)];
- hold.numberOfTapsRequired = 0;
- hold.numberOfTouchesRequired = 1;
- hold.minimumPressDuration = 0.25; /* 1/4th second */
-
- // Two finger pinch to zoom in on the view.
- UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc]
- initWithTarget:self
- action:@selector(handlePinch:)];
-
- [stap requireGestureRecognizerToFail: dtap];
- [stap requireGestureRecognizerToFail: hold];
- [dtap requireGestureRecognizerToFail: hold];
- [pan requireGestureRecognizerToFail: hold];
- [pan2 requireGestureRecognizerToFail: pinch];
-
- [self setMultipleTouchEnabled:YES];
-
- [self addGestureRecognizer: dtap];
- [self addGestureRecognizer: stap];
- [self addGestureRecognizer: pan];
- [self addGestureRecognizer: pan2];
- [self addGestureRecognizer: hold];
- [self addGestureRecognizer: pinch];
-
- [dtap release];
- [stap release];
- [pan release];
- [pan2 release];
- [hold release];
- [pinch release];
-}
-
-
-/* Given a mouse (touch) coordinate in unrotated, unscaled view coordinates,
- convert it to what X11 and OpenGL expect.
-
- Getting this crap right is tricky, given the confusion of the various
- scale factors, so here's a checklist that I think covers all of the X11
- and OpenGL cases. For each of these: rotate to all 4 orientations;
- ensure the mouse tracks properly to all 4 corners.
-
- Test it in Xcode 6, because Xcode 5.0.2 can't run the iPhone6+ simulator.
-
- Test hacks must cover:
- X11 ignoreRotation = true
- X11 ignoreRotation = false
- OpenGL (rotation is handled manually, so they never ignoreRotation)
-
- Test devices must cover:
- contentScaleFactor = 1, hackedContentScaleFactor = 1 (iPad 2)
- contentScaleFactor = 2, hackedContentScaleFactor = 1 (iPad Retina Air)
- contentScaleFactor = 2, hackedContentScaleFactor = 2 (iPhone 5 5s 6 6+)
-
- iPad 2: 768x1024 / 1 = 768x1024
- iPad Air: 1536x2048 / 2 = 768x1024 (iPad Retina is identical)
- iPhone 4s: 640x960 / 2 = 320x480
- iPhone 5: 640x1136 / 2 = 320x568 (iPhone 5s and iPhone 6 are identical)
- iPhone 6+: 640x1136 / 2 = 320x568 (nativeBounds 960x1704 nativeScale 3)
-
- Tests:
- iPad2 iPadAir iPhone4s iPhone5 iPhone6+
- Attraction X yes - - - - Y
- Fireworkx X no - - - - Y
- Carousel GL yes - - - - Y
- Voronoi GL no - - - - -
- */
-- (void) convertMouse:(CGPoint *)p
-{
- CGFloat xx = p->x, yy = p->y;
-
-# if 0 // TARGET_IPHONE_SIMULATOR
- {
- XWindowAttributes xgwa;
- XGetWindowAttributes (xdpy, xwindow, &xgwa);
- NSLog (@"TOUCH %4g, %-4g in %4d x %-4d cs=%.0f hcs=%.0f r=%d ig=%d\n",
- p->x, p->y,
- xgwa.width, xgwa.height,
- [self contentScaleFactor],
- [self hackedContentScaleFactor],
- [self rotateTouches], [self ignoreRotation]);
- }
-# endif // TARGET_IPHONE_SIMULATOR
-
- if ([self rotateTouches]) {
-
- // The XScreenSaverGLView case:
- // The X11 window is rotated, as is the framebuffer.
- // The device coordinates match the framebuffer dimensions,
- // but might have axes swapped... and we need to swap them
- // by ratios.
- //
- int w = [self frame].size.width;
- int h = [self frame].size.height;
- GLfloat xr = (GLfloat) xx / w;
- GLfloat yr = (GLfloat) yy / h;
- GLfloat swap;
- int o = (int) current_device_rotation();
- switch (o) {
- case -90: case 270: swap = xr; xr = 1-yr; yr = swap; break;
- case 90: case -270: swap = xr; xr = yr; yr = 1-swap; break;
- case 180: case -180: xr = 1-xr; yr = 1-yr; break;
- default: break;
- }
- xx = xr * w;
- yy = yr * h;
-
- } else if ([self ignoreRotation]) {
-
- // The X11 case, where the hack has opted not to rotate:
- // The X11 window is unrotated, but the framebuffer is rotated.
- // The device coordinates match the framebuffer, so they need to
- // be de-rotated to match the X11 window.
- //
- int w = [self frame].size.width;
- int h = [self frame].size.height;
- int swap;
- int o = (int) current_device_rotation();
- switch (o) {
- case -90: case 270: swap = xx; xx = h-yy; yy = swap; break;
- case 90: case -270: swap = xx; xx = yy; yy = w-swap; break;
- case 180: case -180: xx = w-xx; yy = h-yy; break;
- default: break;
- }
- }
-
- double s = [self hackedContentScaleFactor];
- p->x = xx * s;
- p->y = yy * s;
-
-# if 0 // TARGET_IPHONE_SIMULATOR || !defined __OPTIMIZE__
- {
- XWindowAttributes xgwa;
- XGetWindowAttributes (xdpy, xwindow, &xgwa);
- NSLog (@"touch %4g, %-4g in %4d x %-4d cs=%.0f hcs=%.0f r=%d ig=%d\n",
- p->x, p->y,
- xgwa.width, xgwa.height,
- [self contentScaleFactor],
- [self hackedContentScaleFactor],
- [self rotateTouches], [self ignoreRotation]);
- if (p->x < 0 || p->y < 0 || p->x > xgwa.width || p->y > xgwa.height)
- abort();
- }
-# endif // TARGET_IPHONE_SIMULATOR
-}
-
-
-/* Single click exits saver.
- */
-- (void) handleTap:(UIGestureRecognizer *)sender
-{
- if (!xwindow)
- return;
-
- XEvent xe;
- memset (&xe, 0, sizeof(xe));
-
- [self showCloseButton];
-
- CGPoint p = [sender locationInView:self]; // this is in points, not pixels
- [self convertMouse:&p];
- NSAssert (xwindow->type == WINDOW, @"not a window");
- xwindow->window.last_mouse_x = p.x;
- xwindow->window.last_mouse_y = p.y;
-
- xe.xany.type = ButtonPress;
- xe.xbutton.button = 1;
- xe.xbutton.x = p.x;
- xe.xbutton.y = p.y;
-
- if (! [self sendEvent: &xe])
- ; //[self beep];
-
- xe.xany.type = ButtonRelease;
- xe.xbutton.button = 1;
- xe.xbutton.x = p.x;
- xe.xbutton.y = p.y;
-
- [self sendEvent: &xe];
-}
-
-
-/* Double click sends Space KeyPress.
- */
-- (void) handleDoubleTap
-{
- if (!xsft->event_cb || !xwindow) return;
-
- [self showCloseButton];
-
- XEvent xe;
- memset (&xe, 0, sizeof(xe));
- xe.xkey.keycode = ' ';
- xe.xany.type = KeyPress;
- BOOL ok1 = [self sendEvent: &xe];
- xe.xany.type = KeyRelease;
- BOOL ok2 = [self sendEvent: &xe];
- if (!(ok1 || ok2))
- [self beep];
-}
-
-
-/* Drag with one finger down: send MotionNotify.
- */
-- (void) handlePan:(UIGestureRecognizer *)sender
-{
- if (!xsft->event_cb || !xwindow) return;
-
- [self showCloseButton];
-
- XEvent xe;
- memset (&xe, 0, sizeof(xe));
-
- CGPoint p = [sender locationInView:self]; // this is in points, not pixels
- [self convertMouse:&p];
- NSAssert (xwindow && xwindow->type == WINDOW, @"not a window");
- xwindow->window.last_mouse_x = p.x;
- xwindow->window.last_mouse_y = p.y;
-
- switch (sender.state) {
- case UIGestureRecognizerStateBegan:
- xe.xany.type = ButtonPress;
- xe.xbutton.button = 1;
- xe.xbutton.x = p.x;
- xe.xbutton.y = p.y;
- break;
-
- case UIGestureRecognizerStateEnded:
- xe.xany.type = ButtonRelease;
- xe.xbutton.button = 1;
- xe.xbutton.x = p.x;
- xe.xbutton.y = p.y;
- break;
-
- case UIGestureRecognizerStateChanged:
- xe.xany.type = MotionNotify;
- xe.xmotion.x = p.x;
- xe.xmotion.y = p.y;
- break;
-
- default:
- break;
- }
-
- BOOL ok = [self sendEvent: &xe];
- if (!ok && xe.xany.type == ButtonRelease)
- [self beep];
-}
-
-
-/* Hold one finger down: assume we're about to start dragging.
- Treat the same as Pan.
- */
-- (void) handleLongPress:(UIGestureRecognizer *)sender
-{
- [self handlePan:sender];
-}
-
-
-
-/* Drag with 2 fingers down: send arrow keys.
- */
-- (void) handlePan2:(UIPanGestureRecognizer *)sender
-{
- if (!xsft->event_cb || !xwindow) return;
-
- [self showCloseButton];
-
- if (sender.state != UIGestureRecognizerStateEnded)
- return;
-
- XEvent xe;
- memset (&xe, 0, sizeof(xe));
-
- CGPoint p = [sender locationInView:self]; // this is in points, not pixels
- [self convertMouse:&p];
-
- if (fabs(p.x) > fabs(p.y))
- xe.xkey.keycode = (p.x > 0 ? XK_Right : XK_Left);
- else
- xe.xkey.keycode = (p.y > 0 ? XK_Down : XK_Up);
-
- BOOL ok1 = [self sendEvent: &xe];
- xe.xany.type = KeyRelease;
- BOOL ok2 = [self sendEvent: &xe];
- if (!(ok1 || ok2))
- [self beep];
-}
-
-
-/* Pinch with 2 fingers: zoom in around the center of the fingers.
- */
-- (void) handlePinch:(UIPinchGestureRecognizer *)sender
-{
- if (!xsft->event_cb || !xwindow) return;
-
- [self showCloseButton];
-
- if (sender.state == UIGestureRecognizerStateBegan)
- pinch_transform = self.transform; // Save the base transform
-
- switch (sender.state) {
- case UIGestureRecognizerStateBegan:
- case UIGestureRecognizerStateChanged:
- {
- double scale = sender.scale;
-
- if (scale < 1)
- return;
-
- self.transform = CGAffineTransformScale (pinch_transform, scale, scale);
-
- CGPoint p = [sender locationInView: self];
- p.x /= self.layer.bounds.size.width;
- p.y /= self.layer.bounds.size.height;
-
- CGPoint np = CGPointMake (self.bounds.size.width * p.x,
- self.bounds.size.height * p.y);
- CGPoint op = CGPointMake (self.bounds.size.width *
- self.layer.anchorPoint.x,
- self.bounds.size.height *
- self.layer.anchorPoint.y);
- np = CGPointApplyAffineTransform (np, self.transform);
- op = CGPointApplyAffineTransform (op, self.transform);
-
- CGPoint pos = self.layer.position;
- pos.x -= op.x;
- pos.x += np.x;
- pos.y -= op.y;
- pos.y += np.y;
- self.layer.position = pos;
- self.layer.anchorPoint = p;
- }
- break;
-
- case UIGestureRecognizerStateEnded:
- {
- // When released, snap back to the default zoom (but animate it).
-
- CABasicAnimation *a1 = [CABasicAnimation
- animationWithKeyPath:@"position.x"];
- a1.fromValue = [NSNumber numberWithFloat: self.layer.position.x];
- a1.toValue = [NSNumber numberWithFloat: self.bounds.size.width / 2];
-
- CABasicAnimation *a2 = [CABasicAnimation
- animationWithKeyPath:@"position.y"];
- a2.fromValue = [NSNumber numberWithFloat: self.layer.position.y];
- a2.toValue = [NSNumber numberWithFloat: self.bounds.size.height / 2];
-
- CABasicAnimation *a3 = [CABasicAnimation
- animationWithKeyPath:@"anchorPoint.x"];
- a3.fromValue = [NSNumber numberWithFloat: self.layer.anchorPoint.x];
- a3.toValue = [NSNumber numberWithFloat: 0.5];
-
- CABasicAnimation *a4 = [CABasicAnimation
- animationWithKeyPath:@"anchorPoint.y"];
- a4.fromValue = [NSNumber numberWithFloat: self.layer.anchorPoint.y];
- a4.toValue = [NSNumber numberWithFloat: 0.5];
-
- CABasicAnimation *a5 = [CABasicAnimation
- animationWithKeyPath:@"transform.scale"];
- a5.fromValue = [NSNumber numberWithFloat: sender.scale];
- a5.toValue = [NSNumber numberWithFloat: 1.0];
-
- CAAnimationGroup *group = [CAAnimationGroup animation];
- group.duration = 0.3;
- group.repeatCount = 1;
- group.autoreverses = NO;
- group.animations = @[ a1, a2, a3, a4, a5 ];
- group.timingFunction = [CAMediaTimingFunction
- functionWithName:
- kCAMediaTimingFunctionEaseIn];
- [self.layer addAnimation:group forKey:@"unpinch"];
-
- self.transform = pinch_transform;
- self.layer.anchorPoint = CGPointMake (0.5, 0.5);
- self.layer.position = CGPointMake (self.bounds.size.width / 2,
- self.bounds.size.height / 2);
- }
- break;
- default:
- abort();
- }
-}
-
-
-/* We need this to respond to "shake" gestures
- */
-- (BOOL)canBecomeFirstResponder
-{
- return YES;
-}
-
-- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
-{
-}
-
-
-- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event
-{
-}
-
-/* Shake means exit and launch a new saver.
- */
-- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
-{
- [self stopAndClose:YES];
-}
-
-
-- (void) showCloseButton
-{
- double iw = 24;
- double ih = iw;
- double off = 4;
-
- if (!closeBox) {
- int width = self.bounds.size.width;
- closeBox = [[UIView alloc]
- initWithFrame:CGRectMake(0, 0, width, ih + off)];
- closeBox.backgroundColor = [UIColor clearColor];
- closeBox.autoresizingMask =
- UIViewAutoresizingFlexibleBottomMargin |
- UIViewAutoresizingFlexibleWidth;
-
- // Add the buttons to the bar
- UIImage *img1 = [UIImage imageNamed:@"stop"];
- UIImage *img2 = [UIImage imageNamed:@"settings"];
-
- UIButton *button = [[UIButton alloc] init];
- [button setFrame: CGRectMake(off, off, iw, ih)];
- [button setBackgroundImage:img1 forState:UIControlStateNormal];
- [button addTarget:self
- action:@selector(stopAndClose)
- forControlEvents:UIControlEventTouchUpInside];
- [closeBox addSubview:button];
- [button release];
-
- button = [[UIButton alloc] init];
- [button setFrame: CGRectMake(width - iw - off, off, iw, ih)];
- [button setBackgroundImage:img2 forState:UIControlStateNormal];
- [button addTarget:self
- action:@selector(stopAndOpenSettings)
- forControlEvents:UIControlEventTouchUpInside];
- button.autoresizingMask =
- UIViewAutoresizingFlexibleBottomMargin |
- UIViewAutoresizingFlexibleLeftMargin;
- [closeBox addSubview:button];
- [button release];
-
- [self addSubview:closeBox];
- }
-
- // Don't hide the buttons under the iPhone X bezel.
- UIEdgeInsets is = { 0, };
- if ([self respondsToSelector:@selector(safeAreaInsets)]) {
-# pragma clang diagnostic push // "only available on iOS 11.0 or newer"
-# pragma clang diagnostic ignored "-Wunguarded-availability-new"
- is = [self safeAreaInsets];
-# pragma clang diagnostic pop
- [closeBox setFrame:CGRectMake(is.left, is.top,
- self.bounds.size.width - is.right - is.left,
- ih + off)];
- }
-
- if (closeBox.layer.opacity <= 0) { // Fade in
-
- CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"opacity"];
- anim.duration = 0.2;
- anim.repeatCount = 1;
- anim.autoreverses = NO;
- anim.fromValue = [NSNumber numberWithFloat:0.0];
- anim.toValue = [NSNumber numberWithFloat:1.0];
- [closeBox.layer addAnimation:anim forKey:@"animateOpacity"];
- closeBox.layer.opacity = 1;
- }
-
- // Fade out N seconds from now.
- if (closeBoxTimer)
- [closeBoxTimer invalidate];
- closeBoxTimer = [NSTimer scheduledTimerWithTimeInterval: 3
- target:self
- selector:@selector(closeBoxOff)
- userInfo:nil
- repeats:NO];
-}
-
-
-- (void)closeBoxOff
-{
- if (closeBoxTimer) {
- [closeBoxTimer invalidate];
- closeBoxTimer = 0;
- }
- if (!closeBox)
- return;
-
- CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"opacity"];
- anim.duration = 0.2;
- anim.repeatCount = 1;
- anim.autoreverses = NO;
- anim.fromValue = [NSNumber numberWithFloat: 1];
- anim.toValue = [NSNumber numberWithFloat: 0];
- [closeBox.layer addAnimation:anim forKey:@"animateOpacity"];
- closeBox.layer.opacity = 0;
-}
-
-
-- (void) stopAndOpenSettings
-{
- NSString *s = [NSString stringWithCString:xsft->progclass
- encoding:NSISOLatin1StringEncoding];
- if ([self isAnimating])
- [self stopAnimation];
- [self resignFirstResponder];
- [_delegate wantsFadeOut:self];
- [_delegate openPreferences: s];
-
-}
-
-
-- (void)setScreenLocked:(BOOL)locked
-{
- if (screenLocked == locked) return;
- screenLocked = locked;
- if (locked) {
- if ([self isAnimating])
- [self stopAnimation];
- } else {
- if (! [self isAnimating])
- [self startAnimation];
- }
-}
-
-- (NSDictionary *)getGLProperties
-{
- return [NSDictionary dictionaryWithObjectsAndKeys:
- kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat,
-# ifdef JWXYZ_GL
- /* This could be disabled if we knew the screen would be redrawn
- entirely for every frame.
- */
- [NSNumber numberWithBool:YES], kEAGLDrawablePropertyRetainedBacking,
-# endif // JWXYZ_GL
- nil];
-}
-
-- (void)addExtraRenderbuffers:(CGSize)size
-{
- // No extra renderbuffers are needed for 2D screenhacks.
-}
-
-
-- (NSString *)getCAGravity
-{
- return kCAGravityCenter; // Looks better in e.g. Compass.
-// return kCAGravityBottomLeft;
-}
-
-#endif // USE_IPHONE
-
-
-- (void) checkForUpdates
-{
-# ifndef USE_IPHONE
- // We only check once at startup, even if there are multiple screens,
- // and even if this saver is running for many days.
- // (Uh, except this doesn't work because this static isn't shared,
- // even if we make it an exported global. Not sure why. Oh well.)
- static BOOL checked_p = NO;
- if (checked_p) return;
- checked_p = YES;
-
- // If it's off, don't bother running the updater. Otherwise, the
- // updater will decide if it's time to hit the network.
- if (! get_boolean_resource (xdpy,
- SUSUEnableAutomaticChecksKey,
- SUSUEnableAutomaticChecksKey))
- return;
-
- NSString *updater = @"XScreenSaverUpdater.app";
-
- // There may be multiple copies of the updater: e.g., one in /Applications
- // and one in the mounted installer DMG! It's important that we run the
- // one from the disk and not the DMG, so search for the right one.
- //
- NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
- NSBundle *bundle = [NSBundle bundleForClass:[self class]];
- NSArray *search =
- @[[[bundle bundlePath] stringByDeletingLastPathComponent],
- [@"~/Library/Screen Savers" stringByExpandingTildeInPath],
- @"/Library/Screen Savers",
- @"/System/Library/Screen Savers",
- @"/Applications",
- @"/Applications/Utilities"];
- NSString *app_path = nil;
- for (NSString *dir in search) {
- NSString *p = [dir stringByAppendingPathComponent:updater];
- if ([[NSFileManager defaultManager] fileExistsAtPath:p]) {
- app_path = p;
- break;
- }
- }
-
- if (! app_path)
- app_path = [workspace fullPathForApplication:updater];
-
- if (app_path && [app_path hasPrefix:@"/Volumes/XScreenSaver "])
- app_path = 0; // The DMG version will not do.
-
- if (!app_path) {
- NSLog(@"Unable to find %@", updater);
- return;
- }
-
- NSError *err = nil;
- if (! [workspace launchApplicationAtURL:[NSURL fileURLWithPath:app_path]
- options:(NSWorkspaceLaunchWithoutAddingToRecents |
- NSWorkspaceLaunchWithoutActivation |
- NSWorkspaceLaunchAndHide)
- configuration:[NSMutableDictionary dictionary]
- error:&err]) {
- NSLog(@"Unable to launch %@: %@", app_path, err);
- }
-
-# endif // !USE_IPHONE
-}
-
-
-@end
-
-/* Utility functions...
- */
-
-static PrefsReader *
-get_prefsReader (Display *dpy)
-{
- XScreenSaverView *view = jwxyz_window_view (XRootWindow (dpy, 0));
- if (!view) return 0;
- return [view prefsReader];
-}
-
-
-char *
-get_string_resource (Display *dpy, char *name, char *class)
-{
- return [get_prefsReader(dpy) getStringResource:name];
-}
-
-Bool
-get_boolean_resource (Display *dpy, char *name, char *class)
-{
- return [get_prefsReader(dpy) getBooleanResource:name];
-}
-
-int
-get_integer_resource (Display *dpy, char *name, char *class)
-{
- return [get_prefsReader(dpy) getIntegerResource:name];
-}
-
-double
-get_float_resource (Display *dpy, char *name, char *class)
-{
- return [get_prefsReader(dpy) getFloatResource:name];
-}