+- (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
+}
+