X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?p=xscreensaver;a=blobdiff_plain;f=OSX%2FXScreenSaverView.m;h=947525dde25b52bfcc64ea4c2600663367c6d174;hp=18f4a1aa1270591b72210a9d1bce3ab1d23e06fa;hb=4361b69d3178d7fc98d0388f9a223af6c2651aba;hpb=d6b0217f2417bd19187f0ebc389d6c5c2233b11c diff --git a/OSX/XScreenSaverView.m b/OSX/XScreenSaverView.m index 18f4a1aa..947525dd 100644 --- a/OSX/XScreenSaverView.m +++ b/OSX/XScreenSaverView.m @@ -1,4 +1,4 @@ -/* xscreensaver, Copyright (c) 2006-2016 Jamie Zawinski +/* xscreensaver, Copyright (c) 2006-2017 Jamie Zawinski * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that @@ -23,6 +23,7 @@ #import "Updater.h" #import "screenhackI.h" #import "xlockmoreI.h" +#import "pow2.h" #import "jwxyzI.h" #import "jwxyz-cocoa.h" #import "jwxyz-timers.h" @@ -118,6 +119,7 @@ extern NSDictionary *make_function_table_dict(void); // ios-function-table.m @interface XScreenSaverView (Private) +- (void) stopAndClose; - (void) stopAndClose:(Bool)relaunch; @end @@ -603,8 +605,6 @@ add_default_options (const XrmOptionDescRec *opts, # ifdef USE_IPHONE [UIApplication sharedApplication].idleTimerDisabled = ([UIDevice currentDevice].batteryState != UIDeviceBatteryStateUnplugged); - [[UIApplication sharedApplication] - setStatusBarHidden:YES withAnimation:UIStatusBarAnimationNone]; # endif xwindow = (Window) calloc (1, sizeof(*xwindow)); @@ -739,13 +739,14 @@ add_default_options (const XrmOptionDescRec *opts, [self lockFocus]; // in case something tries to draw from here [self prepareContext]; - /* I considered just not even calling the free callback at all... - But webcollage-cocoa needs it, to kill the inferior webcollage + /* 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...) Instead, I turned off the free call in - xlockmore.c, which is where all of the bogus calls are anyway. + SIGPIPE for them). */ - xsft->free_cb (xdpy, xwindow, xdata); + if (xdata) + xsft->free_cb (xdpy, xwindow, xdata); [self unlockFocus]; jwxyz_free_display (xdpy); @@ -778,8 +779,6 @@ add_default_options (const XrmOptionDescRec *opts, // # ifdef USE_IPHONE [UIApplication sharedApplication].idleTimerDisabled = NO; - [[UIApplication sharedApplication] - setStatusBarHidden:NO withAnimation:UIStatusBarAnimationNone]; # endif // Without this, the GL frame stays on screen when switching tabs @@ -905,7 +904,8 @@ current_device_rotation (void) the opposite direction." */ /* statusBarOrientation deprecated in iOS 9 */ - o = [UIApplication sharedApplication].statusBarOrientation; + o = (UIDeviceOrientation) // from UIInterfaceOrientation + [UIApplication sharedApplication].statusBarOrientation; } switch (o) { @@ -917,29 +917,37 @@ current_device_rotation (void) } -- (void)alertView:(UIAlertView *)av clickedButtonAtIndex:(NSInteger)i -{ - if (i == 0) exit (-1); // Cancel - [self stopAndClose:NO]; // Keep going -} - - (void) handleException: (NSException *)e { NSLog (@"Caught exception: %@", e); - [[[UIAlertView alloc] initWithTitle: - [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] - delegate: self - cancelButtonTitle: @"Exit" - otherButtonTitles: @"Keep going", nil] - show]; + 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]; } @@ -1064,31 +1072,6 @@ gl_check_ver (const struct gl_version *caps, } -static GLsizei -to_pow2 (size_t x) -{ - if (x <= 1) - return 1; - - size_t mask = (size_t)-1; - unsigned bits = sizeof(x) * CHAR_BIT; - unsigned log2 = bits; - - --x; - while (bits) { - if (!(x & mask)) { - log2 -= bits; - x <<= bits; - } - - bits >>= 1; - mask <<= bits; - } - - return 1 << log2; -} - - #ifdef USE_IPHONE - (BOOL) suppressRotationAnimation { @@ -1163,7 +1146,7 @@ to_pow2 (size_t x) CGContextRef ob = backbuffer; void *odata = backbuffer_data; - size_t olen = backbuffer_len; + GLsizei olen = backbuffer_len; # if !defined __OPTIMIZE__ || TARGET_IPHONE_SIMULATOR NSLog(@"backbuffer %.0fx%.0f", @@ -1211,11 +1194,11 @@ to_pow2 (size_t x) if (!gl_limited_npot_p) # endif { - gl_texture_w = to_pow2 (gl_texture_w); - gl_texture_h = to_pow2 (gl_texture_h); + gl_texture_w = (GLsizei) to_pow2 (gl_texture_w); + gl_texture_h = (GLsizei) to_pow2 (gl_texture_h); } - size_t bytes_per_row = gl_texture_w * 4; + 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 @@ -1623,10 +1606,11 @@ to_pow2 (size_t x) if (get_boolean_resource (xdpy, "doFPS", "DoFPS")) { fpst = fps_init (xdpy, xwindow); - if (! xsft->fps_cb) xsft->fps_cb = screenhack_do_fps; + fps_cb = xsft->fps_cb; + if (! fps_cb) fps_cb = screenhack_do_fps; } else { fpst = NULL; - xsft->fps_cb = 0; + fps_cb = 0; } # ifdef USE_IPHONE @@ -1727,8 +1711,8 @@ to_pow2 (size_t x) // NSAssert(xdata, @"no xdata when drawing"); if (! xdata) abort(); unsigned long delay = xsft->draw_cb (xdpy, xwindow, xdata); - if (fpst && xsft->fps_cb) - xsft->fps_cb (xdpy, xwindow, fpst, xdata); + if (fpst && fps_cb) + fps_cb (xdpy, xwindow, fpst, xdata); gettimeofday (&tv, 0); now = tv.tv_sec + (tv.tv_usec / 1000000.0); @@ -2021,7 +2005,7 @@ to_pow2 (size_t x) [e deltaX] < 0 ? Button7 : 0); else - xe.xbutton.button = [e buttonNumber] + 1; + xe.xbutton.button = (unsigned int) [e buttonNumber] + 1; break; case MotionNotify: xe.xmotion.x = x; @@ -2075,9 +2059,9 @@ to_pow2 (size_t x) case NSF12FunctionKey: k = XK_F12; break; default: { - const char *s = + const char *ss = [ns cStringUsingEncoding:NSISOLatin1StringEncoding]; - k = (s && *s ? *s : 0); + k = (ss && *ss ? *ss : 0); } break; } @@ -2229,6 +2213,12 @@ to_pow2 (size_t x) #else // USE_IPHONE +- (void) stopAndClose +{ + [self stopAndClose:NO]; +} + + - (void) stopAndClose:(Bool)relaunch_p { if ([self isAnimating]) @@ -2260,11 +2250,10 @@ to_pow2 (size_t x) /* We distinguish between taps and drags. - Drags/pans (down, motion, up) are sent to the saver to handle. - - Single-taps exit the saver. + - 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. - - This means a saver cannot respond to a single-tap. Only a few try to. + - All taps expose the momentary "Close" button. */ - (void)initGestures @@ -2277,7 +2266,7 @@ to_pow2 (size_t x) UITapGestureRecognizer *stap = [[UITapGestureRecognizer alloc] initWithTarget:self - action:@selector(handleTap)]; + action:@selector(handleTap:)]; stap.numberOfTapsRequired = 1; stap.numberOfTouchesRequired = 1; @@ -2305,10 +2294,16 @@ to_pow2 (size_t x) 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]; @@ -2317,12 +2312,14 @@ to_pow2 (size_t x) [self addGestureRecognizer: pan]; [self addGestureRecognizer: pan2]; [self addGestureRecognizer: hold]; + [self addGestureRecognizer: pinch]; [dtap release]; [stap release]; [pan release]; [pan2 release]; [hold release]; + [pinch release]; } @@ -2363,7 +2360,7 @@ to_pow2 (size_t x) { CGFloat xx = p->x, yy = p->y; -# if TARGET_IPHONE_SIMULATOR +# if 0 // TARGET_IPHONE_SIMULATOR { XWindowAttributes xgwa; XGetWindowAttributes (xdpy, xwindow, &xgwa); @@ -2422,7 +2419,7 @@ to_pow2 (size_t x) p->x = xx * s; p->y = yy * s; -# if TARGET_IPHONE_SIMULATOR || !defined __OPTIMIZE__ +# if 0 // TARGET_IPHONE_SIMULATOR || !defined __OPTIMIZE__ { XWindowAttributes xgwa; XGetWindowAttributes (xdpy, xwindow, &xgwa); @@ -2441,9 +2438,36 @@ to_pow2 (size_t x) /* Single click exits saver. */ -- (void) handleTap +- (void) handleTap:(UIGestureRecognizer *)sender { - [self stopAndClose:NO]; + 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]; } @@ -2453,6 +2477,8 @@ to_pow2 (size_t x) { if (!xsft->event_cb || !xwindow) return; + [self showCloseButton]; + XEvent xe; memset (&xe, 0, sizeof(xe)); xe.xkey.keycode = ' '; @@ -2471,6 +2497,8 @@ to_pow2 (size_t x) { if (!xsft->event_cb || !xwindow) return; + [self showCloseButton]; + XEvent xe; memset (&xe, 0, sizeof(xe)); @@ -2527,6 +2555,8 @@ to_pow2 (size_t x) { if (!xsft->event_cb || !xwindow) return; + [self showCloseButton]; + if (sender.state != UIGestureRecognizerStateEnded) return; @@ -2549,6 +2579,102 @@ to_pow2 (size_t x) } +/* 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 @@ -2573,6 +2699,105 @@ to_pow2 (size_t x) } +- (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]; + } + + 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;