+ CLAMP180(rot_current_angle);
+ _global_rot_current_angle_kludge = rot_current_angle;
+
+# undef CLAMP180
+
+ CGSize rotsize = ((ignore_rotation_p || ![self reshapeRotatedWindow])
+ ? initial_bounds
+ : rot_current_size);
+ if ((int) backbuffer_size.width != (int) rotsize.width ||
+ (int) backbuffer_size.height != (int) rotsize.height)
+ [self resize_x11];
+}
+
+
+- (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];
+ [self stopAnimation];
+}
+
+#endif // USE_IPHONE
+
+
+#ifdef USE_BACKBUFFER
+
+# ifndef USE_IPHONE
+
+struct gl_version
+{
+ // iOS always uses OpenGL ES 1.1.
+ unsigned major;
+ unsigned minor;
+};
+
+static GLboolean
+gl_check_ver (const struct gl_version *caps,
+ unsigned gl_major,
+ unsigned gl_minor)
+{
+ return caps->major > gl_major ||
+ (caps->major == gl_major && caps->minor >= gl_minor);
+}
+
+# else
+
+static GLboolean
+gluCheckExtension (const GLubyte *ext_name, const GLubyte *ext_string)
+{
+ size_t ext_len = strlen ((const char *)ext_name);
+
+ for (;;) {
+ const GLubyte *found = (const GLubyte *)strstr ((const char *)ext_string,
+ (const char *)ext_name);
+ if (!found)
+ break;
+
+ char last_ch = found[ext_len];
+ if ((found == ext_string || found[-1] == ' ') &&
+ (last_ch == ' ' || !last_ch)) {
+ return GL_TRUE;
+ }
+
+ ext_string = found + ext_len;
+ }
+
+ return GL_FALSE;
+}
+
+# endif
+
+/* Called during startAnimation before the first call to createBackbuffer. */
+- (void) enableBackbuffer:(CGSize)new_backbuffer_size
+{
+# ifndef USE_IPHONE
+ struct gl_version version;
+
+ {
+ const char *version_str = (const char *)glGetString (GL_VERSION);
+
+ /* iPhone is always OpenGL ES 1.1. */
+ if (sscanf ((const char *)version_str, "%u.%u",
+ &version.major, &version.minor) < 2)
+ {
+ version.major = 1;
+ version.minor = 1;
+ }
+ }
+# endif
+
+ // The OpenGL extensions in use in here are pretty are pretty much ubiquitous
+ // on OS X, but it's still good form to check.
+ const GLubyte *extensions = glGetString (GL_EXTENSIONS);
+
+ glGenTextures (1, &backbuffer_texture);
+
+ // On really old systems, it would make sense to split the texture
+ // into subsections
+# ifndef USE_IPHONE
+ gl_texture_target = (gluCheckExtension ((const GLubyte *)
+ "GL_ARB_texture_rectangle",
+ extensions)
+ ? GL_TEXTURE_RECTANGLE_EXT : GL_TEXTURE_2D);
+# else
+ // OES_texture_npot also provides this, but iOS never provides it.
+ gl_limited_npot_p = gluCheckExtension ((const GLubyte *)
+ "GL_APPLE_texture_2D_limited_npot",
+ extensions);
+ gl_texture_target = GL_TEXTURE_2D;
+# endif
+
+ glBindTexture (gl_texture_target, &backbuffer_texture);
+ glTexParameteri (gl_texture_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ // GL_LINEAR might make sense on Retina iPads.
+ glTexParameteri (gl_texture_target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameteri (gl_texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri (gl_texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+# ifndef USE_IPHONE
+ // There isn't much sense in supporting one of these if the other
+ // isn't present.
+ gl_apple_client_storage_p =
+ gluCheckExtension ((const GLubyte *)"GL_APPLE_client_storage",
+ extensions) &&
+ gluCheckExtension ((const GLubyte *)"GL_APPLE_texture_range", extensions);
+
+ if (gl_apple_client_storage_p) {
+ glTexParameteri (gl_texture_target, GL_TEXTURE_STORAGE_HINT_APPLE,
+ GL_STORAGE_SHARED_APPLE);
+ glPixelStorei (GL_UNPACK_CLIENT_STORAGE_APPLE, GL_TRUE);
+ }
+# endif
+
+ // If a video adapter suports BGRA textures, then that's probably as fast as
+ // you're gonna get for getting a texture onto the screen.
+# ifdef USE_IPHONE
+ gl_pixel_format =
+ gluCheckExtension ((const GLubyte *)"GL_APPLE_texture_format_BGRA8888",
+ extensions) ? GL_BGRA : GL_RGBA;
+ gl_pixel_type = GL_UNSIGNED_BYTE;
+ // See also OES_read_format.
+# else
+ if (gl_check_ver (&version, 1, 2) ||
+ (gluCheckExtension ((const GLubyte *)"GL_EXT_bgra", extensions) &&
+ gluCheckExtension ((const GLubyte *)"GL_APPLE_packed_pixels",
+ extensions))) {
+ gl_pixel_format = GL_BGRA;
+ // Both Intel and PowerPC-era docs say to use GL_UNSIGNED_INT_8_8_8_8_REV.
+ gl_pixel_type = GL_UNSIGNED_INT_8_8_8_8_REV;
+ } else {
+ gl_pixel_format = GL_RGBA;
+ gl_pixel_type = GL_UNSIGNED_BYTE;
+ }
+ // GL_ABGR_EXT/GL_UNSIGNED_BYTE is another possibilty that may have made more
+ // sense on PowerPC.
+# endif
+
+ glEnable (gl_texture_target);
+ glEnableClientState (GL_VERTEX_ARRAY);
+ glEnableClientState (GL_TEXTURE_COORD_ARRAY);
+
+# ifdef USE_IPHONE
+ glMatrixMode (GL_PROJECTION);
+ glLoadIdentity();
+ NSAssert (new_backbuffer_size.width != 0 && new_backbuffer_size.height != 0,
+ @"initial_bounds never got set");
+ // This is pretty similar to the glOrtho in createBackbuffer for OS X.
+ glOrthof (-new_backbuffer_size.width, new_backbuffer_size.width,
+ -new_backbuffer_size.height, new_backbuffer_size.height, -1, 1);
+# endif // USE_IPHONE
+
+ check_gl_error ("enableBackbuffer");
+}
+
+
+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;
+}
+
+
+/* Create a bitmap context into which we render everything.
+ If the desired size has changed, re-created it.
+ new_size is in rotated pixels, not points: the same size
+ and shape as the X11 window as seen by the hacks.
+ */
+- (void) createBackbuffer:(CGSize)new_size
+{
+ // Colorspaces and CGContexts only happen with non-GL hacks.
+ if (colorspace)
+ CGColorSpaceRelease (colorspace);
+
+# ifdef BACKBUFFER_OPENGL
+ NSAssert ([NSOpenGLContext currentContext] ==
+ ogl_ctx, @"invalid GL context");
+
+ // This almost isn't necessary, except for the ugly aliasing artifacts.
+# ifndef USE_IPHONE
+ glViewport (0, 0, new_size.width, new_size.height);
+
+ glMatrixMode (GL_PROJECTION);
+ glLoadIdentity();
+ // This is pretty similar to the glOrthof in enableBackbuffer for iPhone.
+ glOrtho (-new_size.width, new_size.width, -new_size.height, new_size.height,
+ -1, 1);
+# endif // !USE_IPHONE
+# endif // BACKBUFFER_OPENGL
+
+ NSWindow *window = [self window];
+
+ if (window && xdpy) {
+ [self lockFocus];
+
+# ifdef BACKBUFFER_OPENGL
+ // Was apparently faster until 10.9.
+ colorspace = CGColorSpaceCreateDeviceRGB ();
+# endif // BACKBUFFER_OPENGL
+
+ [self unlockFocus];
+ } else {
+ colorspace = CGColorSpaceCreateDeviceRGB();
+ }
+
+ if (backbuffer &&
+ (int)backbuffer_size.width == (int)new_size.width &&
+ (int)backbuffer_size.height == (int)new_size.height)
+ return;
+
+ CGContextRef ob = backbuffer;
+ void *odata = backbuffer_data;
+ size_t olen = backbuffer_len;
+
+ CGSize osize = backbuffer_size; // pixels, not points.
+ backbuffer_size = new_size; // pixels, not points.
+
+# if TARGET_IPHONE_SIMULATOR
+ NSLog(@"backbuffer %.0fx%.0f",
+ backbuffer_size.width, backbuffer_size.height);
+# endif
+
+ /* OS X uses APPLE_client_storage and APPLE_texture_range, as described in
+ <https://developer.apple.com/library/mac/documentation/GraphicsImaging/Conceptual/OpenGL-MacProgGuide/opengl_texturedata/opengl_texturedata.html>.
+
+ iOS uses bog-standard glTexImage2D (for now).
+
+ glMapBuffer is the standard way to get data from system RAM to video
+ memory asynchronously and without a memcpy, but support for
+ APPLE_client_storage is ubiquitous on OS X (not so for glMapBuffer),
+ and on iOS GL_PIXEL_UNPACK_BUFFER is only available on OpenGL ES 3
+ (iPhone 5S or newer). Plus, glMapBuffer doesn't work well with
+ CGBitmapContext: glMapBuffer can return a different pointer on each
+ call, but a CGBitmapContext doesn't allow its data pointer to be
+ changed -- and recreating the context for a new pointer can be
+ expensive (glyph caches get dumped, for instance).
+
+ glMapBufferRange has MAP_FLUSH_EXPLICIT_BIT and MAP_UNSYNCHRONIZED_BIT,
+ and these seem to allow mapping the buffer and leaving it where it is
+ in client address space while OpenGL works with the buffer, but it
+ requires OpenGL 3 Core profile on OS X (and ES 3 on iOS for
+ GL_PIXEL_UNPACK_BUFFER), so point goes to APPLE_client_storage.
+
+ AMD_pinned_buffer provides the same advantage as glMapBufferRange, but
+ Apple never implemented that one for OS X.
+ */
+
+ backbuffer_data = NULL;
+ gl_texture_w = (int)backbuffer_size.width;
+ gl_texture_h = (int)backbuffer_size.height;
+
+ NSAssert (gl_texture_target == GL_TEXTURE_2D
+# ifndef USE_IPHONE
+ || gl_texture_target == GL_TEXTURE_RECTANGLE_EXT
+# endif
+ , @"unexpected GL texture target");
+
+# ifndef USE_IPHONE
+ if (gl_texture_target != GL_TEXTURE_RECTANGLE_EXT)
+# else
+ if (!gl_limited_npot_p)
+# endif
+ {
+ gl_texture_w = to_pow2 (gl_texture_w);
+ gl_texture_h = to_pow2 (gl_texture_h);
+ }
+
+ size_t 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
+ // it will fall back to a memcpy.
+ // https://developer.apple.com/library/mac/documentation/GraphicsImaging/Conceptual/OpenGL-MacProgGuide/opengl_texturedata/opengl_texturedata.html#//apple_ref/doc/uid/TP40001987-CH407-SW24
+ bytes_per_row = (bytes_per_row + 31) & ~31;
+# endif // BACKBUFFER_OPENGL && !USE_IPHONE
+
+ backbuffer_len = bytes_per_row * gl_texture_h;
+ if (backbuffer_len) // mmap requires this to be non-zero.
+ backbuffer_data = mmap (NULL, backbuffer_len,
+ PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED,
+ -1, 0);
+
+ BOOL alpha_first_p, order_little_p;
+
+ if (gl_pixel_format == GL_BGRA) {
+ alpha_first_p = YES;
+ order_little_p = YES;
+/*
+ } else if (gl_pixel_format == GL_ABGR_EXT) {
+ alpha_first_p = NO;
+ order_little_p = YES; */
+ } else {
+ NSAssert (gl_pixel_format == GL_RGBA, @"unknown GL pixel format");
+ alpha_first_p = NO;
+ order_little_p = NO;
+ }
+
+#ifdef USE_IPHONE
+ NSAssert (gl_pixel_type == GL_UNSIGNED_BYTE, @"unknown GL pixel type");
+#else
+ NSAssert (gl_pixel_type == GL_UNSIGNED_INT_8_8_8_8 ||
+ gl_pixel_type == GL_UNSIGNED_INT_8_8_8_8_REV ||
+ gl_pixel_type == GL_UNSIGNED_BYTE,
+ @"unknown GL pixel type");
+
+#if defined __LITTLE_ENDIAN__
+ const GLenum backwards_pixel_type = GL_UNSIGNED_INT_8_8_8_8;
+#elif defined __BIG_ENDIAN__
+ const GLenum backwards_pixel_type = GL_UNSIGNED_INT_8_8_8_8_REV;
+#else
+# error Unknown byte order.
+#endif
+
+ if (gl_pixel_type == backwards_pixel_type)
+ order_little_p ^= YES;
+#endif
+
+ CGBitmapInfo bitmap_info =
+ (alpha_first_p ? kCGImageAlphaNoneSkipFirst : kCGImageAlphaNoneSkipLast) |
+ (order_little_p ? kCGBitmapByteOrder32Little : kCGBitmapByteOrder32Big);
+
+ backbuffer = CGBitmapContextCreate (backbuffer_data,
+ (int)backbuffer_size.width,
+ (int)backbuffer_size.height,
+ 8,
+ bytes_per_row,
+ colorspace,
+ bitmap_info);
+ 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 defined(BACKBUFFER_OPENGL) && !defined(USE_IPHONE)
+ if (gl_apple_client_storage_p)
+ glTextureRangeAPPLE (gl_texture_target, backbuffer_len, backbuffer_data);
+# endif // BACKBUFFER_OPENGL && !USE_IPHONE
+
+ if (ob) {
+ // Restore old bits, as much as possible, to the X11 upper left origin.
+
+ CGRect rect; // pixels, not points
+ 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);
+
+ if (olen)
+ // munmap should round len up to the nearest page.
+ munmap (odata, olen);
+ }
+
+ check_gl_error ("createBackbuffer");
+}
+
+
+- (void) drawBackbuffer
+{
+# ifdef BACKBUFFER_OPENGL
+
+ NSAssert ([ogl_ctx isKindOfClass:[NSOpenGLContext class]],
+ @"ogl_ctx is not an NSOpenGLContext");
+
+ NSAssert (! (CGBitmapContextGetBytesPerRow (backbuffer) % 4),
+ @"improperly-aligned backbuffer");
+
+ // This gets width and height from the backbuffer in case
+ // APPLE_client_storage is in use. See the note in createBackbuffer.
+ // This still has to happen every frame even when APPLE_client_storage has
+ // the video adapter pulling texture data straight from
+ // XScreenSaverView-owned memory.
+ glTexImage2D (gl_texture_target, 0, GL_RGBA,
+ (GLsizei)(CGBitmapContextGetBytesPerRow (backbuffer) / 4),
+ gl_texture_h, 0, gl_pixel_format, gl_pixel_type,
+ backbuffer_data);
+
+ GLfloat vertices[4][2] =
+ {
+ {-backbuffer_size.width, backbuffer_size.height},
+ { backbuffer_size.width, backbuffer_size.height},
+ { backbuffer_size.width, -backbuffer_size.height},
+ {-backbuffer_size.width, -backbuffer_size.height}
+ };
+
+ GLfloat tex_coords[4][2];
+
+# ifndef USE_IPHONE
+ if (gl_texture_target == GL_TEXTURE_RECTANGLE_EXT) {
+ tex_coords[0][0] = 0;
+ tex_coords[0][1] = 0;
+ tex_coords[1][0] = backbuffer_size.width;
+ tex_coords[1][1] = 0;
+ tex_coords[2][0] = backbuffer_size.width;
+ tex_coords[2][1] = backbuffer_size.height;
+ tex_coords[3][0] = 0;
+ tex_coords[3][1] = backbuffer_size.height;
+ } else
+# endif // USE_IPHONE
+ {
+ GLfloat x = backbuffer_size.width / gl_texture_w;
+ GLfloat y = backbuffer_size.height / gl_texture_h;
+ tex_coords[0][0] = 0;
+ tex_coords[0][1] = 0;
+ tex_coords[1][0] = x;
+ tex_coords[1][1] = 0;
+ tex_coords[2][0] = x;
+ tex_coords[2][1] = y;
+ tex_coords[3][0] = 0;
+ tex_coords[3][1] = y;
+ }
+
+# ifdef USE_IPHONE
+ if (!ignore_rotation_p) {
+ glMatrixMode (GL_MODELVIEW);
+ glLoadIdentity();
+ glRotatef (rot_current_angle, 0, 0, -1);
+
+ if (rotation_ratio >= 0)
+ glClear (GL_COLOR_BUFFER_BIT);
+ }
+# endif // USE_IPHONE
+
+ glVertexPointer (2, GL_FLOAT, 0, vertices);
+ glTexCoordPointer (2, GL_FLOAT, 0, tex_coords);
+ glDrawArrays (GL_TRIANGLE_FAN, 0, 4);
+
+# if !defined __OPTIMIZE__ || TARGET_IPHONE_SIMULATOR
+ check_gl_error ("drawBackbuffer");
+# endif
+
+ // This can also happen near the beginning of render_x11.
+ [self flushBackbuffer];
+
+# endif // BACKBUFFER_OPENGL
+}
+
+
+- (void)flushBackbuffer
+{
+# ifndef USE_IPHONE
+ // The OpenGL pipeline is not automatically synchronized with the contents
+ // of the backbuffer, so without glFinish, OpenGL can start rendering from
+ // the backbuffer texture at the same time that JWXYZ is clearing and
+ // drawing the next frame in the backing store for the backbuffer texture.
+ glFinish();
+
+ if (double_buffered_p)
+ [ogl_ctx flushBuffer]; // despite name, this actually swaps
+# else
+ glBindRenderbufferOES (GL_RENDERBUFFER_OES, gl_renderbuffer);
+ [ogl_ctx presentRenderbuffer:GL_RENDERBUFFER_OES];
+# endif
+
+# if !defined __OPTIMIZE__ || TARGET_IPHONE_SIMULATOR
+ // glGetError waits for the OpenGL command pipe to flush, so skip it in
+ // release builds.
+ // OpenGL Programming Guide for Mac -> OpenGL Application Design
+ // Strategies -> Allow OpenGL to Manage Your Resources
+ // https://developer.apple.com/library/mac/documentation/GraphicsImaging/Conceptual/OpenGL-MacProgGuide/opengl_designstrategies/opengl_designstrategies.html#//apple_ref/doc/uid/TP40001987-CH2-SW7
+ check_gl_error ("flushBackbuffer");
+# endif
+}
+
+
+#endif // USE_BACKBUFFER
+
+
+/* Inform X11 that the size of our window has changed.
+ */
+- (void) resize_x11
+{
+ if (!xwindow) return; // early
+
+ CGSize new_size; // pixels, not points
+
+# ifdef USE_BACKBUFFER
+
+ [self prepareContext];
+
+# if defined(BACKBUFFER_OPENGL) && !defined(USE_IPHONE)
+ [ogl_ctx update];
+# endif // BACKBUFFER_OPENGL && !USE_IPHONE
+
+# ifdef USE_IPHONE
+ CGSize rotsize = ((ignore_rotation_p || ![self reshapeRotatedWindow])
+ ? initial_bounds
+ : rot_current_size);
+ new_size.width = rotsize.width;
+ new_size.height = rotsize.height;
+# else // !USE_IPHONE
+ new_size = NSSizeToCGSize([self bounds].size);
+# endif // !USE_IPHONE
+
+ [self createBackbuffer:new_size];
+ jwxyz_window_resized (xdpy, xwindow, 0, 0, new_size.width, new_size.height,
+ backbuffer);
+# else // !USE_BACKBUFFER
+ new_size = [self bounds].size;
+ jwxyz_window_resized (xdpy, xwindow, 0, 0, new_size.width, new_size.height,
+ 0);
+# endif // !USE_BACKBUFFER
+
+# if TARGET_IPHONE_SIMULATOR
+ NSLog(@"reshape %.0fx%.0f", new_size.width, new_size.height);
+# endif
+
+ // 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];
+# endif
+
+ if (!initted_p) {
+
+ if (! xdpy) {
+# 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
+ /* 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) {
+ setup_p = YES;
+ if (xsft->setup_cb)
+ xsft->setup_cb (xsft, xsft->setup_arg);
+ }
+ initted_p = YES;
+ resized_p = NO;
+ NSAssert(!xdata, @"xdata already initialized");
+
+
+# undef ya_rand_init
+ ya_rand_init (0);
+
+ XSetWindowBackground (xdpy, xwindow,
+ get_pixel_resource (xdpy, 0,
+ "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.
+ It seems like it might make things worse rather than better,
+ though... Plus it makes us binary-incompatible with 10.4.
+
+# if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
+ [[self window] setPreferredBackingLocation:
+ NSWindowBackingLocationVideoMemory];
+# endif
+ */
+
+ /* Kludge: even though the init_cb functions are declared to take 2 args,
+ actually call them with 3, for the benefit of xlockmore_init() and
+ xlockmore_setup().
+ */
+ void *(*init_cb) (Display *, Window, void *) =
+ (void *(*) (Display *, Window, void *)) xsft->init_cb;
+
+ xdata = init_cb (xdpy, xwindow, xsft->setup_arg);
+ // NSAssert(xdata, @"no xdata from init");
+ if (! xdata) abort();
+
+ if (get_boolean_resource (xdpy, "doFPS", "DoFPS")) {
+ fpst = fps_init (xdpy, xwindow);
+ if (! xsft->fps_cb) xsft->fps_cb = screenhack_do_fps;
+ } else {
+ fpst = NULL;
+ xsft->fps_cb = 0;
+ }
+
+ [self checkForUpdates];
+ }
+
+
+ /* 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.
+ */
+ long usecs = 1000000 * [self animationTimeInterval];
+ usecs -= 200; // caller apparently sleeps for slightly less sometimes...
+ if (usecs < 0) usecs = 0;
+ fps_slept (fpst, usecs);
+ }
+
+
+ /* It turns out that on some systems (possibly only 10.5 and older?)
+ [ScreenSaverView setAnimationTimeInterval] does nothing. This means
+ that we cannot rely on it.
+
+ Some of the screen hacks want to delay for long periods, and letting the
+ framework run the update function at 30 FPS when it really wanted half a
+ minute between frames would be bad. So instead, we assume that the
+ framework's animation timer might fire whenever, but we only invoke the
+ screen hack's "draw frame" method when enough time has expired.
+
+ This means two extra calls to gettimeofday() per frame. For fast-cycling
+ screen savers, that might actually slow them down. Oh well.
+
+ A side-effect of this is that it's not possible for a saver to request
+ an animation interval that is faster than animationTimeInterval.
+
+ HOWEVER! On modern systems where setAnimationTimeInterval is *not*
+ ignored, it's important that it be faster than 30 FPS. 240 FPS is good.
+
+ An NSTimer won't fire if the timer is already running the invocation
+ function from a previous firing. So, if we use a 30 FPS
+ animationTimeInterval (33333 µs) and a screenhack takes 40000 µs for a
+ frame, there will be a 26666 µs delay until the next frame, 66666 µs
+ after the beginning of the current frame. In other words, 25 FPS
+ becomes 15 FPS.
+
+ Frame rates tend to snap to values of 30/N, where N is a positive
+ integer, i.e. 30 FPS, 15 FPS, 10, 7.5, 6. And the 'snapped' frame rate
+ is rounded down from what it would normally be.
+
+ So if we set animationTimeInterval to 1/240 instead of 1/30, frame rates
+ become values of 60/N, 120/N, or 240/N, with coarser or finer frame rate
+ steps for higher or lower animation time intervals respectively.
+ */
+ struct timeval tv;
+ gettimeofday (&tv, 0);
+ double now = tv.tv_sec + (tv.tv_usec / 1000000.0);
+ if (now < next_frame_time) return;
+
+ [self prepareContext]; // resize_x11 also calls this.
+ // [self flushBackbuffer];
+
+ if (resized_p) {
+ // We do this here instead of in setFrame so that all the
+ // Xlib drawing takes place under the animation timer.
+
+# ifndef USE_IPHONE
+ if (ogl_ctx)
+ [ogl_ctx setView:self];
+# endif // !USE_IPHONE
+
+ NSRect r;
+# 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_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.)
+ //
+ jwxyz_sources_run (display_sources_data (xdpy));
+
+
+ // And finally:
+ //
+ // 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);
+
+ gettimeofday (&tv, 0);
+ now = tv.tv_sec + (tv.tv_usec / 1000000.0);
+ next_frame_time = now + (delay / 1000000.0);
+
+ [self drawBackbuffer];
+
+# 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
+ following way:
+
+ It only does a collection when a threshold of outstanding
+ collectable allocations has been surpassed. However, CoreGraphics
+ creates lots of small collectable allocations that contain pointers
+ to very large non-collectable allocations: a small CG object that's
+ collectable referencing large malloc'd allocations (non-collectable)
+ containing bitmap data. So the large allocation doesn't get freed
+ until GC collects the small allocation, which triggers its finalizer
+ to run which frees the large allocation. So GC is deciding that it
+ doesn't really need to run, even though the process has gotten
+ enormous. GC eventually runs once pageouts have happened, but by
+ then it's too late, and the machine's resident set has been
+ sodomized.
+
+ So, we force an exhaustive garbage collection in this process
+ approximately every 5 seconds whether the system thinks it needs
+ one or not.
+ */
+ {
+ static int tick = 0;
+ if (++tick > 5*30) {
+ tick = 0;
+ objc_collect (OBJC_EXHAUSTIVE_COLLECTION);
+ }
+ }
+# endif // DO_GC_HACKERY
+
+# ifdef USE_IPHONE
+ }
+ @catch (NSException *e) {
+ [self handleException: e];
+ }
+# endif // USE_IPHONE
+}
+
+
+#ifndef USE_BACKBUFFER
+
+- (void) animateOneFrame
+{
+ [self render_x11];
+ jwxyz_flush_context(xdpy);
+}
+
+#else // USE_BACKBUFFER
+
+- (void) animateOneFrame
+{
+ // Render X11 into the backing store bitmap...
+
+ NSAssert (backbuffer, @"no back buffer");
+
+# ifdef USE_IPHONE
+ UIGraphicsPushContext (backbuffer);
+# endif
+
+ [self render_x11];
+
+# if defined USE_IPHONE && defined USE_BACKBUFFER
+ UIGraphicsPopContext();
+# endif
+}
+
+#endif // USE_BACKBUFFER
+
+
+
+- (void) setFrame:(NSRect) newRect