X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=OSX%2FXScreenSaverView.m;h=a0dc3813200b738f86d24d2c2aea71251d283c9a;hb=b81f521c5ad7022ac12db18ca8fcdd9fb063831e;hp=55c5516b99c9a36d1ca72f2565beb94e2c20d8b5;hpb=c70f94f648d51bb4828193124f325fa52b0e57f3;p=xscreensaver diff --git a/OSX/XScreenSaverView.m b/OSX/XScreenSaverView.m index 55c5516b..a0dc3813 100644 --- a/OSX/XScreenSaverView.m +++ b/OSX/XScreenSaverView.m @@ -22,6 +22,12 @@ #import "xlockmoreI.h" #import "jwxyz-timers.h" +#ifdef USE_IPHONE +# include "ios_function_tables.h" +static NSDictionary *function_tables = 0; +#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. */ @@ -91,6 +97,11 @@ int mono_p = 0; +@interface XScreenSaverView (Private) +- (void) stopAndClose; +- (void) stopAndClose:(Bool)relaunch; +@end + @implementation XScreenSaverView // Given a lower-cased saver name, returns the function table for it. @@ -113,15 +124,30 @@ int mono_p = 0; if (! name) name = [[path lastPathComponent] stringByDeletingPathExtension]; - NSString *table_name = [[name lowercaseString] - stringByAppendingString: - @"_xscreensaver_function_table"]; + 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 + // Remember: any time you add a new saver to the iOS app, + // manually run "make ios_function_tables.h"! + if (! function_tables) + function_tables = [make_function_tables_dict() retain]; + NSValue *v = [function_tables objectForKey: name]; + void *addr = v ? [v pointerValue] : 0; +# endif // USE_IPHONE + return (struct xscreensaver_function_table *) addr; } @@ -146,7 +172,7 @@ int mono_p = 0; strcat (npath, opath); if (putenv (npath)) { perror ("putenv"); - abort(); + NSAssert1 (0, @"putenv \"%s\" failed", npath); } /* Don't free (npath) -- MacOS's putenv() does not copy it. */ @@ -167,7 +193,7 @@ int mono_p = 0; strcat (env, s); if (putenv (env)) { perror ("putenv"); - abort(); + NSAssert1 (0, @"putenv \"%s\" failed", env); } /* Don't free (env) -- MacOS's putenv() does not copy it. */ } @@ -213,7 +239,11 @@ add_default_options (const XrmOptionDescRec *opts, ".textURL: http://twitter.com/statuses/public_timeline.atom", // ".textProgram: ", ".grabDesktopImages: yes", +# ifndef USE_IPHONE ".chooseRandomImages: no", +# else + ".chooseRandomImages: yes", +# endif ".imageDirectory: ~/Pictures", ".relaunchDelay: 2", 0 @@ -325,7 +355,7 @@ double_time (void) "org.jwz.xscreensaver...plist" */ NSString *name = [NSString stringWithCString:xsft->progclass - encoding:NSUTF8StringEncoding]; + encoding:NSISOLatin1StringEncoding]; name = [@"org.jwz.xscreensaver." stringByAppendingString:name]; [self setResourcesEnv:name]; @@ -343,9 +373,12 @@ double_time (void) next_frame_time = 0; -# ifdef USE_IPHONE +# ifdef USE_BACKBUFFER [self createBackbuffer]; + [self initLayer]; +# endif +# ifdef USE_IPHONE // So we can tell when we're docked. [UIDevice currentDevice].batteryMonitoringEnabled = YES; # endif // USE_IPHONE @@ -353,6 +386,15 @@ double_time (void) return self; } +- (void) initLayer +{ +# ifndef USE_IPHONE + [self setLayer: [CALayer layer]]; + [self setWantsLayer: YES]; +# endif +} + + - (id) initWithFrame:(NSRect)frame isPreview:(BOOL)p { return [self initWithFrame:frame saverName:0 isPreview:p]; @@ -366,7 +408,7 @@ double_time (void) if (xdpy) jwxyz_free_display (xdpy); -# ifdef USE_IPHONE +# ifdef USE_BACKBUFFER if (backbuffer) CGContextRelease (backbuffer); # endif @@ -375,7 +417,6 @@ double_time (void) // xsft // fpst - // orientation_timer [super dealloc]; } @@ -508,48 +549,31 @@ screenhack_do_fps (Display *dpy, Window w, fps_state *fpst, void *closure) fps_draw (fpst); } + #ifdef USE_IPHONE -/* Create a bitmap context into which we render everything. +/* On iPhones with Retina displays, we can draw the savers in "real" + pixels, and that works great. The 320x480 "point" screen is really + a 640x960 *pixel* screen. However, Retina iPads have 768x1024 + point screens which are 1536x2048 pixels, and apparently that's + enough pixels that copying those bits to the screen is slow. Like, + drops us from 15fps to 7fps. So, on Retina iPads, we don't draw in + real pixels. This will probably make the savers look better + anyway, since that's a higher resolution than most desktop monitors + have even today. (This is only true for X11 programs, not GL + programs. Those are fine at full rez.) */ -- (void) createBackbuffer +- (CGFloat) hackedContentScaleFactor { - CGContextRef ob = backbuffer; - NSSize osize = backbuffer_size; - - CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB(); - double s = self.contentScaleFactor; - backbuffer_size.width = (int) (s * rot_current_size.width); - backbuffer_size.height = (int) (s * rot_current_size.height); - backbuffer = CGBitmapContextCreate (NULL, - backbuffer_size.width, - backbuffer_size.height, - 8, - backbuffer_size.width * 4, - cs, - kCGImageAlphaPremultipliedLast); - NSAssert (backbuffer, @"unable to allocate back buffer"); - CGColorSpaceRelease (cs); - - // Clear it. - CGContextSetGrayFillColor (backbuffer, 0, 1); - CGRect r = CGRectZero; - r.size = backbuffer_size; - CGContextFillRect (backbuffer, r); - - if (ob) { - // Restore old bits, as much as possible, to the X11 upper left origin. - NSRect rect; - rect.origin.x = 0; - rect.origin.y = (backbuffer_size.height - osize.height); - rect.size = osize; - CGImageRef img = CGBitmapContextCreateImage (ob); - CGContextDrawImage (backbuffer, rect, img); - CGImageRelease (img); - CGContextRelease (ob); - } + GLfloat s = [self contentScaleFactor]; + CGRect frame = [self bounds]; + if (frame.size.width >= 1024 || + frame.size.height >= 1024) + s = 1; + return s; } + static GLfloat _global_rot_current_angle_kludge; double current_device_rotation (void) @@ -604,19 +628,135 @@ double current_device_rotation (void) # undef CLAMP180 - double s = self.contentScaleFactor; + double s = [self hackedContentScaleFactor]; if (((int) backbuffer_size.width != (int) (s * rot_current_size.width) || (int) backbuffer_size.height != (int) (s * rot_current_size.height)) /* && rotation_ratio == -1*/) - [self setFrame:[self frame]]; + [self resize_x11]; +} + + +- (void)alertView:(UIAlertView *)av clickedButtonAtIndex:(NSInteger)i +{ + if (i == 0) exit (-1); // Cancel + [self stopAndClose]; // 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]; + [self stopAnimation]; } #endif // USE_IPHONE +#ifdef USE_BACKBUFFER + +/* Create a bitmap context into which we render everything. + If the desired size has changed, re-created it. + */ +- (void) createBackbuffer +{ +# ifdef USE_IPHONE + double s = [self hackedContentScaleFactor]; + int new_w = s * rot_current_size.width; + int new_h = s * rot_current_size.height; +# else + int new_w = [self bounds].size.width; + int new_h = [self bounds].size.height; +# endif + + if (backbuffer && + backbuffer_size.width == new_w && + backbuffer_size.height == new_h) + return; + + CGSize osize = backbuffer_size; + CGContextRef ob = backbuffer; + + backbuffer_size.width = new_w; + backbuffer_size.height = new_h; + + CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB(); + backbuffer = CGBitmapContextCreate (NULL, + backbuffer_size.width, + backbuffer_size.height, + 8, + backbuffer_size.width * 4, + cs, + kCGImageAlphaPremultipliedLast); + CGColorSpaceRelease (cs); + NSAssert (backbuffer, @"unable to allocate back buffer"); + + // Clear it. + CGRect r; + r.origin.x = r.origin.y = 0; + r.size = backbuffer_size; + CGContextSetGrayFillColor (backbuffer, 0, 1); + CGContextFillRect (backbuffer, r); + + if (ob) { + // Restore old bits, as much as possible, to the X11 upper left origin. + CGRect rect; + rect.origin.x = 0; + rect.origin.y = (backbuffer_size.height - osize.height); + rect.size = osize; + CGImageRef img = CGBitmapContextCreateImage (ob); + CGContextDrawImage (backbuffer, rect, img); + CGImageRelease (img); + CGContextRelease (ob); + } +} + +#endif // USE_BACKBUFFER + + +/* Inform X11 that the size of our window has changed. + */ +- (void) resize_x11 +{ + if (!xwindow) return; // early + +# ifdef USE_BACKBUFFER + [self createBackbuffer]; + jwxyz_window_resized (xdpy, xwindow, + 0, 0, + backbuffer_size.width, backbuffer_size.height, + backbuffer); +# else // !USE_BACKBUFFER + NSRect r = [self frame]; // ignoring rotation is closer + r.size = [self bounds].size; // to what XGetGeometry expects. + jwxyz_window_resized (xdpy, xwindow, + r.origin.x, r.origin.y, + r.size.width, r.size.height, + 0); +# endif // !USE_BACKBUFFER + + // Next time render_x11 is called, run the saver's reshape_cb. + resized_p = YES; +} + + - (void) render_x11 { # ifdef USE_IPHONE + @try { + if (orientation == UIDeviceOrientationUnknown) [self didRotate:nil]; [self hackRotation]; @@ -625,26 +765,14 @@ double current_device_rotation (void) if (!initted_p) { if (! xdpy) { -# ifdef USE_IPHONE +# ifdef USE_BACKBUFFER NSAssert (backbuffer, @"no back buffer"); xdpy = jwxyz_make_display (self, backbuffer); # else xdpy = jwxyz_make_display (self, 0); # endif xwindow = XRootWindow (xdpy, 0); - -# ifdef USE_IPHONE - jwxyz_window_resized (xdpy, xwindow, - 0, 0, - backbuffer_size.width, backbuffer_size.height, - backbuffer); -# else - NSRect r = [self frame]; - jwxyz_window_resized (xdpy, xwindow, - r.origin.x, r.origin.y, - r.size.width, r.size.height, - 0); -# endif + [self resize_x11]; } if (!setup_p) { @@ -747,20 +875,19 @@ double current_device_rotation (void) // Xlib drawing takes place under the animation timer. [self resizeContext]; NSRect r; -# ifndef USE_IPHONE - r = [self frame]; -# else // USE_IPHONE +# ifndef USE_BACKBUFFER + r = [self bounds]; +# else // USE_BACKBUFFER r.origin.x = 0; r.origin.y = 0; r.size.width = backbuffer_size.width; r.size.height = backbuffer_size.height; -# endif // USE_IPHONE +# endif // USE_BACKBUFFER xsft->reshape_cb (xdpy, xwindow, xdata, r.size.width, r.size.height); resized_p = NO; } - // Run any XtAppAddInput callbacks now. // (Note that XtAppAddTimeOut callbacks have already been run by // the Cocoa event loop.) @@ -817,14 +944,19 @@ double current_device_rotation (void) } } # endif // DO_GC_HACKERY + +# ifdef USE_IPHONE + } + @catch (NSException *e) { + [self handleException: e]; + } +# endif // USE_IPHONE } -/* On MacOS: drawRect does nothing, and animateOneFrame renders. - On iOS GL: drawRect does nothing, and animateOneFrame renders. - On iOS X11: drawRect renders, and animateOneFrame marks the view dirty. +/* drawRect always does nothing, and animateOneFrame renders bits to the + screen. This is (now) true of both X11 and GL on both MacOS and iOS. */ -#ifndef USE_IPHONE - (void)drawRect:(NSRect)rect { @@ -834,78 +966,58 @@ double current_device_rotation (void) [super drawRect:rect]; // early: black. } + +#ifndef USE_BACKBUFFER + - (void) animateOneFrame { [self render_x11]; } -#else // USE_IPHONE +#else // USE_BACKBUFFER -- (void)drawRect:(NSRect)rect +- (void) animateOneFrame { // Render X11 into the backing store bitmap... NSAssert (backbuffer, @"no back buffer"); - UIGraphicsPushContext (backbuffer); - [self render_x11]; - UIGraphicsPopContext(); - - // Then copy that bitmap to the screen. - - CGContextRef cgc = UIGraphicsGetCurrentContext(); - - // Mask it to only update the parts that are exposed. -// CGContextClipToRect (cgc, rect); - double s = self.contentScaleFactor; - CGRect frame = [self frame]; +# ifdef USE_IPHONE + UIGraphicsPushContext (backbuffer); +# endif - NSRect target; - target.size.width = backbuffer_size.width; - target.size.height = backbuffer_size.height; - target.origin.x = (s * frame.size.width - target.size.width) / 2; - target.origin.y = (s * frame.size.height - target.size.height) / 2; + [self render_x11]; - target.origin.x /= s; - target.origin.y /= s; - target.size.width /= s; - target.size.height /= s; +# ifdef USE_IPHONE + UIGraphicsPopContext(); +# endif - CGAffineTransform t = CGAffineTransformIdentity; +# ifdef USE_IPHONE + // Then compute the transformations for rotation. - // Rotate around center - float cx = frame.size.width / 2; - float cy = frame.size.height / 2; - t = CGAffineTransformTranslate (t, cx, cy); - t = CGAffineTransformRotate (t, -rot_current_angle / (180.0 / M_PI)); - t = CGAffineTransformTranslate (t, -cx, -cy); + // The rotation origin for layer.affineTransform is in the center already. + CGAffineTransform t = + CGAffineTransformMakeRotation (rot_current_angle / (180.0 / M_PI)); - // Flip Y axis - t = CGAffineTransformConcat (t, - CGAffineTransformMake ( 1, 0, 0, - -1, 0, frame.size.height)); + // Correct the aspect ratio. + CGRect frame = [self bounds]; + double s = [self hackedContentScaleFactor]; + t = CGAffineTransformScale(t, + backbuffer_size.width / (s * frame.size.width), + backbuffer_size.height / (s * frame.size.height)); - // Clear background (visible in corners of screen during rotation) - if (rotation_ratio != -1) { - CGContextSetGrayFillColor (cgc, 0, 1); - CGContextFillRect (cgc, frame); - } + self.layer.affineTransform = t; +# endif // USE_IPHONE - CGContextConcatCTM (cgc, t); + // Then copy that bitmap to the screen, by just stuffing it into + // the layer. The superclass drawRect method will handle the rest. - // Copy the backbuffer to the screen. - // Note that CGContextDrawImage measures in "points", not "pixels". CGImageRef img = CGBitmapContextCreateImage (backbuffer); - CGContextDrawImage (cgc, target, img); + self.layer.contents = (id)img; CGImageRelease (img); } -- (void) animateOneFrame -{ - [self setNeedsDisplay]; -} - -#endif // !USE_IPHONE +#endif // !USE_BACKBUFFER @@ -913,26 +1025,8 @@ double current_device_rotation (void) { [super setFrame:newRect]; -# ifdef USE_IPHONE - [self createBackbuffer]; -# endif - - resized_p = YES; // The reshape_cb runs in render_x11 - if (xwindow) { // inform Xlib that the window has changed now. -# ifdef USE_IPHONE - NSAssert (backbuffer, @"no back buffer"); - // The backbuffer is the rotated size, and so is the xwindow. - jwxyz_window_resized (xdpy, xwindow, - 0, 0, - backbuffer_size.width, backbuffer_size.height, - backbuffer); -# else - jwxyz_window_resized (xdpy, xwindow, - newRect.origin.x, newRect.origin.y, - newRect.size.width, newRect.size.height, - 0); -# endif - } + if (xwindow) // inform Xlib that the window has changed now. + [self resize_x11]; } @@ -940,13 +1034,8 @@ double current_device_rotation (void) - (void) setFrameSize:(NSSize) newSize { [super setFrameSize:newSize]; - resized_p = YES; if (xwindow) - jwxyz_window_resized (xdpy, xwindow, - [self frame].origin.x, - [self frame].origin.y, - newSize.width, newSize.height, - 0); // backbuffer only on iPhone + [self resize_x11]; } # endif // !USE_IPHONE @@ -969,7 +1058,7 @@ double current_device_rotation (void) { NSBundle *bundle = [NSBundle bundleForClass:[self class]]; NSString *file = [NSString stringWithCString:xsft->progclass - encoding:NSUTF8StringEncoding]; + encoding:NSISOLatin1StringEncoding]; file = [file lowercaseString]; NSString *path = [bundle pathForResource:file ofType:@"xml"]; if (!path) { @@ -1040,12 +1129,12 @@ double current_device_rotation (void) NSPoint p = [[[e window] contentView] convertPoint:[e locationInWindow] toView:self]; # ifdef USE_IPHONE - double s = self.contentScaleFactor; + double s = [self hackedContentScaleFactor]; # else int s = 1; # endif int x = s * p.x; - int y = s * ([self frame].size.height - p.y); + int y = s * ([self bounds].size.height - p.y); xe.xany.type = type; switch (type) { @@ -1111,12 +1200,15 @@ double current_device_rotation (void) } } + if (! k) return YES; // E.g., "KeyRelease XK_Shift_L" + xe.xkey.keycode = k; xe.xkey.state = state; break; } default: - abort(); + NSAssert (0, @"unknown X11 event type: %d", type); + break; } [self lockFocus]; @@ -1196,6 +1288,47 @@ double current_device_rotation (void) #else // USE_IPHONE +- (void) stopAndClose +{ + 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]; + } + + UIView *fader = [self superview]; // the "backgroundView" view is our parent + [UIView animateWithDuration: 0.5 + animations:^{ fader.alpha = 0.0; } + completion:^(BOOL finished) { + [fader removeFromSuperview]; + fader.alpha = 1.0; + }]; +} + + +- (void) stopAndClose:(Bool)relaunch_p +{ + [self stopAndClose]; + + if (relaunch_p) { // Fake a shake on the SaverListController. + UIViewController *v = [[self window] rootViewController]; + if ([v isKindOfClass: [UINavigationController class]]) { + UINavigationController *n = (UINavigationController *) v; + [[n topViewController] motionEnded: UIEventSubtypeMotionShake + withEvent: nil]; + } + } +} + + /* Called after the device's orientation has changed. Note: we could include a subclass of UIViewController which @@ -1276,7 +1409,7 @@ double current_device_rotation (void) default: angle_to = 0; break; } - NSRect ff = [self frame]; + NSRect ff = [self bounds]; switch (orientation) { case UIDeviceOrientationLandscapeRight: // from landscape @@ -1311,7 +1444,14 @@ double current_device_rotation (void) } -/* In the simulator, multi-touch sequences look like this: +/* I believe we can't use UIGestureRecognizer for tracking touches + because UIPanGestureRecognizer doesn't give us enough detail in its + callbacks. + + Currently we don't handle multi-touches (just the first touch) but + I'm leaving this comment here for future reference: + + In the simulator, multi-touch sequences look like this: touchesBegan [touchA, touchB] touchesEnd [touchA, touchB] @@ -1335,23 +1475,44 @@ double current_device_rotation (void) number of touchEnds matches the number of touchBegins. */ -static void -rotate_mouse (int *x, int *y, int w, int h, int rot) +- (void) rotateMouse:(int)rot x:(int*)x y:(int *)y w:(int)w h:(int)h +{ + CGRect frame = [self bounds]; // Correct aspect ratio and scale. + double s = [self hackedContentScaleFactor]; + *x *= (backbuffer_size.width / frame.size.width) / s; + *y *= (backbuffer_size.height / frame.size.height) / s; +} + + +#if 0 // AudioToolbox/AudioToolbox.h +- (void) beep { - int ox = *x, oy = *y; - if (rot > 45 && rot < 135) { *x = oy; *y = w-ox; } - else if (rot < -45 && rot > -135) { *x = h-oy; *y = ox; } - else if (rot > 135 || rot < -135) { *x = w-ox; *y = h-oy; } + // There's no way to play a standard system alert sound! + // We'd have to include our own WAV for that. Eh, fuck it. + AudioServicesPlaySystemSound (kSystemSoundID_Vibrate); +# if TARGET_IPHONE_SIMULATOR + NSLog(@"BEEP"); // The sim doesn't vibrate. +# endif } +#endif + +/* We distinguish between taps and drags. + - Drags (down, motion, up) are sent to the saver to handle. + - Single-taps exit the saver. + This means a saver cannot respond to a single-tap. Only a few try to. + */ - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { + tap_time = 0; + if (xsft->event_cb && xwindow) { - double s = self.contentScaleFactor; + double s = [self hackedContentScaleFactor]; XEvent xe; memset (&xe, 0, sizeof(xe)); int i = 0; + // #### 'frame' here or 'bounds'? int w = s * [self frame].size.width; int h = s * [self frame].size.height; for (UITouch *touch in touches) { @@ -1361,10 +1522,19 @@ rotate_mouse (int *x, int *y, int w, int h, int rot) xe.xbutton.button = i + 1; xe.xbutton.x = s * p.x; xe.xbutton.y = s * p.y; - rotate_mouse (&xe.xbutton.x, &xe.xbutton.y, w, h, rot_current_angle); + [self rotateMouse: rot_current_angle + x: &xe.xbutton.x y: &xe.xbutton.y w: w h: h]; jwxyz_mouse_moved (xdpy, xwindow, xe.xbutton.x, xe.xbutton.y); + + // Ignore return code: don't care whether the hack handled it. xsft->event_cb (xdpy, xwindow, xdata, &xe); + + // Remember when/where this was, to determine tap versus drag or hold. + tap_time = double_time(); + tap_point = p; + i++; + break; // No pinches: only look at the first touch. } } } @@ -1372,35 +1542,38 @@ rotate_mouse (int *x, int *y, int w, int h, int rot) - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { - - // Double-tap means "exit" and return to selection menu. - // - for (UITouch *touch in touches) { - if ([touch tapCount] >= 2) { - if ([self isAnimating]) - [self stopAnimation]; - [self removeFromSuperview]; - return; - } - } - if (xsft->event_cb && xwindow) { - double s = self.contentScaleFactor; + double s = [self hackedContentScaleFactor]; XEvent xe; memset (&xe, 0, sizeof(xe)); int i = 0; + // #### 'frame' here or 'bounds'? int w = s * [self frame].size.width; int h = s * [self frame].size.height; for (UITouch *touch in touches) { CGPoint p = [touch locationInView:self]; + + // If the ButtonRelease came less than half a second after ButtonPress, + // and didn't move far, then this was a tap, not a drag or a hold. + // Interpret it as "exit". + // + double dist = sqrt (((p.x - tap_point.x) * (p.x - tap_point.x)) + + ((p.y - tap_point.y) * (p.y - tap_point.y))); + if (tap_time + 0.5 >= double_time() && dist < 20) { + [self stopAndClose]; + return; + } + xe.xany.type = ButtonRelease; xe.xbutton.button = i + 1; xe.xbutton.x = s * p.x; xe.xbutton.y = s * p.y; - rotate_mouse (&xe.xbutton.x, &xe.xbutton.y, w, h, rot_current_angle); + [self rotateMouse: rot_current_angle + x: &xe.xbutton.x y: &xe.xbutton.y w: w h: h]; jwxyz_mouse_moved (xdpy, xwindow, xe.xbutton.x, xe.xbutton.y); xsft->event_cb (xdpy, xwindow, xdata, &xe); i++; + break; // No pinches: only look at the first touch. } } } @@ -1409,10 +1582,11 @@ rotate_mouse (int *x, int *y, int w, int h, int rot) - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { if (xsft->event_cb && xwindow) { - double s = self.contentScaleFactor; + double s = [self hackedContentScaleFactor]; XEvent xe; memset (&xe, 0, sizeof(xe)); int i = 0; + // #### 'frame' here or 'bounds'? int w = s * [self frame].size.width; int h = s * [self frame].size.height; for (UITouch *touch in touches) { @@ -1420,10 +1594,12 @@ rotate_mouse (int *x, int *y, int w, int h, int rot) xe.xany.type = MotionNotify; xe.xmotion.x = s * p.x; xe.xmotion.y = s * p.y; - rotate_mouse (&xe.xbutton.x, &xe.xbutton.y, w, h, rot_current_angle); + [self rotateMouse: rot_current_angle + x: &xe.xbutton.x y: &xe.xbutton.y w: w h: h]; jwxyz_mouse_moved (xdpy, xwindow, xe.xmotion.x, xe.xmotion.y); xsft->event_cb (xdpy, xwindow, xdata, &xe); i++; + break; // No pinches: only look at the first touch. } } } @@ -1431,10 +1607,27 @@ rotate_mouse (int *x, int *y, int w, int h, int rot) /* We need this to respond to "shake" gestures */ -- (BOOL)canBecomeFirstResponder { +- (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)setScreenLocked:(BOOL)locked { @@ -1462,7 +1655,7 @@ static PrefsReader * get_prefsReader (Display *dpy) { XScreenSaverView *view = jwxyz_window_view (XRootWindow (dpy, 0)); - if (!view) abort(); + if (!view) return 0; return [view prefsReader]; }