-/* xscreensaver, Copyright (c) 2006-2011 Jamie Zawinski <jwz@jwz.org>
+/* xscreensaver, Copyright (c) 2006-2012 Jamie Zawinski <jwz@jwz.org>
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
the "screenhack.c" module.
*/
+#import <QuartzCore/QuartzCore.h>
#import "XScreenSaverView.h"
#import "XScreenSaverConfigSheet.h"
#import "screenhackI.h"
int mono_p = 0;
+# ifdef USE_IPHONE
+
+/* Stub definition of the superclass, for iPhone.
+ */
+@implementation ScreenSaverView
+{
+ NSTimeInterval anim_interval;
+ Bool animating_p;
+ NSTimer *anim_timer;
+}
+
+- (id)initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview {
+ self = [super initWithFrame:frame];
+ if (! self) return 0;
+ anim_interval = 1.0/30;
+ return self;
+}
+- (NSTimeInterval)animationTimeInterval { return anim_interval; }
+- (void)setAnimationTimeInterval:(NSTimeInterval)i { anim_interval = i; }
+- (BOOL)hasConfigureSheet { return NO; }
+- (NSWindow *)configureSheet { return nil; }
+- (NSView *)configureView { return nil; }
+- (BOOL)isPreview { return NO; }
+- (BOOL)isAnimating { return animating_p; }
+- (void)animateOneFrame { }
+
+- (void)startAnimation {
+ if (animating_p) return;
+ animating_p = YES;
+ anim_timer = [NSTimer scheduledTimerWithTimeInterval: anim_interval
+ target:self
+ selector:@selector(animateOneFrame)
+ userInfo:nil
+ repeats:YES];
+}
+
+- (void)stopAnimation {
+ if (anim_timer) {
+ [anim_timer invalidate];
+ anim_timer = 0;
+ }
+ animating_p = NO;
+}
+@end
+
+# endif // !USE_IPHONE
+
+
+
@implementation XScreenSaverView
-- (struct xscreensaver_function_table *) findFunctionTable
+// Given a lower-cased saver name, returns the function table for it.
+// If no name, guess the name from the class's bundle name.
+//
+- (struct xscreensaver_function_table *) findFunctionTable:(NSString *)name
{
NSBundle *nsb = [NSBundle bundleForClass:[self class]];
NSAssert1 (nsb, @"no bundle for class %@", [self class]);
-
+
NSString *path = [nsb bundlePath];
- NSString *name = [[[path lastPathComponent] stringByDeletingPathExtension]
- lowercaseString];
- NSString *suffix = @"_xscreensaver_function_table";
- NSString *table_name = [name stringByAppendingString:suffix];
-
CFURLRef url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
(CFStringRef) path,
kCFURLPOSIXPathStyle,
CFRelease (url);
NSAssert1 (cfb, @"no CFBundle for \"%@\"", path);
+ if (! name)
+ name = [[path lastPathComponent] stringByDeletingPathExtension];
+
+ NSString *table_name = [[name lowercaseString]
+ stringByAppendingString:
+ @"_xscreensaver_function_table"];
void *addr = CFBundleGetDataPointerForName (cfb, (CFStringRef) table_name);
- NSAssert2 (addr, @"no symbol \"%@\" in bundle %@", table_name, path);
CFRelease (cfb);
-// NSLog (@"%@ = 0x%08X", table_name, (unsigned long) addr);
+ if (! addr)
+ NSLog (@"no symbol \"%@\" for \"%@\"", table_name, path);
+
return (struct xscreensaver_function_table *) addr;
}
/* These aren't "real" command-line options (there are no actual command-line
options in the Cocoa version); but this is the somewhat kludgey way that
the <xscreensaver-text /> and <xscreensaver-image /> tags in the
- ../hacks/config/*.xml files communicate with the preferences database.
+ ../hacks/config/\*.xml files communicate with the preferences database.
*/
static const XrmOptionDescRec default_options [] = {
{ "-text-mode", ".textMode", XrmoptionSepArg, 0 },
".doFPS: False",
".doubleBuffer: True",
".multiSample: False",
+# ifndef USE_IPHONE
".textMode: date",
+# else
+ ".textMode: url",
+# endif
// ".textLiteral: ",
// ".textFile: ",
".textURL: http://twitter.com/statuses/public_timeline.atom",
".grabDesktopImages: yes",
".chooseRandomImages: no",
".imageDirectory: ~/Pictures",
+ ".relaunchDelay: 2",
0
};
}
-- (id) initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview
+#ifdef USE_IPHONE
+/* Returns the current time in seconds as a double.
+ */
+static double
+double_time (void)
+{
+ struct timeval now;
+# ifdef GETTIMEOFDAY_TWO_ARGS
+ struct timezone tzp;
+ gettimeofday(&now, &tzp);
+# else
+ gettimeofday(&now);
+# endif
+
+ return (now.tv_sec + ((double) now.tv_usec * 0.000001));
+}
+#endif // USE_IPHONE
+
+
+- (id) initWithFrame:(NSRect)frame
+ saverName:(NSString *)saverName
+ isPreview:(BOOL)isPreview
{
+# ifdef USE_IPHONE
+ 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].
+ rotation_ratio = -1;
+# endif
+
if (! (self = [super initWithFrame:frame isPreview:isPreview]))
return 0;
- xsft = [self findFunctionTable];
+ xsft = [self findFunctionTable: saverName];
+ if (! xsft) {
+ [self release];
+ return 0;
+ }
+
[self setShellPath];
+# ifdef USE_IPHONE
+ [self setMultipleTouchEnabled:YES];
+ orientation = UIDeviceOrientationUnknown;
+ [self didRotate:nil];
+# endif // USE_IPHONE
+
setup_p = YES;
if (xsft->setup_cb)
xsft->setup_cb (xsft, xsft->setup_arg);
next_frame_time = 0;
+# ifdef USE_IPHONE
+ [self createBackbuffer];
+
+ // So we can tell when we're docked.
+ [UIDevice currentDevice].batteryMonitoringEnabled = YES;
+# endif // USE_IPHONE
+
return self;
}
+- (id) initWithFrame:(NSRect)frame isPreview:(BOOL)p
+{
+ return [self initWithFrame:frame saverName:0 isPreview:p];
+}
+
+
- (void) dealloc
{
NSAssert(![self isAnimating], @"still animating");
NSAssert(!xdata, @"xdata not yet freed");
if (xdpy)
jwxyz_free_display (xdpy);
+
+# ifdef USE_IPHONE
+ if (backbuffer)
+ CGContextRelease (backbuffer);
+# endif
+
[prefsReader release];
+
+ // xsft
+ // fpst
+ // orientation_timer
+
[super dealloc];
}
}
+#ifdef USE_IPHONE
+- (void) lockFocus { }
+- (void) unlockFocus { }
+#endif // USE_IPHONE
+
+
+
+# ifdef USE_IPHONE
+/* A few seconds after the saver launches, we store the "wasRunning"
+ preference. This is so that if the saver is crashing at startup,
+ we don't launch it again next time, getting stuck in a crash loop.
+ */
+- (void) allSystemsGo: (NSTimer *) timer
+{
+ NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
+ [prefs setBool:YES forKey:@"wasRunning"];
+ NSAssert (timer == crash_timer, @"crash timer screwed up");
+ crash_timer = 0;
+}
+#endif // USE_IPHONE
+
+
- (void) startAnimation
{
NSAssert(![self isAnimating], @"already animating");
initialization of the screen saver (xsft->init_cb) in the first call
to animateOneFrame() instead.
*/
+
+# ifdef USE_IPHONE
+ if (crash_timer)
+ [crash_timer invalidate];
+ crash_timer = [NSTimer scheduledTimerWithTimeInterval: 5
+ target:self
+ selector:@selector(allSystemsGo:)
+ userInfo:nil
+ repeats:NO];
+# endif // USE_IPHONE
+
+ // Never automatically turn the screen off if we are docked,
+ // and an animation is running.
+ //
+# ifdef USE_IPHONE
+ [UIApplication sharedApplication].idleTimerDisabled =
+ ([UIDevice currentDevice].batteryState != UIDeviceBatteryStateUnplugged);
+# endif
}
+
- (void)stopAnimation
{
NSAssert([self isAnimating], @"not animating");
xdata = 0;
}
+# ifdef USE_IPHONE
+ if (crash_timer)
+ [crash_timer invalidate];
+ crash_timer = 0;
+ NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
+ [prefs removeObjectForKey:@"wasRunning"];
+# endif // USE_IPHONE
+
[super stopAnimation];
+
+ // When an animation is no longer running (e.g., looking at the list)
+ // then it's ok to power off the screen when docked.
+ //
+# ifdef USE_IPHONE
+ [UIApplication sharedApplication].idleTimerDisabled = NO;
+# endif
}
fps_draw (fpst);
}
+#ifdef USE_IPHONE
-- (void) animateOneFrame
+/* Create a bitmap context into which we render everything.
+ */
+- (void) createBackbuffer
{
+ 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);
+ }
+}
+
+static GLfloat _global_rot_current_angle_kludge;
+
+double current_device_rotation (void)
+{
+ return -_global_rot_current_angle_kludge;
+}
+
+
+- (void) hackRotation
+{
+ if (rotation_ratio >= 0) { // in the midst of a rotation animation
+
+# define CLAMP180(N) while (N < 0) N += 360; while (N > 180) N -= 360
+ GLfloat f = angle_from;
+ GLfloat t = angle_to;
+ CLAMP180(f);
+ CLAMP180(t);
+ GLfloat dist = -(t-f);
+ CLAMP180(dist);
+
+ // Intermediate angle.
+ rot_current_angle = f - rotation_ratio * dist;
+
+ // Intermediate frame size.
+ rot_current_size.width = rot_from.width +
+ rotation_ratio * (rot_to.width - rot_from.width);
+ rot_current_size.height = rot_from.height +
+ rotation_ratio * (rot_to.height - rot_from.height);
+
+ // Tick animation. Complete rotation in 1/6th sec.
+ double now = double_time();
+ double duration = 1/6.0;
+ rotation_ratio = 1 - ((rot_start_time + duration - now) / duration);
+
+ if (rotation_ratio > 1) { // Done animating.
+ orientation = new_orientation;
+ rot_current_angle = angle_to;
+ rot_current_size = rot_to;
+ rotation_ratio = -1;
+
+ // Check orientation again in case we rotated again while rotating:
+ // this is a no-op if nothing has changed.
+ [self didRotate:nil];
+ }
+ } else { // Not animating a rotation.
+ rot_current_angle = angle_to;
+ rot_current_size = rot_to;
+ }
+
+ CLAMP180(rot_current_angle);
+ _global_rot_current_angle_kludge = rot_current_angle;
+
+# 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]];
+}
+
+#endif // USE_IPHONE
+
+
+- (void) render_x11
+{
+# ifdef USE_IPHONE
+ if (orientation == UIDeviceOrientationUnknown)
+ [self didRotate:nil];
+ [self hackRotation];
+# endif
+
if (!initted_p) {
if (! xdpy) {
- xdpy = jwxyz_make_display (self);
+# ifdef USE_IPHONE
+ 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
}
if (!setup_p) {
"background", "Background"));
XClearWindow (xdpy, xwindow);
+# ifndef USE_IPHONE
[[self window] setAcceptsMouseMovedEvents:YES];
+# endif
/* In MacOS 10.5, this enables "QuartzGL", meaning that the Quartz
drawing primitives will run on the GPU instead of the CPU.
}
}
+
/* I don't understand why we have to do this *every frame*, but we do,
or else the cursor comes back on.
*/
+# ifndef USE_IPHONE
if (![self isPreview])
[NSCursor setHiddenUntilMouseMoves:YES];
+# endif
if (fpst)
/* This is just a guess, but the -fps code wants to know how long
we were sleeping between frames.
*/
- unsigned long usecs = 1000000 * [self animationTimeInterval];
+ long usecs = 1000000 * [self animationTimeInterval];
usecs -= 200; // caller apparently sleeps for slightly less sometimes...
+ if (usecs < 0) usecs = 0;
fps_slept (fpst, usecs);
}
[self prepareContext];
if (resized_p) {
- // We do this here instead of in setFrameSize so that all the
+ // We do this here instead of in setFrame so that all the
// Xlib drawing takes place under the animation timer.
[self resizeContext];
- NSRect r = [self frame];
+ NSRect r;
+# ifndef USE_IPHONE
+ r = [self frame];
+# else // USE_IPHONE
+ r.origin.x = 0;
+ r.origin.y = 0;
+ r.size.width = backbuffer_size.width;
+ r.size.height = backbuffer_size.height;
+# endif // USE_IPHONE
+
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.)
//
jwxyz_sources_run (display_sources_data (xdpy));
+
// And finally:
//
+# ifndef USE_IPHONE
NSDisableScreenUpdates();
+# endif
unsigned long delay = xsft->draw_cb (xdpy, xwindow, xdata);
if (fpst) xsft->fps_cb (xdpy, xwindow, fpst, xdata);
- XSync (xdpy, 0);
+# ifndef USE_IPHONE
NSEnableScreenUpdates();
+# endif
gettimeofday (&tv, 0);
now = tv.tv_sec + (tv.tv_usec / 1000000.0);
next_frame_time = now + (delay / 1000000.0);
+# ifdef USE_IPHONE // Allow savers on the iPhone to run full-tilt.
+ if (delay < [self animationTimeInterval])
+ [self setAnimationTimeInterval:(delay / 1000000.0)];
+# endif
# ifdef DO_GC_HACKERY
/* Current theory is that the 10.6 garbage collector sucks in the
}
+/* 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.
+ */
+#ifndef USE_IPHONE
+
- (void)drawRect:(NSRect)rect
{
if (xwindow) // clear to the X window's bg color, not necessarily black.
[super drawRect:rect]; // early: black.
}
+- (void) animateOneFrame
+{
+ [self render_x11];
+}
-- (void) setFrameSize:(NSSize) newSize
+#else // USE_IPHONE
+
+- (void)drawRect:(NSRect)rect
{
- [super setFrameSize:newSize];
- if ([self isAnimating]) {
- resized_p = YES;
+ // 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];
+
+ 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;
+
+ target.origin.x /= s;
+ target.origin.y /= s;
+ target.size.width /= s;
+ target.size.height /= s;
+
+ CGAffineTransform t = CGAffineTransformIdentity;
+
+ // 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);
+
+ // Flip Y axis
+ t = CGAffineTransformConcat (t,
+ CGAffineTransformMake ( 1, 0, 0,
+ -1, 0, frame.size.height));
+
+ // Clear background (visible in corners of screen during rotation)
+ if (rotation_ratio != -1) {
+ CGContextSetGrayFillColor (cgc, 0, 1);
+ CGContextFillRect (cgc, frame);
}
+
+ 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);
+}
+
+- (void) animateOneFrame
+{
+ [self setNeedsDisplay];
}
+#endif // !USE_IPHONE
+
+
+
- (void) setFrame:(NSRect) newRect
{
[super setFrame:newRect];
- if (xwindow) // inform Xlib that the window has changed.
- jwxyz_window_resized (xdpy, xwindow);
+
+# 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
+ }
+}
+
+
+# ifndef USE_IPHONE // Doesn't exist on iOS
+- (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
}
+# endif // !USE_IPHONE
+(BOOL) performGammaFade
return YES;
}
+#ifndef USE_IPHONE
- (NSWindow *) configureSheet
+#else
+- (UIViewController *) configureView
+#endif
{
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
NSString *file = [NSString stringWithCString:xsft->progclass
return nil;
}
- NSWindow *sheet = [[XScreenSaverConfigSheet alloc]
- initWithXMLFile:path
- options:xsft->options
- controller:[prefsReader userDefaultsController]];
-
+# ifdef USE_IPHONE
+ UIViewController *sheet;
+# else // !USE_IPHONE
+ NSWindow *sheet;
+# endif // !USE_IPHONE
+
+ sheet = [[XScreenSaverConfigSheet alloc]
+ initWithXMLFile:path
+ options:xsft->options
+ controller:[prefsReader userDefaultsController]
+ 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.
[sheet retain];
}
+#ifndef USE_IPHONE
+
/* Convert an NSEvent into an XEvent, and pass it along.
Returns YES if it was handled.
*/
![self isAnimating] ||
!initted_p)
return NO;
-
+
XEvent xe;
memset (&xe, 0, sizeof(xe));
NSPoint p = [[[e window] contentView] convertPoint:[e locationInWindow]
toView:self];
- int x = p.x;
- int y = [self frame].size.height - p.y;
-
+# ifdef USE_IPHONE
+ double s = self.contentScaleFactor;
+# else
+ int s = 1;
+# endif
+ int x = s * p.x;
+ int y = s * ([self frame].size.height - p.y);
+
xe.xany.type = type;
switch (type) {
case ButtonPress:
case KeyPress:
case KeyRelease:
{
- NSString *nss = [e characters];
- const char *s = [nss cStringUsingEncoding:NSISOLatin1StringEncoding];
- xe.xkey.keycode = (s && *s ? *s : 0);
+ NSString *ns = (([e type] == NSFlagsChanged) ? 0 :
+ [e charactersIgnoringModifiers]);
+ KeySym k = 0;
+
+ if (!ns || [ns length] == 0) // dead key
+ {
+ // Cocoa hides the difference between left and right keys.
+ // Also we only get KeyPress events for these, no KeyRelease
+ // (unless we hack the mod state manually. Bleh.)
+ //
+ if (flags & NSAlphaShiftKeyMask) k = XK_Caps_Lock;
+ else if (flags & NSShiftKeyMask) k = XK_Shift_L;
+ else if (flags & NSControlKeyMask) k = XK_Control_L;
+ else if (flags & NSAlternateKeyMask) k = XK_Alt_L;
+ else if (flags & NSCommandKeyMask) k = XK_Meta_L;
+ }
+ else if ([ns length] == 1) // real key
+ {
+ switch ([ns characterAtIndex:0]) {
+ case NSLeftArrowFunctionKey: k = XK_Left; break;
+ case NSRightArrowFunctionKey: k = XK_Right; break;
+ case NSUpArrowFunctionKey: k = XK_Up; break;
+ case NSDownArrowFunctionKey: k = XK_Down; break;
+ case NSPageUpFunctionKey: k = XK_Page_Up; break;
+ case NSPageDownFunctionKey: k = XK_Page_Down; break;
+ case NSHomeFunctionKey: k = XK_Home; break;
+ case NSPrevFunctionKey: k = XK_Prior; break;
+ case NSNextFunctionKey: k = XK_Next; break;
+ case NSBeginFunctionKey: k = XK_Begin; break;
+ case NSEndFunctionKey: k = XK_End; break;
+ default:
+ {
+ const char *s =
+ [ns cStringUsingEncoding:NSISOLatin1StringEncoding];
+ k = (s && *s ? *s : 0);
+ }
+ break;
+ }
+ }
+
+ xe.xkey.keycode = k;
xe.xkey.state = state;
break;
}
[super keyUp:e];
}
+- (void) flagsChanged: (NSEvent *) e
+{
+ if (! [self doEvent:e type:KeyPress])
+ [super flagsChanged:e];
+}
+
+#else // USE_IPHONE
+
+
+/* Called after the device's orientation has changed.
+
+ Note: we could include a subclass of UIViewController which
+ contains a shouldAutorotateToInterfaceOrientation method that
+ returns YES, in which case Core Animation would auto-rotate our
+ View for us in response to rotation events... but, that interacts
+ badly with the EAGLContext -- if you introduce Core Animation into
+ the path, the OpenGL pipeline probably falls back on software
+ rendering and performance goes to hell. Also, the scaling and
+ rotation that Core Animation does interacts incorrectly with the GL
+ context anyway.
+
+ So, we have to hack the rotation animation manually, in the GL world.
+
+ Possibly XScreenSaverView should use Core Animation, and
+ XScreenSaverGLView should override that.
+*/
+- (void)didRotate:(NSNotification *)notification
+{
+ UIDeviceOrientation current = [[UIDevice currentDevice] orientation];
+
+ /* If the simulator starts up in the rotated position, sometimes
+ the UIDevice says we're in Portrait when we're not -- but it
+ turns out that the UINavigationController knows what's up!
+ So get it from there.
+ */
+ if (current == UIDeviceOrientationUnknown) {
+ switch ([[[self window] rootViewController] interfaceOrientation]) {
+ case UIInterfaceOrientationPortrait:
+ current = UIDeviceOrientationPortrait;
+ break;
+ case UIInterfaceOrientationPortraitUpsideDown:
+ current = UIDeviceOrientationPortraitUpsideDown;
+ break;
+ case UIInterfaceOrientationLandscapeLeft: // It's opposite day
+ current = UIDeviceOrientationLandscapeRight;
+ break;
+ case UIInterfaceOrientationLandscapeRight:
+ current = UIDeviceOrientationLandscapeLeft;
+ break;
+ default:
+ break;
+ }
+ }
+
+ /* On the iPad (but not iPhone 3GS, or the simulator) sometimes we get
+ an orientation change event with an unknown orientation. Those seem
+ to always be immediately followed by another orientation change with
+ a *real* orientation change, so let's try just ignoring those bogus
+ ones and hoping that the real one comes in shortly...
+ */
+ if (current == UIDeviceOrientationUnknown)
+ return;
+
+ if (rotation_ratio >= 0) return; // in the midst of rotation animation
+ if (orientation == current) return; // no change
+
+ new_orientation = current; // current animation target
+ rotation_ratio = 0; // start animating
+ rot_start_time = double_time();
+
+ switch (orientation) {
+ case UIInterfaceOrientationLandscapeRight: angle_from = 90; break;
+ case UIInterfaceOrientationLandscapeLeft: angle_from = 270; break;
+ case UIInterfaceOrientationPortraitUpsideDown: angle_from = 180; break;
+ default: angle_from = 0; break;
+ }
+
+ switch (new_orientation) {
+ case UIInterfaceOrientationLandscapeRight: angle_to = 90; break;
+ case UIInterfaceOrientationLandscapeLeft: angle_to = 270; break;
+ case UIInterfaceOrientationPortraitUpsideDown: angle_to = 180; break;
+ default: angle_to = 0; break;
+ }
+
+ NSRect ff = [self frame];
+
+ switch (orientation) {
+ case UIInterfaceOrientationLandscapeLeft: // from landscape
+ case UIInterfaceOrientationLandscapeRight:
+ rot_from.width = ff.size.height;
+ rot_from.height = ff.size.width;
+ break;
+ default: // from portrait
+ rot_from.width = ff.size.width;
+ rot_from.height = ff.size.height;
+ break;
+ }
+
+ switch (new_orientation) {
+ case UIInterfaceOrientationLandscapeLeft: // to landscape
+ case UIInterfaceOrientationLandscapeRight:
+ rot_to.width = ff.size.height;
+ rot_to.height = ff.size.width;
+ break;
+ default: // to portrait
+ rot_to.width = ff.size.width;
+ rot_to.height = ff.size.height;
+ break;
+ }
+
+ if (! initted_p) {
+ // If we've done a rotation but the saver hasn't been initialized yet,
+ // don't bother going through an X11 resize, but just do it now.
+ rot_start_time = 0; // dawn of time
+ [self hackRotation];
+ }
+}
+
+
+/* In the simulator, multi-touch sequences look like this:
+
+ touchesBegan [touchA, touchB]
+ touchesEnd [touchA, touchB]
+
+ But on real devices, sometimes you get that, but sometimes you get:
+
+ touchesBegan [touchA, touchB]
+ touchesEnd [touchB]
+ touchesEnd [touchA]
+
+ Or even
+
+ touchesBegan [touchA]
+ touchesBegan [touchB]
+ touchesEnd [touchA]
+ touchesEnd [touchB]
+
+ So the only way to properly detect a "pinch" gesture is to remember
+ the start-point of each touch as it comes in; and the end-point of
+ each touch as those come in; and only process the gesture once the
+ number of touchEnds matches the number of touchBegins.
+ */
+
+static void
+rotate_mouse (int *x, int *y, int w, int h, int rot)
+{
+ 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; }
+}
+
+
+- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
+{
+ if (xsft->event_cb && xwindow) {
+ double s = self.contentScaleFactor;
+ XEvent xe;
+ memset (&xe, 0, sizeof(xe));
+ int i = 0;
+ int w = s * [self frame].size.width;
+ int h = s * [self frame].size.height;
+ for (UITouch *touch in touches) {
+ CGPoint p = [touch locationInView:self];
+ xe.xany.type = ButtonPress;
+ xe.xbutton.button = i + 1;
+ 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);
+ jwxyz_mouse_moved (xdpy, xwindow, xe.xbutton.x, xe.xbutton.y);
+ xsft->event_cb (xdpy, xwindow, xdata, &xe);
+ i++;
+ }
+ }
+}
+
+
+- (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;
+ XEvent xe;
+ memset (&xe, 0, sizeof(xe));
+ int i = 0;
+ int w = s * [self frame].size.width;
+ int h = s * [self frame].size.height;
+ for (UITouch *touch in touches) {
+ CGPoint p = [touch locationInView:self];
+ 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);
+ jwxyz_mouse_moved (xdpy, xwindow, xe.xbutton.x, xe.xbutton.y);
+ xsft->event_cb (xdpy, xwindow, xdata, &xe);
+ i++;
+ }
+ }
+}
+
+
+- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
+{
+ if (xsft->event_cb && xwindow) {
+ double s = self.contentScaleFactor;
+ XEvent xe;
+ memset (&xe, 0, sizeof(xe));
+ int i = 0;
+ int w = s * [self frame].size.width;
+ int h = s * [self frame].size.height;
+ for (UITouch *touch in touches) {
+ CGPoint p = [touch locationInView:self];
+ 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);
+ jwxyz_mouse_moved (xdpy, xwindow, xe.xmotion.x, xe.xmotion.y);
+ xsft->event_cb (xdpy, xwindow, xdata, &xe);
+ i++;
+ }
+ }
+}
+
+
+/* We need this to respond to "shake" gestures
+ */
+- (BOOL)canBecomeFirstResponder {
+ return YES;
+}
+
+
+- (void)setScreenLocked:(BOOL)locked
+{
+ if (screenLocked == locked) return;
+ screenLocked = locked;
+ if (locked) {
+ if ([self isAnimating])
+ [self stopAnimation];
+ } else {
+ if (! [self isAnimating])
+ [self startAnimation];
+ }
+}
+
+
+#endif // USE_IPHONE
+
@end