X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?p=xscreensaver;a=blobdiff_plain;f=OSX%2FXScreenSaverView.m;h=a02a5067d3bf9da8515a5b24bcc457abb3263953;hp=438c05f9c712922a7c8ef8087477df1c508088c1;hb=019de959b265701cd0c3fccbb61f2b69f06bf9ee;hpb=6f5482d73adb0165c0130bb47d852644ab0c4869 diff --git a/OSX/XScreenSaverView.m b/OSX/XScreenSaverView.m index 438c05f9..a02a5067 100644 --- a/OSX/XScreenSaverView.m +++ b/OSX/XScreenSaverView.m @@ -1,13 +1,13 @@ -/* xscreensaver, Copyright (c) 2006-2012 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 -* 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. -*/ +/* xscreensaver, Copyright (c) 2006-2013 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 + * 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" @@ -16,12 +16,15 @@ */ #import +#import #import "XScreenSaverView.h" #import "XScreenSaverConfigSheet.h" +#import "Updater.h" #import "screenhackI.h" #import "xlockmoreI.h" #import "jwxyz-timers.h" + /* 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. */ @@ -44,6 +47,8 @@ int mono_p = 0; # ifdef USE_IPHONE +extern NSDictionary *make_function_table_dict(void); // ios-function-table.m + /* Stub definition of the superclass, for iPhone. */ @implementation ScreenSaverView @@ -92,7 +97,6 @@ int mono_p = 0; @interface XScreenSaverView (Private) -- (void) stopAndClose; - (void) stopAndClose:(Bool)relaunch; @end @@ -114,21 +118,35 @@ int mono_p = 0; 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]; - NSString *table_name = [[[name lowercaseString] - stringByReplacingOccurrencesOfString:@" " - withString:@""] - 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-table.m"! + 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; } @@ -204,6 +222,29 @@ add_default_options (const XrmOptionDescRec *opts, { "-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 + // + { "-" 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 [] = { @@ -217,7 +258,7 @@ add_default_options (const XrmOptionDescRec *opts, # endif // ".textLiteral: ", // ".textFile: ", - ".textURL: http://twitter.com/statuses/public_timeline.atom", + ".textURL: http://en.wikipedia.org/w/index.php?title=Special:NewPages&feed=rss", // ".textProgram: ", ".grabDesktopImages: yes", # ifndef USE_IPHONE @@ -227,6 +268,21 @@ add_default_options (const XrmOptionDescRec *opts, # endif ".imageDirectory: ~/Pictures", ".relaunchDelay: 2", + +# 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 }; @@ -304,6 +360,7 @@ double_time (void) isPreview:(BOOL)isPreview { # ifdef USE_IPHONE + initial_bounds = frame.size; rot_current_size = frame.size; // needs to be early, because rot_from = rot_current_size; // [self setFrame] is called by rot_to = rot_current_size; // [super initWithFrame]. @@ -354,9 +411,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 @@ -364,6 +424,17 @@ double_time (void) return self; } +- (void) initLayer +{ +# if !defined(USE_IPHONE) && defined(USE_CALAYER) + [self setLayer: [CALayer layer]]; + self.layer.delegate = self; + self.layer.opaque = YES; + [self setWantsLayer: YES]; +# endif // !USE_IPHONE && USE_CALAYER +} + + - (id) initWithFrame:(NSRect)frame isPreview:(BOOL)p { return [self initWithFrame:frame saverName:0 isPreview:p]; @@ -377,10 +448,19 @@ double_time (void) if (xdpy) jwxyz_free_display (xdpy); -# ifdef USE_IPHONE +# ifdef USE_BACKBUFFER if (backbuffer) CGContextRelease (backbuffer); -# endif + + if (colorspace) + CGColorSpaceRelease (colorspace); + +# ifndef USE_CALAYER + if (window_ctx) + CGContextRelease (window_ctx); +# endif // !USE_CALAYER + +# endif // USE_BACKBUFFER [prefsReader release]; @@ -443,6 +523,7 @@ double_time (void) selector:@selector(allSystemsGo:) userInfo:nil repeats:NO]; + # endif // USE_IPHONE // Never automatically turn the screen off if we are docked, @@ -451,6 +532,8 @@ double_time (void) # ifdef USE_IPHONE [UIApplication sharedApplication].idleTimerDisabled = ([UIDevice currentDevice].batteryState != UIDeviceBatteryStateUnplugged); + [[UIApplication sharedApplication] + setStatusBarHidden:YES withAnimation:UIStatusBarAnimationNone]; # endif } @@ -494,6 +577,8 @@ double_time (void) // # ifdef USE_IPHONE [UIApplication sharedApplication].idleTimerDisabled = NO; + [[UIApplication sharedApplication] + setStatusBarHidden:NO withAnimation:UIStatusBarAnimationNone]; # endif } @@ -518,48 +603,34 @@ 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.) + + This method is overridden in XScreenSaverGLView, since this kludge + isn't necessary for GL programs, being resolution independent by + nature. */ -- (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]; + if (initial_bounds.width >= 1024 || + initial_bounds.height >= 1024) + s = 1; + return s; } + static GLfloat _global_rot_current_angle_kludge; double current_device_rotation (void) @@ -614,18 +685,19 @@ double current_device_rotation (void) # undef CLAMP180 - double s = self.contentScaleFactor; - 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]]; + double s = [self hackedContentScaleFactor]; + if (!ignore_rotation_p && + /* rotation_ratio && */ + ((int) backbuffer_size.width != (int) (s * rot_current_size.width) || + (int) backbuffer_size.height != (int) (s * rot_current_size.height))) + [self resize_x11]; } - (void)alertView:(UIAlertView *)av clickedButtonAtIndex:(NSInteger)i { if (i == 0) exit (-1); // Cancel - [self stopAndClose]; // Keep going + [self stopAndClose:NO]; // Keep going } - (void) handleException: (NSException *)e @@ -651,6 +723,163 @@ double current_device_rotation (void) #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]; + CGSize rotsize = ignore_rotation_p ? initial_bounds : rot_current_size; + int new_w = s * rotsize.width; + int new_h = s * rotsize.height; +# else + int new_w = [self bounds].size.width; + int new_h = [self bounds].size.height; +# endif + + // Colorspaces and CGContexts only happen with non-GL hacks. + if (colorspace) + CGColorSpaceRelease (colorspace); +# ifndef USE_CALAYER + if (window_ctx) + CGContextRelease (window_ctx); +# endif + + NSWindow *window = [self window]; + + if (window && xdpy) { + [self lockFocus]; + +# ifndef USE_CALAYER + // TODO: This was borrowed from jwxyz_window_resized, and should + // probably be refactored. + + // Figure out which screen the window is currently on. + CGDirectDisplayID cgdpy = 0; + + { +// int wx, wy; +// TODO: XTranslateCoordinates is returning (0,1200) on my system. +// Is this right? +// In any case, those weren't valid coordinates for CGGetDisplaysWithPoint. +// XTranslateCoordinates (xdpy, xwindow, NULL, 0, 0, &wx, &wy, NULL); +// p.x = wx; +// p.y = wy; + + NSPoint p0 = {0, 0}; + p0 = [window convertBaseToScreen:p0]; + CGPoint p = {p0.x, p0.y}; + CGDisplayCount n; + CGGetDisplaysWithPoint (p, 1, &cgdpy, &n); + NSAssert (cgdpy, @"unable to find CGDisplay"); + } + + { + // Figure out this screen's colorspace, and use that for every CGImage. + // + CMProfileRef profile = 0; + + // CMGetProfileByAVID is deprecated as of OS X 10.6, but there's no + // documented replacement as of OS X 10.9. + // http://lists.apple.com/archives/colorsync-dev/2012/Nov/msg00001.html + CMGetProfileByAVID ((CMDisplayIDType) cgdpy, &profile); + NSAssert (profile, @"unable to find colorspace profile"); + colorspace = CGColorSpaceCreateWithPlatformColorSpace (profile); + NSAssert (colorspace, @"unable to find colorspace"); + } +# else // USE_CALAYER + // Was apparently faster until 10.9. + colorspace = CGColorSpaceCreateDeviceRGB (); +# endif // USE_CALAYER + +# ifndef USE_CALAYER + window_ctx = [[window graphicsContext] graphicsPort]; + CGContextRetain (window_ctx); +# endif // !USE_CALAYER + + [self unlockFocus]; + } else { +# ifndef USE_CALAYER + window_ctx = NULL; +# endif // !USE_CALAYER + colorspace = CGColorSpaceCreateDeviceRGB(); + } + + 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; + + backbuffer = CGBitmapContextCreate (NULL, + backbuffer_size.width, + backbuffer_size.height, + 8, + backbuffer_size.width * 4, + colorspace, + // kCGImageAlphaPremultipliedLast + (kCGImageAlphaNoneSkipFirst | + kCGBitmapByteOrder32Host) + ); + 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 @@ -664,7 +893,7 @@ 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 @@ -673,17 +902,12 @@ double current_device_rotation (void) 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 + /* Some X11 hacks (fluidballs) want to ignore all rotation events. */ + ignore_rotation_p = + get_boolean_resource (xdpy, "ignoreRotation", "IgnoreRotation"); +# endif // USE_IPHONE + + [self resize_x11]; } if (!setup_p) { @@ -694,7 +918,8 @@ double current_device_rotation (void) initted_p = YES; resized_p = NO; NSAssert(!xdata, @"xdata already initialized"); - + + # undef ya_rand_init ya_rand_init (0); @@ -730,7 +955,11 @@ double current_device_rotation (void) if (get_boolean_resource (xdpy, "doFPS", "DoFPS")) { fpst = fps_init (xdpy, xwindow); if (! xsft->fps_cb) xsft->fps_cb = screenhack_do_fps; + } else { + xsft->fps_cb = 0; } + + [self checkForUpdates]; } @@ -786,14 +1015,14 @@ 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; @@ -865,11 +1094,9 @@ double current_device_rotation (void) } -/* 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 { @@ -879,78 +1106,146 @@ double current_device_rotation (void) [super drawRect:rect]; // early: black. } + +#ifndef USE_BACKBUFFER + - (void) animateOneFrame { [self render_x11]; + jwxyz_flush_context(xdpy); } -#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. +# ifdef USE_IPHONE + UIGraphicsPushContext (backbuffer); +# endif - CGContextRef cgc = UIGraphicsGetCurrentContext(); + [self render_x11]; - // Mask it to only update the parts that are exposed. -// CGContextClipToRect (cgc, rect); +# ifdef USE_IPHONE + UIGraphicsPopContext(); +# endif - double s = self.contentScaleFactor; - CGRect frame = [self frame]; +# ifdef USE_IPHONE + // Then compute the transformations for rotation. + double hs = [self hackedContentScaleFactor]; + double s = [self contentScaleFactor]; + + // The rotation origin for layer.affineTransform is in the center already. + CGAffineTransform t = ignore_rotation_p ? + CGAffineTransformIdentity : + CGAffineTransformMakeRotation (rot_current_angle / (180.0 / M_PI)); + + CGFloat f = s / hs; + self.layer.affineTransform = CGAffineTransformScale(t, f, f); + + CGRect bounds; + bounds.origin.x = 0; + bounds.origin.y = 0; + bounds.size.width = backbuffer_size.width / s; + bounds.size.height = backbuffer_size.height / s; + self.layer.bounds = bounds; +# endif // USE_IPHONE + +# ifdef USE_CALAYER + [self.layer setNeedsDisplay]; +# else // !USE_CALAYER + size_t + w = CGBitmapContextGetWidth (backbuffer), + h = CGBitmapContextGetHeight (backbuffer); + + size_t bpl = CGBitmapContextGetBytesPerRow (backbuffer); + CGDataProviderRef prov = CGDataProviderCreateWithData (NULL, + CGBitmapContextGetData(backbuffer), + bpl * h, + NULL); + + + CGImageRef img = CGImageCreate (w, h, + 8, 32, + CGBitmapContextGetBytesPerRow(backbuffer), + colorspace, + CGBitmapContextGetBitmapInfo(backbuffer), + prov, NULL, NO, + kCGRenderingIntentDefault); + + CGDataProviderRelease (prov); + + CGRect rect; + rect.origin.x = 0; + rect.origin.y = 0; + rect.size = backbuffer_size; + CGContextDrawImage (window_ctx, rect, img); + + CGImageRelease (img); - 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; + CGContextFlush (window_ctx); +# endif // !USE_CALAYER +} - target.origin.x /= s; - target.origin.y /= s; - target.size.width /= s; - target.size.height /= s; +# ifdef USE_CALAYER - CGAffineTransform t = CGAffineTransformIdentity; +- (void) drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx +{ + // This "isn't safe" if NULL is passed to CGBitmapCreateContext before iOS 4. + char *dest_data = (char *)CGBitmapContextGetData (ctx); + + // The CGContext here is normally upside-down on iOS. + if (dest_data && + CGBitmapContextGetBitmapInfo (ctx) == + (kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host) +# ifdef USE_IPHONE + && CGContextGetCTM (ctx).d < 0 +# endif // USE_IPHONE + ) + { + size_t dest_height = CGBitmapContextGetHeight (ctx); + size_t dest_bpr = CGBitmapContextGetBytesPerRow (ctx); + size_t src_height = CGBitmapContextGetHeight (backbuffer); + size_t src_bpr = CGBitmapContextGetBytesPerRow (backbuffer); + char *src_data = (char *)CGBitmapContextGetData (backbuffer); - // 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); + size_t height = src_height < dest_height ? src_height : dest_height; + + if (src_bpr == dest_bpr) { + // iPad 1: 4.0 ms, iPad 2: 6.7 ms + memcpy (dest_data, src_data, src_bpr * height); + } else { + // iPad 1: 4.6 ms, iPad 2: 7.2 ms + size_t bpr = src_bpr < dest_bpr ? src_bpr : dest_bpr; + while (height) { + memcpy (dest_data, src_data, bpr); + --height; + src_data += src_bpr; + dest_data += dest_bpr; + } + } + } else { - // Flip Y axis - t = CGAffineTransformConcat (t, - CGAffineTransformMake ( 1, 0, 0, - -1, 0, frame.size.height)); + // iPad 1: 9.6 ms, iPad 2: 12.1 ms - // Clear background (visible in corners of screen during rotation) - if (rotation_ratio != -1) { - CGContextSetGrayFillColor (cgc, 0, 1); - CGContextFillRect (cgc, frame); +# ifdef USE_IPHONE + CGContextScaleCTM (ctx, 1, -1); + CGFloat s = [self contentScaleFactor]; + CGFloat hs = [self hackedContentScaleFactor]; + CGContextTranslateCTM (ctx, 0, -backbuffer_size.height * hs / s); +# endif // USE_IPHONE + + CGImageRef img = CGBitmapContextCreateImage (backbuffer); + CGContextDrawImage (ctx, self.layer.bounds, img); + CGImageRelease (img); } - - CGContextConcatCTM (cgc, t); - - // Copy the backbuffer to the screen. - // Note that CGContextDrawImage measures in "points", not "pixels". - CGImageRef img = CGBitmapContextCreateImage (backbuffer); - CGContextDrawImage (cgc, target, img); - CGImageRelease (img); } +# endif // USE_CALAYER -- (void) animateOneFrame -{ - [self setNeedsDisplay]; -} - -#endif // !USE_IPHONE +#endif // USE_BACKBUFFER @@ -958,26 +1253,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]; } @@ -985,13 +1262,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 @@ -1006,6 +1278,40 @@ double current_device_rotation (void) return YES; } ++ (NSString *) decompressXML: (NSData *)data +{ + if (! data) return 0; + BOOL compressed_p = !!strncmp ((const char *) data.bytes, "")); + } + + return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; +} + + #ifndef USE_IPHONE - (NSWindow *) configureSheet #else @@ -1029,14 +1335,18 @@ double current_device_rotation (void) NSWindow *sheet; # endif // !USE_IPHONE + NSData *xmld = [NSData dataWithContentsOfFile:path]; + NSString *xml = [[self class] decompressXML: xmld]; sheet = [[XScreenSaverConfigSheet alloc] - initWithXMLFile:path - options:xsft->options - controller:[prefsReader userDefaultsController] - defaults:[prefsReader defaultOptions]]; + 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; @@ -1050,7 +1360,7 @@ double current_device_rotation (void) /* Announce our willingness to accept keyboard input. -*/ + */ - (BOOL)acceptsFirstResponder { return YES; @@ -1085,12 +1395,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) { @@ -1156,12 +1466,14 @@ 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: - NSAssert (0, @"unknown X11 event type: %d", type); + NSAssert1 (0, @"unknown X11 event type: %d", type); break; } @@ -1242,7 +1554,7 @@ double current_device_rotation (void) #else // USE_IPHONE -- (void) stopAndClose +- (void) stopAndClose:(Bool)relaunch_p { if ([self isAnimating]) [self stopAnimation]; @@ -1258,27 +1570,24 @@ double current_device_rotation (void) [[n topViewController] becomeFirstResponder]; } - // [self removeFromSuperview]; - [UIView animateWithDuration: 0.5 - animations:^{ self.alpha = 0.0; } - completion:^(BOOL finished) { - [self removeFromSuperview]; - self.alpha = 1.0; - }]; -} - - -- (void) stopAndClose:(Bool)relaunch_p -{ - [self stopAndClose]; + UIView *fader = [self superview]; // the "backgroundView" view is our parent if (relaunch_p) { // Fake a shake on the SaverListController. - UIViewController *v = [[self window] rootViewController]; + // Why is [self window] sometimes null here? + UIWindow *w = [[UIApplication sharedApplication] keyWindow]; + UIViewController *v = [w rootViewController]; if ([v isKindOfClass: [UINavigationController class]]) { UINavigationController *n = (UINavigationController *) v; [[n topViewController] motionEnded: UIEventSubtypeMotionShake withEvent: nil]; } + } else { // Not launching another, animate our return to the list. + [UIView animateWithDuration: 0.5 + animations:^{ fader.alpha = 0.0; } + completion:^(BOOL finished) { + [fader removeFromSuperview]; + fader.alpha = 1.0; + }]; } } @@ -1299,7 +1608,7 @@ double current_device_rotation (void) Possibly XScreenSaverView should use Core Animation, and XScreenSaverGLView should override that. -*/ + */ - (void)didRotate:(NSNotification *)notification { UIDeviceOrientation current = [[UIDevice currentDevice] orientation]; @@ -1363,29 +1672,27 @@ double current_device_rotation (void) default: angle_to = 0; break; } - NSRect ff = [self frame]; - switch (orientation) { case UIDeviceOrientationLandscapeRight: // from landscape case UIDeviceOrientationLandscapeLeft: - rot_from.width = ff.size.height; - rot_from.height = ff.size.width; + rot_from.width = initial_bounds.height; + rot_from.height = initial_bounds.width; break; default: // from portrait - rot_from.width = ff.size.width; - rot_from.height = ff.size.height; + rot_from.width = initial_bounds.width; + rot_from.height = initial_bounds.height; break; } switch (new_orientation) { case UIDeviceOrientationLandscapeRight: // to landscape case UIDeviceOrientationLandscapeLeft: - rot_to.width = ff.size.height; - rot_to.height = ff.size.width; + rot_to.width = initial_bounds.height; + rot_to.height = initial_bounds.width; break; default: // to portrait - rot_to.width = ff.size.width; - rot_to.height = ff.size.height; + rot_to.width = initial_bounds.width; + rot_to.height = initial_bounds.height; break; } @@ -1402,6 +1709,9 @@ double current_device_rotation (void) 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] @@ -1426,13 +1736,14 @@ 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 { - 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; } + // This is a no-op unless contentScaleFactor != hackedContentScaleFactor. + // Currently, this is the iPad Retina only. + CGRect frame = [self bounds]; // Scale. + double s = [self hackedContentScaleFactor]; + *x *= (backbuffer_size.width / frame.size.width) / s; + *y *= (backbuffer_size.height / frame.size.height) / s; } @@ -1457,13 +1768,18 @@ rotate_mouse (int *x, int *y, int w, int h, int rot) - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { + // If they are trying to pinch, just do nothing. + if ([[event allTouches] count] > 1) + return; + 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) { @@ -1473,7 +1789,8 @@ 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. @@ -1492,11 +1809,16 @@ rotate_mouse (int *x, int *y, int w, int h, int rot) - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { + // If they are trying to pinch, just do nothing. + if ([[event allTouches] count] > 1) + 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) { @@ -1509,7 +1831,7 @@ rotate_mouse (int *x, int *y, int w, int h, int rot) 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]; + [self stopAndClose:NO]; return; } @@ -1517,7 +1839,8 @@ 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); xsft->event_cb (xdpy, xwindow, xdata, &xe); i++; @@ -1529,11 +1852,16 @@ rotate_mouse (int *x, int *y, int w, int h, int rot) - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { + // If they are trying to pinch, just do nothing. + if ([[event allTouches] count] > 1) + 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) { @@ -1541,7 +1869,8 @@ 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++; @@ -1588,10 +1917,34 @@ rotate_mouse (int *x, int *y, int w, int h, int rot) } } - #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 (! get_boolean_resource (xdpy, + SUSUEnableAutomaticChecksKey, + SUSUEnableAutomaticChecksKey)) + return; // If it's off, don't bother running the updater. + + // Otherwise, the updater will decide if it's time to hit the network. + NSString *updater = @"XScreenSaverUpdater"; + if (! [[NSWorkspace sharedWorkspace] + launchApplication:updater showIcon:NO autolaunch:NO]) { + NSLog(@"Unable to launch %@", updater); + } +# endif // USE_IPHONE +} + + @end /* Utility functions...